@open-mercato/core 0.4.9-develop-e55592929f → 0.4.9-develop-ce96cffe00
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/integration/api.js +66 -0
- package/dist/helpers/integration/api.js.map +7 -0
- package/dist/helpers/integration/apiKeysFixtures.js +16 -0
- package/dist/helpers/integration/apiKeysFixtures.js.map +7 -0
- package/dist/helpers/integration/attachmentsFixtures.js +61 -0
- package/dist/helpers/integration/attachmentsFixtures.js.map +7 -0
- package/dist/helpers/integration/auth.js +190 -0
- package/dist/helpers/integration/auth.js.map +7 -0
- package/dist/helpers/integration/authFixtures.js +39 -0
- package/dist/helpers/integration/authFixtures.js.map +7 -0
- package/dist/helpers/integration/authUi.js +31 -0
- package/dist/helpers/integration/authUi.js.map +7 -0
- package/dist/helpers/integration/businessRulesFixtures.js +40 -0
- package/dist/helpers/integration/businessRulesFixtures.js.map +7 -0
- package/dist/helpers/integration/catalogFixtures.js +49 -0
- package/dist/helpers/integration/catalogFixtures.js.map +7 -0
- package/dist/helpers/integration/crmFixtures.js +91 -0
- package/dist/helpers/integration/crmFixtures.js.map +7 -0
- package/dist/helpers/integration/currenciesFixtures.js +39 -0
- package/dist/helpers/integration/currenciesFixtures.js.map +7 -0
- package/dist/helpers/integration/dictionariesFixtures.js +16 -0
- package/dist/helpers/integration/dictionariesFixtures.js.map +7 -0
- package/dist/helpers/integration/featureTogglesFixtures.js +23 -0
- package/dist/helpers/integration/featureTogglesFixtures.js.map +7 -0
- package/dist/helpers/integration/generalFixtures.js +56 -0
- package/dist/helpers/integration/generalFixtures.js.map +7 -0
- package/dist/helpers/integration/inboxFixtures.js +67 -0
- package/dist/helpers/integration/inboxFixtures.js.map +7 -0
- package/dist/helpers/integration/notificationsFixtures.js +48 -0
- package/dist/helpers/integration/notificationsFixtures.js.map +7 -0
- package/dist/helpers/integration/salesFixtures.js +63 -0
- package/dist/helpers/integration/salesFixtures.js.map +7 -0
- package/dist/helpers/integration/salesUi.js +827 -0
- package/dist/helpers/integration/salesUi.js.map +7 -0
- package/dist/helpers/integration/sseEventCollector.js +27 -0
- package/dist/helpers/integration/sseEventCollector.js.map +7 -0
- package/dist/helpers/integration/staffFixtures.js +47 -0
- package/dist/helpers/integration/staffFixtures.js.map +7 -0
- package/dist/testing/integration/api.js +2 -0
- package/dist/testing/integration/api.js.map +7 -0
- package/dist/testing/integration/auth.js +2 -0
- package/dist/testing/integration/auth.js.map +7 -0
- package/dist/testing/integration/authFixtures.js +2 -0
- package/dist/testing/integration/authFixtures.js.map +7 -0
- package/dist/testing/integration/authUi.js +2 -0
- package/dist/testing/integration/authUi.js.map +7 -0
- package/dist/testing/integration/crmFixtures.js +2 -0
- package/dist/testing/integration/crmFixtures.js.map +7 -0
- package/dist/testing/integration/dictionariesFixtures.js +2 -0
- package/dist/testing/integration/dictionariesFixtures.js.map +7 -0
- package/dist/testing/integration/generalFixtures.js +2 -0
- package/dist/testing/integration/generalFixtures.js.map +7 -0
- package/dist/testing/integration/index.js +48 -0
- package/dist/testing/integration/index.js.map +7 -0
- package/package.json +11 -3
- package/src/helpers/integration/api.ts +87 -0
- package/src/helpers/integration/apiKeysFixtures.ts +17 -0
- package/src/helpers/integration/attachmentsFixtures.ts +114 -0
- package/src/helpers/integration/auth.ts +208 -0
- package/src/helpers/integration/authFixtures.ts +52 -0
- package/src/helpers/integration/authUi.ts +33 -0
- package/src/helpers/integration/businessRulesFixtures.ts +53 -0
- package/src/helpers/integration/catalogFixtures.ts +73 -0
- package/src/helpers/integration/crmFixtures.ts +132 -0
- package/src/helpers/integration/currenciesFixtures.ts +49 -0
- package/src/helpers/integration/dictionariesFixtures.ts +17 -0
- package/src/helpers/integration/featureTogglesFixtures.ts +28 -0
- package/src/helpers/integration/generalFixtures.ts +71 -0
- package/src/helpers/integration/inboxFixtures.ts +94 -0
- package/src/helpers/integration/notificationsFixtures.ts +67 -0
- package/src/helpers/integration/salesFixtures.ts +89 -0
- package/src/helpers/integration/salesUi.ts +936 -0
- package/src/helpers/integration/sseEventCollector.ts +30 -0
- package/src/helpers/integration/staffFixtures.ts +61 -0
- package/src/testing/integration/api.ts +1 -0
- package/src/testing/integration/auth.ts +1 -0
- package/src/testing/integration/authFixtures.ts +1 -0
- package/src/testing/integration/authUi.ts +1 -0
- package/src/testing/integration/crmFixtures.ts +1 -0
- package/src/testing/integration/dictionariesFixtures.ts +1 -0
- package/src/testing/integration/generalFixtures.ts +1 -0
- package/src/testing/integration/index.ts +22 -0
- package/tsconfig.json +3 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { expect, type APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { apiRequest } from './api';
|
|
3
|
+
import { getTokenContext } from './generalFixtures';
|
|
4
|
+
|
|
5
|
+
export async function createCurrencyFixture(
|
|
6
|
+
request: APIRequestContext,
|
|
7
|
+
token: string,
|
|
8
|
+
input: { code: string; name: string; symbol?: string },
|
|
9
|
+
): Promise<string> {
|
|
10
|
+
const { organizationId, tenantId } = getTokenContext(token);
|
|
11
|
+
const response = await apiRequest(request, 'POST', '/api/currencies/currencies', {
|
|
12
|
+
token,
|
|
13
|
+
data: { organizationId, tenantId, code: input.code, name: input.name, symbol: input.symbol ?? null },
|
|
14
|
+
});
|
|
15
|
+
expect(response.ok(), `Failed to create currency fixture: ${response.status()}`).toBeTruthy();
|
|
16
|
+
const body = (await response.json()) as { id?: string };
|
|
17
|
+
expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy();
|
|
18
|
+
return body.id as string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function createFetchConfigFixture(
|
|
22
|
+
request: APIRequestContext,
|
|
23
|
+
token: string,
|
|
24
|
+
input: { provider: string; isEnabled: boolean },
|
|
25
|
+
): Promise<string> {
|
|
26
|
+
const response = await apiRequest(request, 'POST', '/api/currencies/fetch-configs', {
|
|
27
|
+
token,
|
|
28
|
+
data: input,
|
|
29
|
+
});
|
|
30
|
+
expect(response.ok(), `Failed to create fetch config fixture: ${response.status()}`).toBeTruthy();
|
|
31
|
+
const body = (await response.json()) as { config?: { id?: string } };
|
|
32
|
+
const id = body.config?.id;
|
|
33
|
+
expect(typeof id === 'string' && id.length > 0).toBeTruthy();
|
|
34
|
+
return id as string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function deleteCurrenciesEntityIfExists(
|
|
38
|
+
request: APIRequestContext,
|
|
39
|
+
token: string | null,
|
|
40
|
+
path: string,
|
|
41
|
+
id: string | null,
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
if (!token || !id) return;
|
|
44
|
+
try {
|
|
45
|
+
await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { expect, type APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { apiRequest } from './api';
|
|
3
|
+
|
|
4
|
+
export async function createDictionaryFixture(
|
|
5
|
+
request: APIRequestContext,
|
|
6
|
+
token: string,
|
|
7
|
+
input: { key: string; name: string },
|
|
8
|
+
): Promise<string> {
|
|
9
|
+
const response = await apiRequest(request, 'POST', '/api/dictionaries', {
|
|
10
|
+
token,
|
|
11
|
+
data: { key: input.key, name: input.name },
|
|
12
|
+
});
|
|
13
|
+
expect(response.ok(), `Failed to create dictionary fixture: ${response.status()}`).toBeTruthy();
|
|
14
|
+
const body = (await response.json()) as { id?: string };
|
|
15
|
+
expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy();
|
|
16
|
+
return body.id as string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { expect, type APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { apiRequest } from './api';
|
|
3
|
+
import { expectId, readJsonSafe } from './generalFixtures';
|
|
4
|
+
|
|
5
|
+
export async function createFeatureToggleFixture(
|
|
6
|
+
request: APIRequestContext,
|
|
7
|
+
token: string,
|
|
8
|
+
data: Record<string, unknown>,
|
|
9
|
+
): Promise<string> {
|
|
10
|
+
const response = await apiRequest(request, 'POST', '/api/feature_toggles/global', { token, data });
|
|
11
|
+
const body = await readJsonSafe<{ id?: string }>(response);
|
|
12
|
+
expect(response.status(), 'POST /api/feature_toggles/global should return 201').toBe(201);
|
|
13
|
+
return expectId(body?.id, 'Feature toggle creation response should include id');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function deleteFeatureToggleIfExists(
|
|
17
|
+
request: APIRequestContext,
|
|
18
|
+
token: string | null,
|
|
19
|
+
toggleId: string | null,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
if (!token || !toggleId) return;
|
|
22
|
+
await apiRequest(
|
|
23
|
+
request,
|
|
24
|
+
'DELETE',
|
|
25
|
+
`/api/feature_toggles/global?id=${encodeURIComponent(toggleId)}`,
|
|
26
|
+
{ token },
|
|
27
|
+
).catch(() => undefined);
|
|
28
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { expect, type APIRequestContext, type APIResponse } from '@playwright/test';
|
|
2
|
+
import { apiRequest } from './api';
|
|
3
|
+
|
|
4
|
+
function readTokenPayload(token: string): { orgId?: string; tenantId?: string; sub?: string } {
|
|
5
|
+
const parts = token.split('.');
|
|
6
|
+
return JSON.parse(Buffer.from(parts[1], 'base64url').toString()) as {
|
|
7
|
+
orgId?: string;
|
|
8
|
+
tenantId?: string;
|
|
9
|
+
sub?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getTokenContext(token: string): { organizationId: string; tenantId: string } {
|
|
14
|
+
const payload = readTokenPayload(token);
|
|
15
|
+
return { organizationId: payload.orgId ?? '', tenantId: payload.tenantId ?? '' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getTokenScope(token: string): {
|
|
19
|
+
organizationId: string;
|
|
20
|
+
tenantId: string;
|
|
21
|
+
userId: string;
|
|
22
|
+
} {
|
|
23
|
+
const payload = readTokenPayload(token);
|
|
24
|
+
return {
|
|
25
|
+
organizationId: payload.orgId ?? '',
|
|
26
|
+
tenantId: payload.tenantId ?? '',
|
|
27
|
+
userId: payload.sub ?? '',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function readJsonSafe<T = unknown>(response: APIResponse): Promise<T | null> {
|
|
32
|
+
const raw = await response.text();
|
|
33
|
+
if (!raw) return null;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw) as T;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function expectId(value: unknown, message: string): string {
|
|
42
|
+
expect(typeof value === 'string' && value.length > 0, message).toBe(true);
|
|
43
|
+
return value as string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function deleteEntityByPathIfExists(
|
|
47
|
+
request: APIRequestContext,
|
|
48
|
+
token: string | null,
|
|
49
|
+
fullPath: string | null,
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
if (!token || !fullPath) return;
|
|
52
|
+
try {
|
|
53
|
+
await apiRequest(request, 'DELETE', fullPath, { token });
|
|
54
|
+
} catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function deleteGeneralEntityIfExists(
|
|
60
|
+
request: APIRequestContext,
|
|
61
|
+
token: string | null,
|
|
62
|
+
path: string,
|
|
63
|
+
id: string | null,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
if (!token || !id) return;
|
|
66
|
+
try {
|
|
67
|
+
await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { apiRequest } from './api';
|
|
3
|
+
import { readJsonSafe } from './crmFixtures';
|
|
4
|
+
|
|
5
|
+
export async function submitTextExtraction(
|
|
6
|
+
request: APIRequestContext,
|
|
7
|
+
token: string,
|
|
8
|
+
input?: { text?: string; title?: string; metadata?: Record<string, unknown> },
|
|
9
|
+
): Promise<{ ok: boolean; emailId?: string; error?: string; status: number }> {
|
|
10
|
+
const text = input?.text ?? 'Test email from John Doe <john@example.com> requesting 10 widgets at $5 each.'
|
|
11
|
+
const title = input?.title ?? `QA Fixture ${Date.now()}`
|
|
12
|
+
|
|
13
|
+
const response = await apiRequest(request, 'POST', '/api/inbox_ops/extract', {
|
|
14
|
+
token,
|
|
15
|
+
data: { text, title, metadata: input?.metadata },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const body = await readJsonSafe<{ ok?: boolean; emailId?: string; error?: string }>(response);
|
|
19
|
+
return {
|
|
20
|
+
ok: response.ok(),
|
|
21
|
+
emailId: body?.emailId ?? undefined,
|
|
22
|
+
error: body?.error ?? undefined,
|
|
23
|
+
status: response.status(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function waitForEmailProcessed(
|
|
28
|
+
request: APIRequestContext,
|
|
29
|
+
token: string,
|
|
30
|
+
emailId: string,
|
|
31
|
+
timeoutMs = 30000,
|
|
32
|
+
): Promise<{ status: string; proposalId?: string } | null> {
|
|
33
|
+
const pollInterval = 1000
|
|
34
|
+
const deadline = Date.now() + timeoutMs
|
|
35
|
+
|
|
36
|
+
while (Date.now() < deadline) {
|
|
37
|
+
const response = await apiRequest(request, 'GET', `/api/inbox_ops/emails/${emailId}`, { token });
|
|
38
|
+
if (!response.ok()) return null
|
|
39
|
+
|
|
40
|
+
const body = await readJsonSafe<{ email?: { status?: string; id?: string } }>(response);
|
|
41
|
+
const status = body?.email?.status
|
|
42
|
+
|
|
43
|
+
if (status === 'processed' || status === 'needs_review' || status === 'failed') {
|
|
44
|
+
const proposalsResponse = await apiRequest(request, 'GET', '/api/inbox_ops/proposals?pageSize=5', { token });
|
|
45
|
+
const proposalsBody = await readJsonSafe<{ items?: Array<{ id: string; inboxEmailId?: string }> }>(proposalsResponse);
|
|
46
|
+
const proposal = proposalsBody?.items?.find((p) => p.inboxEmailId === emailId);
|
|
47
|
+
return { status, proposalId: proposal?.id };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function deleteInboxEmail(
|
|
57
|
+
request: APIRequestContext,
|
|
58
|
+
token: string,
|
|
59
|
+
emailId: string,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
await apiRequest(request, 'DELETE', `/api/inbox_ops/emails/${emailId}`, { token }).catch(() => {})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function listInboxEmails(
|
|
65
|
+
request: APIRequestContext,
|
|
66
|
+
token: string,
|
|
67
|
+
params?: { status?: string; page?: number; pageSize?: number },
|
|
68
|
+
): Promise<{ items: Array<Record<string, unknown>>; total: number }> {
|
|
69
|
+
const searchParams = new URLSearchParams();
|
|
70
|
+
if (params?.status) searchParams.set('status', params.status);
|
|
71
|
+
if (params?.page) searchParams.set('page', String(params.page));
|
|
72
|
+
if (params?.pageSize) searchParams.set('pageSize', String(params.pageSize));
|
|
73
|
+
|
|
74
|
+
const path = `/api/inbox_ops/emails${searchParams.toString() ? `?${searchParams}` : ''}`;
|
|
75
|
+
const response = await apiRequest(request, 'GET', path, { token });
|
|
76
|
+
const body = await readJsonSafe<{ items?: Array<Record<string, unknown>>; total?: number }>(response);
|
|
77
|
+
return { items: body?.items ?? [], total: body?.total ?? 0 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function listInboxProposals(
|
|
81
|
+
request: APIRequestContext,
|
|
82
|
+
token: string,
|
|
83
|
+
params?: { status?: string; page?: number; pageSize?: number },
|
|
84
|
+
): Promise<{ items: Array<Record<string, unknown>>; total: number }> {
|
|
85
|
+
const searchParams = new URLSearchParams();
|
|
86
|
+
if (params?.status) searchParams.set('status', params.status);
|
|
87
|
+
if (params?.page) searchParams.set('page', String(params.page));
|
|
88
|
+
if (params?.pageSize) searchParams.set('pageSize', String(params.pageSize));
|
|
89
|
+
|
|
90
|
+
const path = `/api/inbox_ops/proposals${searchParams.toString() ? `?${searchParams}` : ''}`;
|
|
91
|
+
const response = await apiRequest(request, 'GET', path, { token });
|
|
92
|
+
const body = await readJsonSafe<{ items?: Array<Record<string, unknown>>; total?: number }>(response);
|
|
93
|
+
return { items: body?.items ?? [], total: body?.total ?? 0 };
|
|
94
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { expect, type APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { apiRequest } from './api';
|
|
3
|
+
import { expectId, readJsonSafe } from './generalFixtures';
|
|
4
|
+
|
|
5
|
+
export async function createNotificationFixture(
|
|
6
|
+
request: APIRequestContext,
|
|
7
|
+
token: string,
|
|
8
|
+
data: Record<string, unknown>,
|
|
9
|
+
): Promise<string> {
|
|
10
|
+
const response = await apiRequest(request, 'POST', '/api/notifications', { token, data });
|
|
11
|
+
const body = await readJsonSafe<{ id?: string }>(response);
|
|
12
|
+
expect(response.status(), 'POST /api/notifications should return 201').toBe(201);
|
|
13
|
+
return expectId(body?.id, 'Notification creation response should include id');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function listNotifications(
|
|
17
|
+
request: APIRequestContext,
|
|
18
|
+
token: string,
|
|
19
|
+
query?: Record<string, string | number | undefined>,
|
|
20
|
+
): Promise<{
|
|
21
|
+
items: Array<Record<string, unknown>>;
|
|
22
|
+
total: number;
|
|
23
|
+
}> {
|
|
24
|
+
const params = new URLSearchParams();
|
|
25
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
26
|
+
if (value === undefined) continue;
|
|
27
|
+
params.set(key, String(value));
|
|
28
|
+
}
|
|
29
|
+
const path = `/api/notifications${params.toString() ? `?${params.toString()}` : ''}`;
|
|
30
|
+
const response = await apiRequest(request, 'GET', path, { token });
|
|
31
|
+
const body = await readJsonSafe<{ items?: Array<Record<string, unknown>>; total?: number }>(response);
|
|
32
|
+
expect(response.ok(), `GET ${path} should succeed`).toBeTruthy();
|
|
33
|
+
return {
|
|
34
|
+
items: body?.items ?? [],
|
|
35
|
+
total: body?.total ?? 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function dismissNotificationIfExists(
|
|
40
|
+
request: APIRequestContext,
|
|
41
|
+
token: string | null,
|
|
42
|
+
notificationId: string | null,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
if (!token || !notificationId) return;
|
|
45
|
+
await apiRequest(
|
|
46
|
+
request,
|
|
47
|
+
'PUT',
|
|
48
|
+
`/api/notifications/${encodeURIComponent(notificationId)}/dismiss`,
|
|
49
|
+
{ token },
|
|
50
|
+
).catch(() => undefined);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function dismissNotificationsByType(
|
|
54
|
+
request: APIRequestContext,
|
|
55
|
+
token: string | null,
|
|
56
|
+
type: string | null,
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
if (!token || !type) return;
|
|
59
|
+
const notifications = await listNotifications(request, token, { type, pageSize: 100 }).catch(() => null);
|
|
60
|
+
if (!notifications) return;
|
|
61
|
+
await Promise.all(
|
|
62
|
+
notifications.items
|
|
63
|
+
.map((item) => (typeof item.id === 'string' ? item.id : null))
|
|
64
|
+
.filter((id): id is string => Boolean(id))
|
|
65
|
+
.map((id) => dismissNotificationIfExists(request, token, id)),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { expect, type APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { apiRequest } from './api';
|
|
3
|
+
|
|
4
|
+
type JsonMap = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
function readId(payload: unknown, keys: string[]): string | null {
|
|
7
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
8
|
+
const map = payload as JsonMap;
|
|
9
|
+
for (const key of keys) {
|
|
10
|
+
const value = map[key];
|
|
11
|
+
if (typeof value === 'string' && value.length > 0) return value;
|
|
12
|
+
}
|
|
13
|
+
for (const value of Object.values(map)) {
|
|
14
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
15
|
+
const nested = readId(value, keys);
|
|
16
|
+
if (nested) return nested;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function createEntity(
|
|
23
|
+
request: APIRequestContext,
|
|
24
|
+
token: string,
|
|
25
|
+
path: string,
|
|
26
|
+
data: Record<string, unknown>,
|
|
27
|
+
idKeys: string[],
|
|
28
|
+
): Promise<string> {
|
|
29
|
+
const response = await apiRequest(request, 'POST', path, { token, data });
|
|
30
|
+
const body = (await response.json()) as unknown;
|
|
31
|
+
expect(response.ok(), `Failed POST ${path}: ${response.status()}`).toBeTruthy();
|
|
32
|
+
const id = readId(body, idKeys);
|
|
33
|
+
expect(id, `No id in POST ${path} response`).toBeTruthy();
|
|
34
|
+
return id as string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function createSalesQuoteFixture(
|
|
38
|
+
request: APIRequestContext,
|
|
39
|
+
token: string,
|
|
40
|
+
currencyCode = 'USD',
|
|
41
|
+
): Promise<string> {
|
|
42
|
+
return createEntity(request, token, '/api/sales/quotes', { currencyCode }, ['id', 'quoteId']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function createSalesOrderFixture(
|
|
46
|
+
request: APIRequestContext,
|
|
47
|
+
token: string,
|
|
48
|
+
currencyCode = 'USD',
|
|
49
|
+
): Promise<string> {
|
|
50
|
+
return createEntity(request, token, '/api/sales/orders', { currencyCode }, ['id', 'orderId']);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function createOrderLineFixture(
|
|
54
|
+
request: APIRequestContext,
|
|
55
|
+
token: string,
|
|
56
|
+
orderId: string,
|
|
57
|
+
data?: Record<string, unknown>,
|
|
58
|
+
): Promise<string> {
|
|
59
|
+
return createEntity(
|
|
60
|
+
request,
|
|
61
|
+
token,
|
|
62
|
+
'/api/sales/order-lines',
|
|
63
|
+
{
|
|
64
|
+
orderId,
|
|
65
|
+
currencyCode: 'USD',
|
|
66
|
+
quantity: 1,
|
|
67
|
+
name: `QA line ${Date.now()}`,
|
|
68
|
+
unitPriceNet: 10,
|
|
69
|
+
unitPriceGross: 12,
|
|
70
|
+
...(data ?? {}),
|
|
71
|
+
},
|
|
72
|
+
['id', 'lineId'],
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function deleteSalesEntityIfExists(
|
|
77
|
+
request: APIRequestContext,
|
|
78
|
+
token: string | null,
|
|
79
|
+
path: string,
|
|
80
|
+
id: string | null,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
if (!token || !id) return;
|
|
83
|
+
try {
|
|
84
|
+
await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|