Skip to content

Commit 8d8cf3a

Browse files
committed
Allowed null fields in user PATCH endpoint (to unlink profile)
1 parent 3e81fe6 commit 8d8cf3a

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

src/lib/components/v2/admin/UserEditor.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@
630630
class="form-select"
631631
bind:value={selectedResourceId}
632632
onchange={resourceChanged}
633+
aria-label="Select resource"
633634
>
634635
<option value={undefined}>Select resource...</option>
635636
{#each resources as resource (resource.id)}
@@ -644,6 +645,7 @@
644645
bind:value={editableUser.profile_id}
645646
class:is-invalid={userFormSubmitted && $userValidationErrors['profile_id']}
646647
disabled={selectedResourceId === undefined}
648+
aria-label="Select profile"
647649
>
648650
<option value={null}>Select profile...</option>
649651
{#each profiles as profile (profile.id)}

src/routes/v2/admin/users/[userId]/edit/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
is_verified: user.is_verified,
3636
profile_id: user.profile_id
3737
},
38-
{ stripEmptyElements: true }
38+
{ nullifyEmptyStrings: true }
3939
)
4040
});
4141
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { expect, test } from '@playwright/test';
2+
import { setUploadFile, waitModal, waitModalClosed, waitPageLoading } from '../utils.js';
3+
import path from 'path';
4+
import { fileURLToPath } from 'url';
5+
6+
const __filename = fileURLToPath(import.meta.url);
7+
const __dirname = path.dirname(__filename);
8+
9+
const resourceFile = path.join(__dirname, '..', 'data', 'resource.json');
10+
11+
const randomResourceName = `${Math.random().toString(36).substring(7)} resource`;
12+
const randomProfileName = `${Math.random().toString(36).substring(7)} profile`;
13+
14+
test('Associate a profile to a user', async ({ page }) => {
15+
await test.step('Create a new resource', async () => {
16+
await page.goto('/v2/admin/resources/create');
17+
await waitPageLoading(page);
18+
await setUploadFile(page, 'Load from file', resourceFile);
19+
await expect(page.locator('textarea')).toHaveValue(/{/);
20+
const text = await page.locator('textarea').inputValue();
21+
const resource = JSON.parse(/**@type {string} */ (text));
22+
await page.locator('textarea').fill(JSON.stringify({ ...resource, name: randomResourceName }));
23+
await page.getByRole('button', { name: 'Create resource' }).click();
24+
await page.waitForURL(/\/v2\/admin\/resources$/);
25+
await waitPageLoading(page);
26+
});
27+
28+
await test.step('Open resource profiles', async () => {
29+
await page
30+
.getByRole('row', { name: randomResourceName })
31+
.getByRole('link', { name: 'Profiles' })
32+
.click();
33+
await page.waitForURL(/\/v2\/admin\/resources\/\d+\/profiles$/);
34+
await waitPageLoading(page);
35+
});
36+
37+
await test.step('Create new profile', async () => {
38+
await page.getByRole('link', { name: 'New profile' }).click();
39+
await page.waitForURL(/\/v2\/admin\/resources\/\d+\/profiles\/create$/);
40+
await waitPageLoading(page);
41+
await page.getByRole('textbox', { name: 'Profile name' }).fill(randomProfileName);
42+
await page.getByRole('button', { name: 'Save' }).click();
43+
await page.waitForURL(/\/v2\/admin\/resources\/\d+\/profiles$/);
44+
await waitPageLoading(page);
45+
});
46+
47+
await test.step('Create test user and associate it with new profile', async () => {
48+
const randomEmail = Math.random().toString(36).substring(7) + '@example.com';
49+
await page.goto('/v2/admin/users/register');
50+
await waitPageLoading(page);
51+
await page.getByRole('textbox', { name: 'E-mail' }).fill(randomEmail);
52+
await page.getByLabel('Password', { exact: true }).fill('test');
53+
await page.getByLabel('Confirm password').fill('test');
54+
await page.getByRole('combobox', { name: 'Select resource' }).selectOption(randomResourceName);
55+
await page.getByRole('combobox', { name: 'Select profile' }).selectOption(randomProfileName);
56+
await page.getByRole('button', { name: 'Save' }).click();
57+
await page.waitForURL(/\/v2\/admin\/users\/\d+\/edit/);
58+
await waitPageLoading(page);
59+
});
60+
61+
await test.step('Reload the page and verify the selected profile', async () => {
62+
await page.reload();
63+
await waitPageLoading(page);
64+
await expect(
65+
page
66+
.getByRole('combobox', { name: 'Select resource' })
67+
.getByRole('option', { selected: true })
68+
).toHaveText(randomResourceName);
69+
await expect(
70+
page.getByRole('combobox', { name: 'Select profile' }).getByRole('option', { selected: true })
71+
).toHaveText(randomProfileName);
72+
});
73+
74+
await test.step('Unset the profile', async () => {
75+
await page.getByRole('combobox', { name: 'Select profile' }).selectOption('Select profile...');
76+
await page.getByRole('button', { name: 'Save' }).click();
77+
await expect(page.getByText('User successfully updated')).toBeVisible();
78+
});
79+
80+
await test.step('Reload the page and verify that no profile is selected', async () => {
81+
await page.reload();
82+
await waitPageLoading(page);
83+
await expect(
84+
page
85+
.getByRole('combobox', { name: 'Select resource' })
86+
.getByRole('option', { selected: true })
87+
).toHaveText('Select resource...');
88+
await expect(
89+
page.getByRole('combobox', { name: 'Select profile' }).getByRole('option', { selected: true })
90+
).toHaveText('Select profile...');
91+
});
92+
93+
await test.step('Delete profile', async () => {
94+
await page.goto('/v2/admin/resources');
95+
await waitPageLoading(page);
96+
await page
97+
.getByRole('row', { name: randomResourceName })
98+
.getByRole('link', { name: 'Profiles' })
99+
.click();
100+
await page.waitForURL(/\/v2\/admin\/resources\/\d+\/profiles$/);
101+
await waitPageLoading(page);
102+
await page
103+
.getByRole('row', { name: randomProfileName })
104+
.getByRole('button', { name: 'Delete' })
105+
.click();
106+
const modal = await waitModal(page);
107+
await modal.getByRole('button', { name: 'Confirm' }).click();
108+
await waitModalClosed(page);
109+
await expect(page.getByRole('row', { name: randomProfileName })).not.toBeVisible();
110+
});
111+
112+
await test.step('Delete test resource', async () => {
113+
await page.getByRole('link', { name: 'Resources' }).click();
114+
await page.waitForURL(/\/v2\/admin\/resources$/);
115+
await waitPageLoading(page);
116+
await page
117+
.getByRole('row', { name: randomResourceName })
118+
.getByRole('button', { name: 'Delete' })
119+
.click();
120+
const modal = await waitModal(page);
121+
await modal.getByRole('button', { name: 'Confirm' }).click();
122+
await waitModalClosed(page);
123+
await expect(page.getByRole('row', { name: randomResourceName })).not.toBeVisible();
124+
});
125+
});

0 commit comments

Comments
 (0)