@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,66 @@
|
|
|
1
|
+
import { DEFAULT_CREDENTIALS } from "./auth.js";
|
|
2
|
+
const BASE_URL = process.env.BASE_URL?.trim() || null;
|
|
3
|
+
function resolveUrl(path) {
|
|
4
|
+
return BASE_URL ? `${BASE_URL}${path}` : path;
|
|
5
|
+
}
|
|
6
|
+
async function getAuthToken(request, roleOrEmail = "admin", password) {
|
|
7
|
+
const role = roleOrEmail in DEFAULT_CREDENTIALS ? roleOrEmail : null;
|
|
8
|
+
const credentialAttempts = [];
|
|
9
|
+
if (role) {
|
|
10
|
+
const configured = DEFAULT_CREDENTIALS[role];
|
|
11
|
+
credentialAttempts.push({ email: configured.email, password: password ?? configured.password });
|
|
12
|
+
if (!password) {
|
|
13
|
+
credentialAttempts.push({ email: `${role}@acme.com`, password: "secret" });
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
credentialAttempts.push({ email: roleOrEmail, password: password ?? "secret" });
|
|
17
|
+
}
|
|
18
|
+
let lastStatus = 0;
|
|
19
|
+
for (const attempt of credentialAttempts) {
|
|
20
|
+
const form = new URLSearchParams();
|
|
21
|
+
form.set("email", attempt.email);
|
|
22
|
+
form.set("password", attempt.password);
|
|
23
|
+
const response = await request.post(resolveUrl("/api/auth/login"), {
|
|
24
|
+
headers: {
|
|
25
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
26
|
+
},
|
|
27
|
+
data: form.toString()
|
|
28
|
+
});
|
|
29
|
+
const raw = await response.text();
|
|
30
|
+
let body = null;
|
|
31
|
+
try {
|
|
32
|
+
body = raw ? JSON.parse(raw) : null;
|
|
33
|
+
} catch {
|
|
34
|
+
body = null;
|
|
35
|
+
}
|
|
36
|
+
lastStatus = response.status();
|
|
37
|
+
if (response.ok() && body && typeof body.token === "string" && body.token) {
|
|
38
|
+
return body.token;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`Failed to obtain auth token (status ${lastStatus})`);
|
|
42
|
+
}
|
|
43
|
+
async function apiRequest(request, method, path, options) {
|
|
44
|
+
const headers = {
|
|
45
|
+
Authorization: `Bearer ${options.token}`,
|
|
46
|
+
"Content-Type": "application/json"
|
|
47
|
+
};
|
|
48
|
+
return request.fetch(resolveUrl(path), { method, headers, data: options.data });
|
|
49
|
+
}
|
|
50
|
+
async function postForm(request, path, data, options) {
|
|
51
|
+
const form = new URLSearchParams();
|
|
52
|
+
for (const [key, value] of Object.entries(data)) form.set(key, value);
|
|
53
|
+
return request.post(resolveUrl(path), {
|
|
54
|
+
headers: {
|
|
55
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
56
|
+
...options?.headers ?? {}
|
|
57
|
+
},
|
|
58
|
+
data: form.toString()
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
apiRequest,
|
|
63
|
+
getAuthToken,
|
|
64
|
+
postForm
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/api.ts"],
|
|
4
|
+
"sourcesContent": ["import { type APIRequestContext } from '@playwright/test';\nimport { DEFAULT_CREDENTIALS, type Role } from './auth';\n\nconst BASE_URL = process.env.BASE_URL?.trim() || null;\n\nfunction resolveUrl(path: string): string {\n return BASE_URL ? `${BASE_URL}${path}` : path;\n}\n\nexport async function getAuthToken(\n request: APIRequestContext,\n roleOrEmail: Role | string = 'admin',\n password?: string,\n): Promise<string> {\n const role = roleOrEmail in DEFAULT_CREDENTIALS ? (roleOrEmail as Role) : null;\n const credentialAttempts: Array<{ email: string; password: string }> = [];\n\n if (role) {\n const configured = DEFAULT_CREDENTIALS[role];\n credentialAttempts.push({ email: configured.email, password: password ?? configured.password });\n if (!password) {\n credentialAttempts.push({ email: `${role}@acme.com`, password: 'secret' });\n }\n } else {\n credentialAttempts.push({ email: roleOrEmail, password: password ?? 'secret' });\n }\n\n let lastStatus = 0;\n\n for (const attempt of credentialAttempts) {\n const form = new URLSearchParams();\n form.set('email', attempt.email);\n form.set('password', attempt.password);\n\n const response = await request.post(resolveUrl('/api/auth/login'), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: form.toString(),\n });\n\n const raw = await response.text();\n let body: Record<string, unknown> | null = null;\n try {\n body = raw ? (JSON.parse(raw) as Record<string, unknown>) : null;\n } catch {\n body = null;\n }\n\n lastStatus = response.status();\n if (response.ok() && body && typeof body.token === 'string' && body.token) {\n return body.token;\n }\n }\n\n throw new Error(`Failed to obtain auth token (status ${lastStatus})`);\n}\n\nexport async function apiRequest(\n request: APIRequestContext,\n method: string,\n path: string,\n options: { token: string; data?: unknown },\n) {\n const headers = {\n Authorization: `Bearer ${options.token}`,\n 'Content-Type': 'application/json',\n };\n return request.fetch(resolveUrl(path), { method, headers, data: options.data });\n}\n\nexport async function postForm(\n request: APIRequestContext,\n path: string,\n data: Record<string, string>,\n options?: { headers?: Record<string, string> },\n) {\n const form = new URLSearchParams();\n for (const [key, value] of Object.entries(data)) form.set(key, value);\n return request.post(resolveUrl(path), {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n ...(options?.headers ?? {}),\n },\n data: form.toString(),\n });\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,2BAAsC;AAE/C,MAAM,WAAW,QAAQ,IAAI,UAAU,KAAK,KAAK;AAEjD,SAAS,WAAW,MAAsB;AACxC,SAAO,WAAW,GAAG,QAAQ,GAAG,IAAI,KAAK;AAC3C;AAEA,eAAsB,aACpB,SACA,cAA6B,SAC7B,UACiB;AACjB,QAAM,OAAO,eAAe,sBAAuB,cAAuB;AAC1E,QAAM,qBAAiE,CAAC;AAExE,MAAI,MAAM;AACR,UAAM,aAAa,oBAAoB,IAAI;AAC3C,uBAAmB,KAAK,EAAE,OAAO,WAAW,OAAO,UAAU,YAAY,WAAW,SAAS,CAAC;AAC9F,QAAI,CAAC,UAAU;AACb,yBAAmB,KAAK,EAAE,OAAO,GAAG,IAAI,aAAa,UAAU,SAAS,CAAC;AAAA,IAC3E;AAAA,EACF,OAAO;AACL,uBAAmB,KAAK,EAAE,OAAO,aAAa,UAAU,YAAY,SAAS,CAAC;AAAA,EAChF;AAEA,MAAI,aAAa;AAEjB,aAAW,WAAW,oBAAoB;AACxC,UAAM,OAAO,IAAI,gBAAgB;AACjC,SAAK,IAAI,SAAS,QAAQ,KAAK;AAC/B,SAAK,IAAI,YAAY,QAAQ,QAAQ;AAErC,UAAM,WAAW,MAAM,QAAQ,KAAK,WAAW,iBAAiB,GAAG;AAAA,MACjE,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,SAAS;AAAA,IACtB,CAAC;AAED,UAAM,MAAM,MAAM,SAAS,KAAK;AAChC,QAAI,OAAuC;AAC3C,QAAI;AACF,aAAO,MAAO,KAAK,MAAM,GAAG,IAAgC;AAAA,IAC9D,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,iBAAa,SAAS,OAAO;AAC7B,QAAI,SAAS,GAAG,KAAK,QAAQ,OAAO,KAAK,UAAU,YAAY,KAAK,OAAO;AACzE,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,uCAAuC,UAAU,GAAG;AACtE;AAEA,eAAsB,WACpB,SACA,QACA,MACA,SACA;AACA,QAAM,UAAU;AAAA,IACd,eAAe,UAAU,QAAQ,KAAK;AAAA,IACtC,gBAAgB;AAAA,EAClB;AACA,SAAO,QAAQ,MAAM,WAAW,IAAI,GAAG,EAAE,QAAQ,SAAS,MAAM,QAAQ,KAAK,CAAC;AAChF;AAEA,eAAsB,SACpB,SACA,MACA,MACA,SACA;AACA,QAAM,OAAO,IAAI,gBAAgB;AACjC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,EAAG,MAAK,IAAI,KAAK,KAAK;AACpE,SAAO,QAAQ,KAAK,WAAW,IAAI,GAAG;AAAA,IACpC,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,GAAI,SAAS,WAAW,CAAC;AAAA,IAC3B;AAAA,IACA,MAAM,KAAK,SAAS;AAAA,EACtB,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { apiRequest } from "./api.js";
|
|
3
|
+
async function createApiKeyFixture(request, token, name) {
|
|
4
|
+
const response = await apiRequest(request, "POST", "/api/api_keys/keys", {
|
|
5
|
+
token,
|
|
6
|
+
data: { name }
|
|
7
|
+
});
|
|
8
|
+
expect(response.ok(), `Failed to create API key fixture: ${response.status()}`).toBeTruthy();
|
|
9
|
+
const body = await response.json();
|
|
10
|
+
expect(typeof body.id === "string" && body.id.length > 0).toBeTruthy();
|
|
11
|
+
return { id: body.id, secret: body.secret ?? "" };
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
createApiKeyFixture
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=apiKeysFixtures.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/apiKeysFixtures.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\n\nexport async function createApiKeyFixture(\n request: APIRequestContext,\n token: string,\n name: string,\n): Promise<{ id: string; secret: string }> {\n const response = await apiRequest(request, 'POST', '/api/api_keys/keys', {\n token,\n data: { name },\n });\n expect(response.ok(), `Failed to create API key fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { id?: string; secret?: string };\n expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy();\n return { id: body.id as string, secret: body.secret ?? '' };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAE3B,eAAsB,oBACpB,SACA,OACA,MACyC;AACzC,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,sBAAsB;AAAA,IACvE;AAAA,IACA,MAAM,EAAE,KAAK;AAAA,EACf,CAAC;AACD,SAAO,SAAS,GAAG,GAAG,qCAAqC,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC3F,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,CAAC,EAAE,WAAW;AACrE,SAAO,EAAE,IAAI,KAAK,IAAc,QAAQ,KAAK,UAAU,GAAG;AAC5D;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { apiRequest } from "./api.js";
|
|
3
|
+
import { expectId, readJsonSafe } from "./generalFixtures.js";
|
|
4
|
+
const BASE_URL = process.env.BASE_URL?.trim() || "http://localhost:3000";
|
|
5
|
+
function resolveApiUrl(path) {
|
|
6
|
+
return `${BASE_URL}${path}`;
|
|
7
|
+
}
|
|
8
|
+
async function uploadAttachmentFixture(request, token, input) {
|
|
9
|
+
const multipart = {
|
|
10
|
+
entityId: input.entityId,
|
|
11
|
+
recordId: input.recordId,
|
|
12
|
+
file: {
|
|
13
|
+
name: input.fileName,
|
|
14
|
+
mimeType: input.mimeType,
|
|
15
|
+
buffer: input.buffer
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
if (input.partitionCode) multipart.partitionCode = input.partitionCode;
|
|
19
|
+
if (input.tags) multipart.tags = JSON.stringify(input.tags);
|
|
20
|
+
if (input.assignments) multipart.assignments = JSON.stringify(input.assignments);
|
|
21
|
+
const response = await request.fetch(resolveApiUrl("/api/attachments"), {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
Authorization: `Bearer ${token}`
|
|
25
|
+
},
|
|
26
|
+
multipart
|
|
27
|
+
});
|
|
28
|
+
const body = await readJsonSafe(response);
|
|
29
|
+
expect(response.status(), "POST /api/attachments should return 200").toBe(200);
|
|
30
|
+
return {
|
|
31
|
+
id: expectId(body?.item?.id, "Attachment upload response should include item.id"),
|
|
32
|
+
partitionCode: String(body?.item?.partitionCode ?? ""),
|
|
33
|
+
fileName: String(body?.item?.fileName ?? ""),
|
|
34
|
+
tags: body?.item?.tags ?? [],
|
|
35
|
+
assignments: body?.item?.assignments ?? []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function deleteAttachmentIfExists(request, token, attachmentId) {
|
|
39
|
+
if (!token || !attachmentId) return;
|
|
40
|
+
await apiRequest(
|
|
41
|
+
request,
|
|
42
|
+
"DELETE",
|
|
43
|
+
`/api/attachments?id=${encodeURIComponent(attachmentId)}`,
|
|
44
|
+
{ token }
|
|
45
|
+
).catch(() => void 0);
|
|
46
|
+
}
|
|
47
|
+
async function deleteAttachmentPartitionIfExists(request, token, partitionId) {
|
|
48
|
+
if (!token || !partitionId) return;
|
|
49
|
+
await apiRequest(
|
|
50
|
+
request,
|
|
51
|
+
"DELETE",
|
|
52
|
+
`/api/attachments/partitions?id=${encodeURIComponent(partitionId)}`,
|
|
53
|
+
{ token }
|
|
54
|
+
).catch(() => void 0);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
deleteAttachmentIfExists,
|
|
58
|
+
deleteAttachmentPartitionIfExists,
|
|
59
|
+
uploadAttachmentFixture
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=attachmentsFixtures.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/attachmentsFixtures.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\nimport { expectId, readJsonSafe } from './generalFixtures';\n\nconst BASE_URL = process.env.BASE_URL?.trim() || 'http://localhost:3000';\n\ntype AttachmentAssignment = {\n type: string;\n id: string;\n href?: string | null;\n label?: string | null;\n};\n\ntype MultipartFieldValue =\n | string\n | number\n | boolean\n | {\n name: string;\n mimeType: string;\n buffer: Buffer;\n };\n\nfunction resolveApiUrl(path: string): string {\n return `${BASE_URL}${path}`;\n}\n\nexport async function uploadAttachmentFixture(\n request: APIRequestContext,\n token: string,\n input: {\n entityId: string;\n recordId: string;\n fileName: string;\n mimeType: string;\n buffer: Buffer;\n partitionCode?: string;\n tags?: string[];\n assignments?: AttachmentAssignment[];\n },\n): Promise<{\n id: string;\n partitionCode: string;\n fileName: string;\n tags: string[];\n assignments: AttachmentAssignment[];\n}> {\n const multipart: Record<string, MultipartFieldValue> = {\n entityId: input.entityId,\n recordId: input.recordId,\n file: {\n name: input.fileName,\n mimeType: input.mimeType,\n buffer: input.buffer,\n },\n };\n if (input.partitionCode) multipart.partitionCode = input.partitionCode;\n if (input.tags) multipart.tags = JSON.stringify(input.tags);\n if (input.assignments) multipart.assignments = JSON.stringify(input.assignments);\n\n const response = await request.fetch(resolveApiUrl('/api/attachments'), {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n },\n multipart,\n });\n const body = await readJsonSafe<{\n ok?: boolean;\n item?: {\n id?: string;\n partitionCode?: string;\n fileName?: string;\n tags?: string[];\n assignments?: AttachmentAssignment[];\n };\n }>(response);\n expect(response.status(), 'POST /api/attachments should return 200').toBe(200);\n return {\n id: expectId(body?.item?.id, 'Attachment upload response should include item.id'),\n partitionCode: String(body?.item?.partitionCode ?? ''),\n fileName: String(body?.item?.fileName ?? ''),\n tags: body?.item?.tags ?? [],\n assignments: body?.item?.assignments ?? [],\n };\n}\n\nexport async function deleteAttachmentIfExists(\n request: APIRequestContext,\n token: string | null,\n attachmentId: string | null,\n): Promise<void> {\n if (!token || !attachmentId) return;\n await apiRequest(\n request,\n 'DELETE',\n `/api/attachments?id=${encodeURIComponent(attachmentId)}`,\n { token },\n ).catch(() => undefined);\n}\n\nexport async function deleteAttachmentPartitionIfExists(\n request: APIRequestContext,\n token: string | null,\n partitionId: string | null,\n): Promise<void> {\n if (!token || !partitionId) return;\n await apiRequest(\n request,\n 'DELETE',\n `/api/attachments/partitions?id=${encodeURIComponent(partitionId)}`,\n { token },\n ).catch(() => undefined);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,UAAU,oBAAoB;AAEvC,MAAM,WAAW,QAAQ,IAAI,UAAU,KAAK,KAAK;AAmBjD,SAAS,cAAc,MAAsB;AAC3C,SAAO,GAAG,QAAQ,GAAG,IAAI;AAC3B;AAEA,eAAsB,wBACpB,SACA,OACA,OAgBC;AACD,QAAM,YAAiD;AAAA,IACrD,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,IAChB,MAAM;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM;AAAA,MAChB,QAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AACA,MAAI,MAAM,cAAe,WAAU,gBAAgB,MAAM;AACzD,MAAI,MAAM,KAAM,WAAU,OAAO,KAAK,UAAU,MAAM,IAAI;AAC1D,MAAI,MAAM,YAAa,WAAU,cAAc,KAAK,UAAU,MAAM,WAAW;AAE/E,QAAM,WAAW,MAAM,QAAQ,MAAM,cAAc,kBAAkB,GAAG;AAAA,IACtE,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,KAAK;AAAA,IAChC;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,OAAO,MAAM,aAShB,QAAQ;AACX,SAAO,SAAS,OAAO,GAAG,yCAAyC,EAAE,KAAK,GAAG;AAC7E,SAAO;AAAA,IACL,IAAI,SAAS,MAAM,MAAM,IAAI,mDAAmD;AAAA,IAChF,eAAe,OAAO,MAAM,MAAM,iBAAiB,EAAE;AAAA,IACrD,UAAU,OAAO,MAAM,MAAM,YAAY,EAAE;AAAA,IAC3C,MAAM,MAAM,MAAM,QAAQ,CAAC;AAAA,IAC3B,aAAa,MAAM,MAAM,eAAe,CAAC;AAAA,EAC3C;AACF;AAEA,eAAsB,yBACpB,SACA,OACA,cACe;AACf,MAAI,CAAC,SAAS,CAAC,aAAc;AAC7B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,uBAAuB,mBAAmB,YAAY,CAAC;AAAA,IACvD,EAAE,MAAM;AAAA,EACV,EAAE,MAAM,MAAM,MAAS;AACzB;AAEA,eAAsB,kCACpB,SACA,OACA,aACe;AACf,MAAI,CAAC,SAAS,CAAC,YAAa;AAC5B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,kCAAkC,mBAAmB,WAAW,CAAC;AAAA,IACjE,EAAE,MAAM;AAAA,EACV,EAAE,MAAM,MAAM,MAAS;AACzB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
function loadEnvFileContent() {
|
|
4
|
+
const candidatePaths = [
|
|
5
|
+
resolve(process.cwd(), "apps/mercato/.env"),
|
|
6
|
+
resolve(process.cwd(), ".env")
|
|
7
|
+
];
|
|
8
|
+
for (const envPath of candidatePaths) {
|
|
9
|
+
try {
|
|
10
|
+
const content = readFileSync(envPath, "utf-8");
|
|
11
|
+
if (content.trim().length > 0) {
|
|
12
|
+
return content;
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function loadEnvValue(key) {
|
|
21
|
+
if (process.env[key]) return process.env[key];
|
|
22
|
+
const content = loadEnvFileContent();
|
|
23
|
+
if (!content) return void 0;
|
|
24
|
+
const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
|
|
25
|
+
return match?.[1]?.trim();
|
|
26
|
+
}
|
|
27
|
+
const DEFAULT_CREDENTIALS = {
|
|
28
|
+
superadmin: {
|
|
29
|
+
email: loadEnvValue("OM_INIT_SUPERADMIN_EMAIL") || "superadmin@acme.com",
|
|
30
|
+
password: loadEnvValue("OM_INIT_SUPERADMIN_PASSWORD") || "secret"
|
|
31
|
+
},
|
|
32
|
+
admin: { email: "admin@acme.com", password: "secret" },
|
|
33
|
+
employee: { email: "employee@acme.com", password: "secret" }
|
|
34
|
+
};
|
|
35
|
+
function decodeJwtClaims(token) {
|
|
36
|
+
const parts = token.split(".");
|
|
37
|
+
if (parts.length < 2) return null;
|
|
38
|
+
try {
|
|
39
|
+
const normalized = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
40
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
41
|
+
const payload = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
42
|
+
return payload;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function acknowledgeGlobalNotices(page) {
|
|
48
|
+
const baseUrl = process.env.BASE_URL || "http://localhost:3000";
|
|
49
|
+
await page.context().addCookies([
|
|
50
|
+
{
|
|
51
|
+
name: "om_demo_notice_ack",
|
|
52
|
+
value: "ack",
|
|
53
|
+
url: baseUrl,
|
|
54
|
+
sameSite: "Lax"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "om_cookie_notice_ack",
|
|
58
|
+
value: "ack",
|
|
59
|
+
url: baseUrl,
|
|
60
|
+
sameSite: "Lax"
|
|
61
|
+
}
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
async function dismissGlobalNoticesIfPresent(page) {
|
|
65
|
+
const cookieAcceptButton = page.getByRole("button", { name: /accept cookies/i }).first();
|
|
66
|
+
if (await cookieAcceptButton.isVisible().catch(() => false)) {
|
|
67
|
+
await cookieAcceptButton.click();
|
|
68
|
+
}
|
|
69
|
+
const demoNotice = page.getByText(/this instance is provided for demo purposes only/i).first();
|
|
70
|
+
if (await demoNotice.isVisible().catch(() => false)) {
|
|
71
|
+
const noticeContainer = demoNotice.locator('xpath=ancestor::div[contains(@class,"pointer-events-auto")]').first();
|
|
72
|
+
const dismissButton = noticeContainer.locator("button").first();
|
|
73
|
+
if (await dismissButton.isVisible().catch(() => false)) {
|
|
74
|
+
await dismissButton.click();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function recoverClientSideErrorPageIfPresent(page) {
|
|
79
|
+
const clientErrorHeading = page.getByRole("heading", { name: /Application error: a client-side exception has occurred/i }).first();
|
|
80
|
+
if (!await clientErrorHeading.isVisible().catch(() => false)) return;
|
|
81
|
+
await page.reload({ waitUntil: "domcontentloaded" });
|
|
82
|
+
await dismissGlobalNoticesIfPresent(page);
|
|
83
|
+
}
|
|
84
|
+
async function recoverGenericErrorPageIfPresent(page) {
|
|
85
|
+
const errorHeading = page.getByRole("heading", { name: /^Something went wrong$/i }).first();
|
|
86
|
+
if (!await errorHeading.isVisible().catch(() => false)) return;
|
|
87
|
+
const retryButton = page.getByRole("button", { name: /Try again/i }).first();
|
|
88
|
+
if (await retryButton.isVisible().catch(() => false)) {
|
|
89
|
+
await retryButton.click().catch(() => {
|
|
90
|
+
});
|
|
91
|
+
await page.waitForLoadState("domcontentloaded").catch(() => {
|
|
92
|
+
});
|
|
93
|
+
await page.waitForTimeout(500).catch(() => {
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
await page.reload({ waitUntil: "domcontentloaded" });
|
|
97
|
+
}
|
|
98
|
+
await dismissGlobalNoticesIfPresent(page);
|
|
99
|
+
}
|
|
100
|
+
async function login(page, role = "admin") {
|
|
101
|
+
const creds = DEFAULT_CREDENTIALS[role];
|
|
102
|
+
const loginReadySelector = 'form[data-auth-ready="1"]';
|
|
103
|
+
const hasBackendUrl = () => /\/backend(?:\/.*)?$/.test(page.url());
|
|
104
|
+
const waitForBackend = async (timeout) => {
|
|
105
|
+
try {
|
|
106
|
+
await page.waitForURL(/\/backend(?:\/.*)?$/, { timeout });
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
return hasBackendUrl();
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
await acknowledgeGlobalNotices(page);
|
|
113
|
+
const apiLoginForm = new URLSearchParams();
|
|
114
|
+
apiLoginForm.set("email", creds.email);
|
|
115
|
+
apiLoginForm.set("password", creds.password);
|
|
116
|
+
const apiLoginResponse = await page.request.post("/api/auth/login", {
|
|
117
|
+
headers: {
|
|
118
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
119
|
+
},
|
|
120
|
+
data: apiLoginForm.toString()
|
|
121
|
+
}).catch(() => null);
|
|
122
|
+
if (apiLoginResponse?.ok()) {
|
|
123
|
+
const apiLoginBody = await apiLoginResponse.json().catch(() => null);
|
|
124
|
+
const claims = typeof apiLoginBody?.token === "string" ? decodeJwtClaims(apiLoginBody.token) : null;
|
|
125
|
+
const baseUrl = process.env.BASE_URL || "http://localhost:3000";
|
|
126
|
+
const cookies = [];
|
|
127
|
+
if (claims?.tenantId) {
|
|
128
|
+
cookies.push({
|
|
129
|
+
name: "om_selected_tenant",
|
|
130
|
+
value: claims.tenantId,
|
|
131
|
+
url: baseUrl,
|
|
132
|
+
sameSite: "Lax"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (claims?.orgId) {
|
|
136
|
+
cookies.push({
|
|
137
|
+
name: "om_selected_org",
|
|
138
|
+
value: claims.orgId,
|
|
139
|
+
url: baseUrl,
|
|
140
|
+
sameSite: "Lax"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (cookies.length > 0) {
|
|
144
|
+
await page.context().addCookies(cookies);
|
|
145
|
+
}
|
|
146
|
+
await page.goto("/backend", { waitUntil: "domcontentloaded" });
|
|
147
|
+
if (await waitForBackend(8e3)) return;
|
|
148
|
+
}
|
|
149
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
150
|
+
await page.goto("/login", { waitUntil: "domcontentloaded" });
|
|
151
|
+
await dismissGlobalNoticesIfPresent(page);
|
|
152
|
+
await recoverClientSideErrorPageIfPresent(page);
|
|
153
|
+
await recoverGenericErrorPageIfPresent(page);
|
|
154
|
+
await page.waitForSelector(loginReadySelector, { state: "visible", timeout: 3e3 }).catch(() => null);
|
|
155
|
+
if (await page.getByLabel("Email").isVisible().catch(() => false)) break;
|
|
156
|
+
if (attempt === 3) {
|
|
157
|
+
throw new Error(`Login form is unavailable for role: ${role}; current URL: ${page.url()}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
await page.getByLabel("Email").fill(creds.email);
|
|
161
|
+
const passwordInput = page.getByLabel("Password").first();
|
|
162
|
+
if (await passwordInput.isVisible().catch(() => false)) {
|
|
163
|
+
await passwordInput.fill(creds.password);
|
|
164
|
+
await passwordInput.press("Enter");
|
|
165
|
+
} else {
|
|
166
|
+
const submitButton = page.getByRole("button", { name: /login|sign in|continue with sso/i }).first();
|
|
167
|
+
await submitButton.click();
|
|
168
|
+
}
|
|
169
|
+
if (await waitForBackend(7e3)) return;
|
|
170
|
+
const loginForm = page.locator("form").first();
|
|
171
|
+
if (await loginForm.isVisible().catch(() => false)) {
|
|
172
|
+
await loginForm.evaluate((element) => {
|
|
173
|
+
const form = element;
|
|
174
|
+
form.requestSubmit();
|
|
175
|
+
}).catch(() => {
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (await waitForBackend(5e3)) return;
|
|
179
|
+
const loginButton = page.getByRole("button", { name: /login|sign in|continue with sso/i }).first();
|
|
180
|
+
if (await loginButton.isVisible().catch(() => false)) {
|
|
181
|
+
await loginButton.click({ force: true });
|
|
182
|
+
}
|
|
183
|
+
if (await waitForBackend(8e3)) return;
|
|
184
|
+
throw new Error(`Login did not reach backend for role: ${role}; current URL: ${page.url()}`);
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
DEFAULT_CREDENTIALS,
|
|
188
|
+
login
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/auth.ts"],
|
|
4
|
+
"sourcesContent": ["import { type Page } from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\n\nfunction loadEnvFileContent(): string | null {\n const candidatePaths = [\n resolve(process.cwd(), 'apps/mercato/.env'),\n resolve(process.cwd(), '.env'),\n ];\n\n for (const envPath of candidatePaths) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n if (content.trim().length > 0) {\n return content;\n }\n } catch {\n continue;\n }\n }\n\n return null;\n}\n\nfunction loadEnvValue(key: string): string | undefined {\n if (process.env[key]) return process.env[key];\n const content = loadEnvFileContent();\n if (!content) return undefined;\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n return match?.[1]?.trim();\n}\n\nexport const DEFAULT_CREDENTIALS: Record<string, { email: string; password: string }> = {\n superadmin: {\n email: loadEnvValue('OM_INIT_SUPERADMIN_EMAIL') || 'superadmin@acme.com',\n password: loadEnvValue('OM_INIT_SUPERADMIN_PASSWORD') || 'secret',\n },\n admin: { email: 'admin@acme.com', password: 'secret' },\n employee: { email: 'employee@acme.com', password: 'secret' },\n};\n\nexport type Role = 'superadmin' | 'admin' | 'employee';\n\nfunction decodeJwtClaims(token: string): { tenantId?: string; orgId?: string | null } | null {\n const parts = token.split('.');\n if (parts.length < 2) return null;\n try {\n const normalized = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');\n const payload = JSON.parse(Buffer.from(padded, 'base64').toString('utf8')) as {\n tenantId?: string;\n orgId?: string | null;\n };\n return payload;\n } catch {\n return null;\n }\n}\n\nasync function acknowledgeGlobalNotices(page: Page): Promise<void> {\n const baseUrl = process.env.BASE_URL || 'http://localhost:3000';\n await page.context().addCookies([\n {\n name: 'om_demo_notice_ack',\n value: 'ack',\n url: baseUrl,\n sameSite: 'Lax',\n },\n {\n name: 'om_cookie_notice_ack',\n value: 'ack',\n url: baseUrl,\n sameSite: 'Lax',\n },\n ]);\n}\n\nasync function dismissGlobalNoticesIfPresent(page: Page): Promise<void> {\n const cookieAcceptButton = page.getByRole('button', { name: /accept cookies/i }).first();\n if (await cookieAcceptButton.isVisible().catch(() => false)) {\n await cookieAcceptButton.click();\n }\n\n const demoNotice = page.getByText(/this instance is provided for demo purposes only/i).first();\n if (await demoNotice.isVisible().catch(() => false)) {\n const noticeContainer = demoNotice.locator('xpath=ancestor::div[contains(@class,\"pointer-events-auto\")]').first();\n const dismissButton = noticeContainer.locator('button').first();\n if (await dismissButton.isVisible().catch(() => false)) {\n await dismissButton.click();\n }\n }\n}\n\nasync function recoverClientSideErrorPageIfPresent(page: Page): Promise<void> {\n const clientErrorHeading = page\n .getByRole('heading', { name: /Application error: a client-side exception has occurred/i })\n .first();\n if (!(await clientErrorHeading.isVisible().catch(() => false))) return;\n await page.reload({ waitUntil: 'domcontentloaded' });\n await dismissGlobalNoticesIfPresent(page);\n}\n\nasync function recoverGenericErrorPageIfPresent(page: Page): Promise<void> {\n const errorHeading = page.getByRole('heading', { name: /^Something went wrong$/i }).first();\n if (!(await errorHeading.isVisible().catch(() => false))) return;\n const retryButton = page.getByRole('button', { name: /Try again/i }).first();\n if (await retryButton.isVisible().catch(() => false)) {\n await retryButton.click().catch(() => {});\n await page.waitForLoadState('domcontentloaded').catch(() => {});\n await page.waitForTimeout(500).catch(() => {});\n } else {\n await page.reload({ waitUntil: 'domcontentloaded' });\n }\n await dismissGlobalNoticesIfPresent(page);\n}\n\nexport async function login(page: Page, role: Role = 'admin'): Promise<void> {\n const creds = DEFAULT_CREDENTIALS[role];\n const loginReadySelector = 'form[data-auth-ready=\"1\"]';\n const hasBackendUrl = (): boolean => /\\/backend(?:\\/.*)?$/.test(page.url());\n const waitForBackend = async (timeout: number): Promise<boolean> => {\n try {\n await page.waitForURL(/\\/backend(?:\\/.*)?$/, { timeout });\n return true;\n } catch {\n return hasBackendUrl();\n }\n };\n\n await acknowledgeGlobalNotices(page);\n const apiLoginForm = new URLSearchParams();\n apiLoginForm.set('email', creds.email);\n apiLoginForm.set('password', creds.password);\n const apiLoginResponse = await page.request.post('/api/auth/login', {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: apiLoginForm.toString(),\n }).catch(() => null);\n if (apiLoginResponse?.ok()) {\n const apiLoginBody = (await apiLoginResponse.json().catch(() => null)) as { token?: string } | null;\n const claims = typeof apiLoginBody?.token === 'string' ? decodeJwtClaims(apiLoginBody.token) : null;\n const baseUrl = process.env.BASE_URL || 'http://localhost:3000';\n const cookies = [];\n if (claims?.tenantId) {\n cookies.push({\n name: 'om_selected_tenant',\n value: claims.tenantId,\n url: baseUrl,\n sameSite: 'Lax' as const,\n });\n }\n if (claims?.orgId) {\n cookies.push({\n name: 'om_selected_org',\n value: claims.orgId,\n url: baseUrl,\n sameSite: 'Lax' as const,\n });\n }\n if (cookies.length > 0) {\n await page.context().addCookies(cookies);\n }\n await page.goto('/backend', { waitUntil: 'domcontentloaded' });\n if (await waitForBackend(8_000)) return;\n }\n\n for (let attempt = 0; attempt < 4; attempt += 1) {\n await page.goto('/login', { waitUntil: 'domcontentloaded' });\n await dismissGlobalNoticesIfPresent(page);\n await recoverClientSideErrorPageIfPresent(page);\n await recoverGenericErrorPageIfPresent(page);\n await page.waitForSelector(loginReadySelector, { state: 'visible', timeout: 3_000 }).catch(() => null);\n if (await page.getByLabel('Email').isVisible().catch(() => false)) break;\n if (attempt === 3) {\n throw new Error(`Login form is unavailable for role: ${role}; current URL: ${page.url()}`);\n }\n }\n await page.getByLabel('Email').fill(creds.email);\n\n const passwordInput = page.getByLabel('Password').first();\n if (await passwordInput.isVisible().catch(() => false)) {\n await passwordInput.fill(creds.password);\n await passwordInput.press('Enter');\n } else {\n const submitButton = page.getByRole('button', { name: /login|sign in|continue with sso/i }).first();\n await submitButton.click();\n }\n\n if (await waitForBackend(7_000)) return;\n\n const loginForm = page.locator('form').first();\n if (await loginForm.isVisible().catch(() => false)) {\n await loginForm.evaluate((element) => {\n const form = element as HTMLFormElement\n form.requestSubmit()\n }).catch(() => {})\n }\n if (await waitForBackend(5_000)) return;\n\n const loginButton = page.getByRole('button', { name: /login|sign in|continue with sso/i }).first();\n if (await loginButton.isVisible().catch(() => false)) {\n await loginButton.click({ force: true });\n }\n if (await waitForBackend(8_000)) return;\n\n throw new Error(`Login did not reach backend for role: ${role}; current URL: ${page.url()}`);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { apiRequest } from "./api.js";
|
|
3
|
+
import { expectId, readJsonSafe } from "./generalFixtures.js";
|
|
4
|
+
async function createRoleFixture(request, token, input) {
|
|
5
|
+
const response = await apiRequest(request, "POST", "/api/auth/roles", {
|
|
6
|
+
token,
|
|
7
|
+
data: {
|
|
8
|
+
name: input.name,
|
|
9
|
+
tenantId: input.tenantId ?? null
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
const body = await readJsonSafe(response);
|
|
13
|
+
expect(response.status(), "POST /api/auth/roles should return 201").toBe(201);
|
|
14
|
+
return expectId(body?.id, "Role creation response should include id");
|
|
15
|
+
}
|
|
16
|
+
async function deleteRoleIfExists(request, token, roleId) {
|
|
17
|
+
if (!token || !roleId) return;
|
|
18
|
+
await apiRequest(request, "DELETE", `/api/auth/roles?id=${encodeURIComponent(roleId)}`, { token }).catch(() => void 0);
|
|
19
|
+
}
|
|
20
|
+
async function createUserFixture(request, token, input) {
|
|
21
|
+
const response = await apiRequest(request, "POST", "/api/auth/users", {
|
|
22
|
+
token,
|
|
23
|
+
data: input
|
|
24
|
+
});
|
|
25
|
+
const body = await readJsonSafe(response);
|
|
26
|
+
expect(response.status(), "POST /api/auth/users should return 201").toBe(201);
|
|
27
|
+
return expectId(body?.id, "User creation response should include id");
|
|
28
|
+
}
|
|
29
|
+
async function deleteUserIfExists(request, token, userId) {
|
|
30
|
+
if (!token || !userId) return;
|
|
31
|
+
await apiRequest(request, "DELETE", `/api/auth/users?id=${encodeURIComponent(userId)}`, { token }).catch(() => void 0);
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
createRoleFixture,
|
|
35
|
+
createUserFixture,
|
|
36
|
+
deleteRoleIfExists,
|
|
37
|
+
deleteUserIfExists
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=authFixtures.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/authFixtures.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\nimport { expectId, readJsonSafe } from './generalFixtures';\n\nexport async function createRoleFixture(\n request: APIRequestContext,\n token: string,\n input: { name: string; tenantId?: string | null },\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/auth/roles', {\n token,\n data: {\n name: input.name,\n tenantId: input.tenantId ?? null,\n },\n });\n const body = await readJsonSafe<{ id?: string }>(response);\n expect(response.status(), 'POST /api/auth/roles should return 201').toBe(201);\n return expectId(body?.id, 'Role creation response should include id');\n}\n\nexport async function deleteRoleIfExists(\n request: APIRequestContext,\n token: string | null,\n roleId: string | null,\n): Promise<void> {\n if (!token || !roleId) return;\n await apiRequest(request, 'DELETE', `/api/auth/roles?id=${encodeURIComponent(roleId)}`, { token }).catch(() => undefined);\n}\n\nexport async function createUserFixture(\n request: APIRequestContext,\n token: string,\n input: { email: string; password: string; organizationId: string; roles: string[] },\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/auth/users', {\n token,\n data: input,\n });\n const body = await readJsonSafe<{ id?: string }>(response);\n expect(response.status(), 'POST /api/auth/users should return 201').toBe(201);\n return expectId(body?.id, 'User creation response should include id');\n}\n\nexport async function deleteUserIfExists(\n request: APIRequestContext,\n token: string | null,\n userId: string | null,\n): Promise<void> {\n if (!token || !userId) return;\n await apiRequest(request, 'DELETE', `/api/auth/users?id=${encodeURIComponent(userId)}`, { token }).catch(() => undefined);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,UAAU,oBAAoB;AAEvC,eAAsB,kBACpB,SACA,OACA,OACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,mBAAmB;AAAA,IACpE;AAAA,IACA,MAAM;AAAA,MACJ,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM,YAAY;AAAA,IAC9B;AAAA,EACF,CAAC;AACD,QAAM,OAAO,MAAM,aAA8B,QAAQ;AACzD,SAAO,SAAS,OAAO,GAAG,wCAAwC,EAAE,KAAK,GAAG;AAC5E,SAAO,SAAS,MAAM,IAAI,0CAA0C;AACtE;AAEA,eAAsB,mBACpB,SACA,OACA,QACe;AACf,MAAI,CAAC,SAAS,CAAC,OAAQ;AACvB,QAAM,WAAW,SAAS,UAAU,sBAAsB,mBAAmB,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,MAAM,MAAS;AAC1H;AAEA,eAAsB,kBACpB,SACA,OACA,OACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,mBAAmB;AAAA,IACpE;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACD,QAAM,OAAO,MAAM,aAA8B,QAAQ;AACzD,SAAO,SAAS,OAAO,GAAG,wCAAwC,EAAE,KAAK,GAAG;AAC5E,SAAO,SAAS,MAAM,IAAI,0CAA0C;AACtE;AAEA,eAAsB,mBACpB,SACA,OACA,QACe;AACf,MAAI,CAAC,SAAS,CAAC,OAAQ;AACvB,QAAM,WAAW,SAAS,UAAU,sBAAsB,mBAAmB,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,MAAM,MAAS;AAC1H;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
async function createUserViaUi(page, input) {
|
|
3
|
+
const role = input.role ?? "employee";
|
|
4
|
+
await page.goto("/backend/users/create");
|
|
5
|
+
await expect(page.getByText("Create User")).toBeVisible();
|
|
6
|
+
await page.getByRole("textbox").nth(0).fill(input.email);
|
|
7
|
+
await page.getByRole("textbox").nth(1).fill(input.password);
|
|
8
|
+
const orgSelect = page.locator("main").locator("select").first();
|
|
9
|
+
await expect(orgSelect).toBeEnabled();
|
|
10
|
+
const orgValue = await orgSelect.evaluate((element) => {
|
|
11
|
+
const select = element;
|
|
12
|
+
for (const option of Array.from(select.options)) {
|
|
13
|
+
if (option.value && option.value.trim().length > 0) return option.value;
|
|
14
|
+
}
|
|
15
|
+
return "";
|
|
16
|
+
});
|
|
17
|
+
if (orgValue) {
|
|
18
|
+
await orgSelect.selectOption(orgValue);
|
|
19
|
+
}
|
|
20
|
+
const rolesInput = page.getByRole("textbox", { name: /add tag and press enter/i });
|
|
21
|
+
await rolesInput.fill(role);
|
|
22
|
+
await rolesInput.press("Enter");
|
|
23
|
+
await page.getByRole("button", { name: "Create" }).first().click();
|
|
24
|
+
await expect(page).toHaveURL(/\/backend\/users(?:\?.*)?$/);
|
|
25
|
+
await page.getByRole("textbox", { name: "Search" }).fill(input.email);
|
|
26
|
+
await expect(page.getByRole("row", { name: new RegExp(input.email, "i") })).toBeVisible();
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
createUserViaUi
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=authUi.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/authUi.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, type Page } from '@playwright/test';\n\nexport async function createUserViaUi(page: Page, input: { email: string; password: string; role?: string }) {\n const role = input.role ?? 'employee';\n\n await page.goto('/backend/users/create');\n await expect(page.getByText('Create User')).toBeVisible();\n\n await page.getByRole('textbox').nth(0).fill(input.email);\n await page.getByRole('textbox').nth(1).fill(input.password);\n\n const orgSelect = page.locator('main').locator('select').first();\n await expect(orgSelect).toBeEnabled();\n const orgValue = await orgSelect.evaluate((element) => {\n const select = element as HTMLSelectElement;\n for (const option of Array.from(select.options)) {\n if (option.value && option.value.trim().length > 0) return option.value;\n }\n return '';\n });\n if (orgValue) {\n await orgSelect.selectOption(orgValue);\n }\n\n const rolesInput = page.getByRole('textbox', { name: /add tag and press enter/i });\n await rolesInput.fill(role);\n await rolesInput.press('Enter');\n\n await page.getByRole('button', { name: 'Create' }).first().click();\n await expect(page).toHaveURL(/\\/backend\\/users(?:\\?.*)?$/);\n await page.getByRole('textbox', { name: 'Search' }).fill(input.email);\n await expect(page.getByRole('row', { name: new RegExp(input.email, 'i') })).toBeVisible();\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAyB;AAElC,eAAsB,gBAAgB,MAAY,OAA2D;AAC3G,QAAM,OAAO,MAAM,QAAQ;AAE3B,QAAM,KAAK,KAAK,uBAAuB;AACvC,QAAM,OAAO,KAAK,UAAU,aAAa,CAAC,EAAE,YAAY;AAExD,QAAM,KAAK,UAAU,SAAS,EAAE,IAAI,CAAC,EAAE,KAAK,MAAM,KAAK;AACvD,QAAM,KAAK,UAAU,SAAS,EAAE,IAAI,CAAC,EAAE,KAAK,MAAM,QAAQ;AAE1D,QAAM,YAAY,KAAK,QAAQ,MAAM,EAAE,QAAQ,QAAQ,EAAE,MAAM;AAC/D,QAAM,OAAO,SAAS,EAAE,YAAY;AACpC,QAAM,WAAW,MAAM,UAAU,SAAS,CAAC,YAAY;AACrD,UAAM,SAAS;AACf,eAAW,UAAU,MAAM,KAAK,OAAO,OAAO,GAAG;AAC/C,UAAI,OAAO,SAAS,OAAO,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,OAAO;AAAA,IACpE;AACA,WAAO;AAAA,EACT,CAAC;AACD,MAAI,UAAU;AACZ,UAAM,UAAU,aAAa,QAAQ;AAAA,EACvC;AAEA,QAAM,aAAa,KAAK,UAAU,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACjF,QAAM,WAAW,KAAK,IAAI;AAC1B,QAAM,WAAW,MAAM,OAAO;AAE9B,QAAM,KAAK,UAAU,UAAU,EAAE,MAAM,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM;AACjE,QAAM,OAAO,IAAI,EAAE,UAAU,4BAA4B;AACzD,QAAM,KAAK,UAAU,WAAW,EAAE,MAAM,SAAS,CAAC,EAAE,KAAK,MAAM,KAAK;AACpE,QAAM,OAAO,KAAK,UAAU,OAAO,EAAE,MAAM,IAAI,OAAO,MAAM,OAAO,GAAG,EAAE,CAAC,CAAC,EAAE,YAAY;AAC1F;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { apiRequest } from "./api.js";
|
|
3
|
+
import { expectId, readJsonSafe } from "./generalFixtures.js";
|
|
4
|
+
async function createRuleSetFixture(request, token, data) {
|
|
5
|
+
const response = await apiRequest(request, "POST", "/api/business_rules/sets", { token, data });
|
|
6
|
+
const body = await readJsonSafe(response);
|
|
7
|
+
expect(response.status(), "POST /api/business_rules/sets should return 201").toBe(201);
|
|
8
|
+
return expectId(body?.id, "Rule set creation response should include id");
|
|
9
|
+
}
|
|
10
|
+
async function deleteRuleSetIfExists(request, token, ruleSetId) {
|
|
11
|
+
if (!token || !ruleSetId) return;
|
|
12
|
+
await apiRequest(
|
|
13
|
+
request,
|
|
14
|
+
"DELETE",
|
|
15
|
+
`/api/business_rules/sets?id=${encodeURIComponent(ruleSetId)}`,
|
|
16
|
+
{ token }
|
|
17
|
+
).catch(() => void 0);
|
|
18
|
+
}
|
|
19
|
+
async function createBusinessRuleFixture(request, token, data) {
|
|
20
|
+
const response = await apiRequest(request, "POST", "/api/business_rules/rules", { token, data });
|
|
21
|
+
const body = await readJsonSafe(response);
|
|
22
|
+
expect(response.status(), "POST /api/business_rules/rules should return 201").toBe(201);
|
|
23
|
+
return expectId(body?.id, "Business rule creation response should include id");
|
|
24
|
+
}
|
|
25
|
+
async function deleteBusinessRuleIfExists(request, token, ruleId) {
|
|
26
|
+
if (!token || !ruleId) return;
|
|
27
|
+
await apiRequest(
|
|
28
|
+
request,
|
|
29
|
+
"DELETE",
|
|
30
|
+
`/api/business_rules/rules?id=${encodeURIComponent(ruleId)}`,
|
|
31
|
+
{ token }
|
|
32
|
+
).catch(() => void 0);
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
createBusinessRuleFixture,
|
|
36
|
+
createRuleSetFixture,
|
|
37
|
+
deleteBusinessRuleIfExists,
|
|
38
|
+
deleteRuleSetIfExists
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=businessRulesFixtures.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/businessRulesFixtures.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\nimport { expectId, readJsonSafe } from './generalFixtures';\n\nexport async function createRuleSetFixture(\n request: APIRequestContext,\n token: string,\n data: Record<string, unknown>,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/business_rules/sets', { token, data });\n const body = await readJsonSafe<{ id?: string }>(response);\n expect(response.status(), 'POST /api/business_rules/sets should return 201').toBe(201);\n return expectId(body?.id, 'Rule set creation response should include id');\n}\n\nexport async function deleteRuleSetIfExists(\n request: APIRequestContext,\n token: string | null,\n ruleSetId: string | null,\n): Promise<void> {\n if (!token || !ruleSetId) return;\n await apiRequest(\n request,\n 'DELETE',\n `/api/business_rules/sets?id=${encodeURIComponent(ruleSetId)}`,\n { token },\n ).catch(() => undefined);\n}\n\nexport async function createBusinessRuleFixture(\n request: APIRequestContext,\n token: string,\n data: Record<string, unknown>,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/business_rules/rules', { token, data });\n const body = await readJsonSafe<{ id?: string }>(response);\n expect(response.status(), 'POST /api/business_rules/rules should return 201').toBe(201);\n return expectId(body?.id, 'Business rule creation response should include id');\n}\n\nexport async function deleteBusinessRuleIfExists(\n request: APIRequestContext,\n token: string | null,\n ruleId: string | null,\n): Promise<void> {\n if (!token || !ruleId) return;\n await apiRequest(\n request,\n 'DELETE',\n `/api/business_rules/rules?id=${encodeURIComponent(ruleId)}`,\n { token },\n ).catch(() => undefined);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,UAAU,oBAAoB;AAEvC,eAAsB,qBACpB,SACA,OACA,MACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,4BAA4B,EAAE,OAAO,KAAK,CAAC;AAC9F,QAAM,OAAO,MAAM,aAA8B,QAAQ;AACzD,SAAO,SAAS,OAAO,GAAG,iDAAiD,EAAE,KAAK,GAAG;AACrF,SAAO,SAAS,MAAM,IAAI,8CAA8C;AAC1E;AAEA,eAAsB,sBACpB,SACA,OACA,WACe;AACf,MAAI,CAAC,SAAS,CAAC,UAAW;AAC1B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,+BAA+B,mBAAmB,SAAS,CAAC;AAAA,IAC5D,EAAE,MAAM;AAAA,EACV,EAAE,MAAM,MAAM,MAAS;AACzB;AAEA,eAAsB,0BACpB,SACA,OACA,MACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,6BAA6B,EAAE,OAAO,KAAK,CAAC;AAC/F,QAAM,OAAO,MAAM,aAA8B,QAAQ;AACzD,SAAO,SAAS,OAAO,GAAG,kDAAkD,EAAE,KAAK,GAAG;AACtF,SAAO,SAAS,MAAM,IAAI,mDAAmD;AAC/E;AAEA,eAAsB,2BACpB,SACA,OACA,QACe;AACf,MAAI,CAAC,SAAS,CAAC,OAAQ;AACvB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,gCAAgC,mBAAmB,MAAM,CAAC;AAAA,IAC1D,EAAE,MAAM;AAAA,EACV,EAAE,MAAM,MAAM,MAAS;AACzB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { apiRequest } from "./api.js";
|
|
3
|
+
async function createProductFixture(request, token, input) {
|
|
4
|
+
const response = await apiRequest(request, "POST", "/api/catalog/products", {
|
|
5
|
+
token,
|
|
6
|
+
data: {
|
|
7
|
+
title: input.title,
|
|
8
|
+
sku: input.sku,
|
|
9
|
+
description: "Long enough description for SEO checks in QA automation flows. This text keeps the create validation satisfied."
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
expect(response.ok(), `Failed to create product fixture: ${response.status()}`).toBeTruthy();
|
|
13
|
+
const body = await response.json();
|
|
14
|
+
expect(typeof body.id === "string" && body.id.length > 0).toBeTruthy();
|
|
15
|
+
return body.id;
|
|
16
|
+
}
|
|
17
|
+
async function createCategoryFixture(request, token, input) {
|
|
18
|
+
const response = await apiRequest(request, "POST", "/api/catalog/categories", {
|
|
19
|
+
token,
|
|
20
|
+
data: { name: input.name }
|
|
21
|
+
});
|
|
22
|
+
expect(response.ok(), `Failed to create category fixture: ${response.status()}`).toBeTruthy();
|
|
23
|
+
const body = await response.json();
|
|
24
|
+
expect(typeof body.id === "string" && body.id.length > 0).toBeTruthy();
|
|
25
|
+
return body.id;
|
|
26
|
+
}
|
|
27
|
+
async function deleteCatalogCategoryIfExists(request, token, categoryId) {
|
|
28
|
+
if (!token || !categoryId) return;
|
|
29
|
+
try {
|
|
30
|
+
await apiRequest(request, "DELETE", `/api/catalog/categories?id=${encodeURIComponent(categoryId)}`, { token });
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function deleteCatalogProductIfExists(request, token, productId) {
|
|
36
|
+
if (!token || !productId) return;
|
|
37
|
+
try {
|
|
38
|
+
await apiRequest(request, "DELETE", `/api/catalog/products?id=${encodeURIComponent(productId)}`, { token });
|
|
39
|
+
} catch {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
createCategoryFixture,
|
|
45
|
+
createProductFixture,
|
|
46
|
+
deleteCatalogCategoryIfExists,
|
|
47
|
+
deleteCatalogProductIfExists
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=catalogFixtures.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/helpers/integration/catalogFixtures.ts"],
|
|
4
|
+
"sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\n\ntype ProductFixtureInput = {\n title: string;\n sku: string;\n};\n\nexport async function createProductFixture(\n request: APIRequestContext,\n token: string,\n input: ProductFixtureInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/catalog/products', {\n token,\n data: {\n title: input.title,\n sku: input.sku,\n description:\n 'Long enough description for SEO checks in QA automation flows. This text keeps the create validation satisfied.',\n },\n });\n expect(response.ok(), `Failed to create product fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { id?: string };\n expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy();\n return body.id as string;\n}\n\ntype CategoryFixtureInput = {\n name: string;\n};\n\nexport async function createCategoryFixture(\n request: APIRequestContext,\n token: string,\n input: CategoryFixtureInput,\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/catalog/categories', {\n token,\n data: { name: input.name },\n });\n expect(response.ok(), `Failed to create category fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { id?: string };\n expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy();\n return body.id as string;\n}\n\nexport async function deleteCatalogCategoryIfExists(\n request: APIRequestContext,\n token: string | null,\n categoryId: string | null,\n): Promise<void> {\n if (!token || !categoryId) return;\n try {\n await apiRequest(request, 'DELETE', `/api/catalog/categories?id=${encodeURIComponent(categoryId)}`, { token });\n } catch {\n return;\n }\n}\n\nexport async function deleteCatalogProductIfExists(\n request: APIRequestContext,\n token: string | null,\n productId: string | null,\n): Promise<void> {\n if (!token || !productId) return;\n try {\n await apiRequest(request, 'DELETE', `/api/catalog/products?id=${encodeURIComponent(productId)}`, { token });\n } catch {\n return;\n }\n}\n\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAO3B,eAAsB,qBACpB,SACA,OACA,OACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,yBAAyB;AAAA,IAC1E;AAAA,IACA,MAAM;AAAA,MACJ,OAAO,MAAM;AAAA,MACb,KAAK,MAAM;AAAA,MACX,aACE;AAAA,IACJ;AAAA,EACF,CAAC;AACD,SAAO,SAAS,GAAG,GAAG,qCAAqC,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC3F,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,CAAC,EAAE,WAAW;AACrE,SAAO,KAAK;AACd;AAMA,eAAsB,sBACpB,SACA,OACA,OACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,2BAA2B;AAAA,IAC5E;AAAA,IACA,MAAM,EAAE,MAAM,MAAM,KAAK;AAAA,EAC3B,CAAC;AACD,SAAO,SAAS,GAAG,GAAG,sCAAsC,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC5F,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,CAAC,EAAE,WAAW;AACrE,SAAO,KAAK;AACd;AAEA,eAAsB,8BACpB,SACA,OACA,YACe;AACf,MAAI,CAAC,SAAS,CAAC,WAAY;AAC3B,MAAI;AACF,UAAM,WAAW,SAAS,UAAU,8BAA8B,mBAAmB,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EAC/G,QAAQ;AACN;AAAA,EACF;AACF;AAEA,eAAsB,6BACpB,SACA,OACA,WACe;AACf,MAAI,CAAC,SAAS,CAAC,UAAW;AAC1B,MAAI;AACF,UAAM,WAAW,SAAS,UAAU,4BAA4B,mBAAmB,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EAC5G,QAAQ;AACN;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|