Skip to content

Commit be691d3

Browse files
committed
feat: send cookies
1 parent b754eaa commit be691d3

4 files changed

Lines changed: 71 additions & 15 deletions

File tree

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { createEvent, createStore } from 'effector';
22

3-
import { Extensions } from '#shared/constants';
4-
53
export const profileImportExtensionNameChanged = createEvent<string>();
64
export const profileImportExtensionNameCleared = createEvent();
75
export const profileImportExtensionNameReset = createEvent();
86

9-
export const $profileImportExtensionName = createStore<string | null>(Extensions.ModHeader)
7+
// Defaults to `null` so the regular "Import profile" flow never runs an extension adapter.
8+
// The "Import from other extension" modal selects a concrete extension on mount (see
9+
// ImportFromExtensionModal). This avoids a race: the wiring that clears the name on
10+
// `importModalOpened` lives in a lazily-loaded chunk and may not be registered yet when the modal
11+
// opens, so a ModHeader default leaked into plain imports and crashed the ModHeader adapter.
12+
export const $profileImportExtensionName = createStore<string | null>(null)
1013
.on(profileImportExtensionNameChanged, (_, string) => string)
1114
.on(profileImportExtensionNameCleared, () => null)
1215
.reset(profileImportExtensionNameReset);

src/features/import-profile/utils/validateProfileList.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Profile, RequestHeader } from '#entities/request-profile/types';
1+
import { Profile, RequestCookie, RequestHeader } from '#entities/request-profile/types';
22
import { generateIdWithExcludeList } from '#shared/utils/generateId';
33

44
export function validateProfileList(profileList: Profile[], existingProfileList: Profile[]) {
@@ -45,6 +45,32 @@ export function validateProfileList(profileList: Profile[], existingProfileList:
4545
}
4646
});
4747

48+
if (profile.requestCookies !== undefined && !Array.isArray(profile.requestCookies)) {
49+
throw new Error(`The profile ${currentProfileNumber} "requestCookies" value must be an array`);
50+
}
51+
52+
(profile.requestCookies ?? []).forEach((cookie: Partial<RequestCookie>, cookieIndex: number) => {
53+
const currentCookieNumber = cookieIndex + 1;
54+
55+
if (typeof cookie.name !== 'string') {
56+
throw new Error(
57+
`The cookie ${currentCookieNumber} in profile ${currentProfileNumber} must have a string "name" value`,
58+
);
59+
}
60+
61+
if (typeof cookie.value !== 'string') {
62+
throw new Error(
63+
`The cookie ${currentCookieNumber} in profile ${currentProfileNumber} must have a string "value" value`,
64+
);
65+
}
66+
67+
if (typeof cookie.disabled !== 'boolean') {
68+
throw new Error(
69+
`The cookie ${currentCookieNumber} in profile ${currentProfileNumber} must have a boolean "disabled" value`,
70+
);
71+
}
72+
});
73+
4874
existingProfileList.forEach((existingProfile, existingProfileIndex) => {
4975
const currentExistingProfileNumber = existingProfileIndex + 1;
5076

@@ -74,6 +100,9 @@ export function generateProfileList(profileList: Profile[], existingProfileList:
74100
const existingProfileRequestHeadersListId = existingProfileList.flatMap(profile =>
75101
profile.requestHeaders.map(header => header.id),
76102
);
103+
const existingProfileRequestCookiesListId = existingProfileList.flatMap(profile =>
104+
(profile.requestCookies ?? []).map(cookie => cookie.id),
105+
);
77106

78107
return profileList.map(profile => ({
79108
...profile,
@@ -82,6 +111,10 @@ export function generateProfileList(profileList: Profile[], existingProfileList:
82111
...header,
83112
id: generateIdWithExcludeList(existingProfileRequestHeadersListId),
84113
})),
114+
requestCookies: (profile.requestCookies ?? []).map((cookie: RequestCookie) => ({
115+
...cookie,
116+
id: generateIdWithExcludeList(existingProfileRequestCookiesListId),
117+
})),
85118
urlFilters: profile.urlFilters || [],
86119
}));
87120
}

src/widgets/modals/components/ImportFromExtensionModal/ImportFromExtensionModal.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ export function ImportFromExtensionModal() {
4646

4747
const textFieldRef = useRef<HTMLTextAreaElement>(null);
4848

49+
// Preselect ModHeader when opening the modal without a chosen extension. Done here (not via a
50+
// store default) so the plain "Import profile" flow keeps a `null` extension name and never runs
51+
// an adapter.
52+
useEffect(() => {
53+
if (!profileImportExtensionName) {
54+
onProfileImportExtensionNameChanged(Extensions.ModHeader);
55+
}
56+
// eslint-disable-next-line react-hooks/exhaustive-deps
57+
}, []);
58+
4959
useEffect(() => {
5060
if (isError && textFieldRef.current) {
5161
textFieldRef.current.focus();

tests/e2e/profiles.spec.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,10 @@ test.describe('Profile Actions', () => {
106106
const profilesBefore = page.locator('[data-test-id="profile-select"]');
107107
const countBefore = await profilesBefore.count();
108108

109-
// Open the profile actions menu
110-
await openProfileActionsMenu(page);
111-
112-
// Select "Delete profile"
113-
const deleteOption = page.getByRole('menuitem', { name: 'Delete profile' });
114-
await expect(deleteOption).toBeVisible({ timeout: 5000 });
115-
await deleteOption.click();
109+
// Delete the selected profile via the dedicated trash button
110+
const removeProfileButton = page.locator('[data-test-id="remove-profile-button"]');
111+
await expect(removeProfileButton).toBeEnabled({ timeout: 5000 });
112+
await removeProfileButton.click();
116113

117114
// Wait for the profile to be removed
118115
const profilesAfter = page.locator('[data-test-id="profile-select"]');
@@ -228,19 +225,25 @@ test.describe('Profile Actions', () => {
228225
const jsonTextarea = page.locator('[data-test-id="import-profile-json-textarea"] textarea');
229226
await expect(jsonTextarea).toBeVisible({ timeout: 10000 });
230227

228+
// Mirror the real export/import shape: profiles carry no id/name, and headers/cookies/filters
229+
// are stored without ids (ids are regenerated on import).
231230
const importJson = JSON.stringify([
232231
{
233-
id: 'imported-profile-1',
234-
name: 'Imported Profile',
232+
urlFilters: [],
235233
requestHeaders: [
236234
{
237-
id: 1,
238235
name: 'X-Imported-Header',
239236
value: 'imported-value',
240237
disabled: false,
241238
},
242239
],
243-
urlFilters: [],
240+
requestCookies: [
241+
{
242+
name: 'imported_session',
243+
value: 'cookie-value-123',
244+
disabled: false,
245+
},
246+
],
244247
},
245248
]);
246249

@@ -253,6 +256,13 @@ test.describe('Profile Actions', () => {
253256
// Verify that the profile was imported (check header input)
254257
const headerNameField = page.locator('[data-test-id="header-name-input"] input');
255258
await expect(headerNameField.first()).toHaveValue('X-Imported-Header', { timeout: 5000 });
259+
260+
// Verify that the imported request cookies were applied as well
261+
await page.getByRole('tab', { name: 'Request cookies' }).click();
262+
const cookieNameField = page.locator('[data-test-id="cookie-name-input"] input');
263+
const cookieValueField = page.locator('[data-test-id="cookie-value-input"] input');
264+
await expect(cookieNameField.first()).toHaveValue('imported_session', { timeout: 5000 });
265+
await expect(cookieValueField.first()).toHaveValue('cookie-value-123');
256266
});
257267

258268
/**

0 commit comments

Comments
 (0)