@open-mercato/core 0.6.5-develop.4703.1.59a086a9ee → 0.6.5-develop.4718.1.56d834bb34
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/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/currenciesFixtures.js +2 -1
- package/dist/helpers/integration/currenciesFixtures.js.map +2 -2
- package/dist/helpers/integration/undoHarness.js +2 -1
- package/dist/helpers/integration/undoHarness.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/currenciesFixtures.ts +2 -1
- package/src/helpers/integration/undoHarness.ts +2 -1
- package/src/modules/currencies/i18n/de.json +1 -0
- package/src/modules/currencies/i18n/en.json +1 -0
- package/src/modules/currencies/i18n/es.json +1 -0
- package/src/modules/currencies/i18n/pl.json +1 -0
- package/src/modules/data_sync/i18n/de.json +1 -0
- package/src/modules/data_sync/i18n/en.json +1 -0
- package/src/modules/data_sync/i18n/es.json +1 -0
- package/src/modules/data_sync/i18n/pl.json +1 -0
- package/src/modules/inbox_ops/i18n/de.json +2 -0
- package/src/modules/inbox_ops/i18n/en.json +2 -0
- package/src/modules/inbox_ops/i18n/es.json +2 -0
- package/src/modules/inbox_ops/i18n/pl.json +2 -0
- package/src/modules/integrations/i18n/de.json +2 -0
- package/src/modules/integrations/i18n/en.json +2 -0
- package/src/modules/integrations/i18n/es.json +2 -0
- package/src/modules/integrations/i18n/pl.json +2 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expect } from "@playwright/test";
|
|
2
|
+
import { randomInt } from "node:crypto";
|
|
2
3
|
import { apiRequest } from "./api.js";
|
|
3
4
|
import { getTokenContext } from "./generalFixtures.js";
|
|
4
5
|
async function createCurrencyFixture(request, token, input) {
|
|
@@ -26,7 +27,7 @@ const SEEDED_CURRENCY_CODES = /* @__PURE__ */ new Set([
|
|
|
26
27
|
]);
|
|
27
28
|
const reservedCurrencyCodes = /* @__PURE__ */ new Set();
|
|
28
29
|
function generateUniqueCurrencyCode() {
|
|
29
|
-
const letter = () => String.fromCharCode(65 +
|
|
30
|
+
const letter = () => String.fromCharCode(65 + randomInt(26));
|
|
30
31
|
for (let attempt = 0; attempt < 200; attempt += 1) {
|
|
31
32
|
const code = `${letter()}${letter()}${letter()}`;
|
|
32
33
|
if (!SEEDED_CURRENCY_CODES.has(code) && !reservedCurrencyCodes.has(code)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/currenciesFixtures.ts"],
|
|
4
|
-
"sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\nimport { getTokenContext } from './generalFixtures';\n\nexport async function createCurrencyFixture(\n request: APIRequestContext,\n token: string,\n input: { code: string; name: string; symbol?: string },\n): Promise<string> {\n const { organizationId, tenantId } = getTokenContext(token);\n const response = await apiRequest(request, 'POST', '/api/currencies/currencies', {\n token,\n data: { organizationId, tenantId, code: input.code, name: input.name, symbol: input.symbol ?? null },\n });\n expect(response.ok(), `Failed to create currency 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\n// Codes seeded by the currencies module (seedExampleCurrencies). Generated test\n// codes avoid these so fixtures never collide with seeded rows.\nconst SEEDED_CURRENCY_CODES = new Set([\n 'USD', 'EUR', 'JPY', 'GBP', 'CHF', 'CAD', 'AUD', 'CNY', 'CNH', 'PLN',\n]);\n// Reserved across the worker so two fixtures never draw the same code in one run.\nconst reservedCurrencyCodes = new Set<string>();\n\n/** Draws an ISO-style three-letter code unused by seeds or earlier fixtures. */\nexport function generateUniqueCurrencyCode(): string {\n const letter = () => String.fromCharCode(65 +
|
|
5
|
-
"mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAEhC,eAAsB,sBACpB,SACA,OACA,OACiB;AACjB,QAAM,EAAE,gBAAgB,SAAS,IAAI,gBAAgB,KAAK;AAC1D,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,8BAA8B;AAAA,IAC/E;AAAA,IACA,MAAM,EAAE,gBAAgB,UAAU,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,MAAM,UAAU,KAAK;AAAA,EACrG,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;AAIA,MAAM,wBAAwB,oBAAI,IAAI;AAAA,EACpC;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AACjE,CAAC;AAED,MAAM,wBAAwB,oBAAI,IAAY;AAGvC,SAAS,6BAAqC;AACnD,QAAM,SAAS,MAAM,OAAO,aAAa,KAAK,
|
|
4
|
+
"sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { randomInt } from 'node:crypto';\nimport { apiRequest } from './api';\nimport { getTokenContext } from './generalFixtures';\n\nexport async function createCurrencyFixture(\n request: APIRequestContext,\n token: string,\n input: { code: string; name: string; symbol?: string },\n): Promise<string> {\n const { organizationId, tenantId } = getTokenContext(token);\n const response = await apiRequest(request, 'POST', '/api/currencies/currencies', {\n token,\n data: { organizationId, tenantId, code: input.code, name: input.name, symbol: input.symbol ?? null },\n });\n expect(response.ok(), `Failed to create currency 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\n// Codes seeded by the currencies module (seedExampleCurrencies). Generated test\n// codes avoid these so fixtures never collide with seeded rows.\nconst SEEDED_CURRENCY_CODES = new Set([\n 'USD', 'EUR', 'JPY', 'GBP', 'CHF', 'CAD', 'AUD', 'CNY', 'CNH', 'PLN',\n]);\n// Reserved across the worker so two fixtures never draw the same code in one run.\nconst reservedCurrencyCodes = new Set<string>();\n\n/** Draws an ISO-style three-letter code unused by seeds or earlier fixtures. */\nexport function generateUniqueCurrencyCode(): string {\n const letter = () => String.fromCharCode(65 + randomInt(26));\n for (let attempt = 0; attempt < 200; attempt += 1) {\n const code = `${letter()}${letter()}${letter()}`;\n if (!SEEDED_CURRENCY_CODES.has(code) && !reservedCurrencyCodes.has(code)) {\n reservedCurrencyCodes.add(code);\n return code;\n }\n }\n throw new Error('[internal] exhausted unique currency code space');\n}\n\n/**\n * Creates a currency with a generated unique code and returns its id and code.\n *\n * Currency DELETE is a soft delete, but the (organization, tenant, code) unique\n * constraint still counts soft-deleted rows \u2014 re-using a code an earlier test\n * soft-deleted makes the create fail. Drawing from the full three-letter space\n * (minus seeds) and retrying with a fresh code on an accidental collision keeps\n * fixture setup deterministic across runs that share a database.\n */\nexport async function createRandomCurrencyFixture(\n request: APIRequestContext,\n token: string,\n input: { name: string; symbol?: string; isActive?: boolean },\n): Promise<{ id: string; code: string }> {\n const { organizationId, tenantId } = getTokenContext(token);\n let lastStatus = 0;\n for (let attempt = 0; attempt < 8; attempt += 1) {\n const code = generateUniqueCurrencyCode();\n const data: Record<string, unknown> = {\n organizationId,\n tenantId,\n code,\n name: input.name,\n symbol: input.symbol ?? null,\n };\n if (typeof input.isActive === 'boolean') data.isActive = input.isActive;\n const response = await apiRequest(request, 'POST', '/api/currencies/currencies', { token, data });\n if (response.status() === 201) {\n const body = (await response.json()) as { id?: string };\n if (typeof body.id === 'string' && body.id.length > 0) {\n return { id: body.id, code };\n }\n }\n lastStatus = response.status();\n }\n throw new Error(`[internal] failed to create currency fixture after retries (last status ${lastStatus})`);\n}\n\nexport async function createFetchConfigFixture(\n request: APIRequestContext,\n token: string,\n input: { provider: string; isEnabled: boolean },\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/currencies/fetch-configs', {\n token,\n data: input,\n });\n expect(response.ok(), `Failed to create fetch config fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { config?: { id?: string } };\n const id = body.config?.id;\n expect(typeof id === 'string' && id.length > 0).toBeTruthy();\n return id as string;\n}\n\nexport async function deleteCurrenciesEntityIfExists(\n request: APIRequestContext,\n token: string | null,\n path: string,\n id: string | null,\n): Promise<void> {\n if (!token || !id) return;\n try {\n await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });\n } catch {\n return;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAEhC,eAAsB,sBACpB,SACA,OACA,OACiB;AACjB,QAAM,EAAE,gBAAgB,SAAS,IAAI,gBAAgB,KAAK;AAC1D,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,8BAA8B;AAAA,IAC/E;AAAA,IACA,MAAM,EAAE,gBAAgB,UAAU,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,MAAM,UAAU,KAAK;AAAA,EACrG,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;AAIA,MAAM,wBAAwB,oBAAI,IAAI;AAAA,EACpC;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AACjE,CAAC;AAED,MAAM,wBAAwB,oBAAI,IAAY;AAGvC,SAAS,6BAAqC;AACnD,QAAM,SAAS,MAAM,OAAO,aAAa,KAAK,UAAU,EAAE,CAAC;AAC3D,WAAS,UAAU,GAAG,UAAU,KAAK,WAAW,GAAG;AACjD,UAAM,OAAO,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC;AAC9C,QAAI,CAAC,sBAAsB,IAAI,IAAI,KAAK,CAAC,sBAAsB,IAAI,IAAI,GAAG;AACxE,4BAAsB,IAAI,IAAI;AAC9B,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,IAAI,MAAM,iDAAiD;AACnE;AAWA,eAAsB,4BACpB,SACA,OACA,OACuC;AACvC,QAAM,EAAE,gBAAgB,SAAS,IAAI,gBAAgB,KAAK;AAC1D,MAAI,aAAa;AACjB,WAAS,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG;AAC/C,UAAM,OAAO,2BAA2B;AACxC,UAAM,OAAgC;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM,UAAU;AAAA,IAC1B;AACA,QAAI,OAAO,MAAM,aAAa,UAAW,MAAK,WAAW,MAAM;AAC/D,UAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,8BAA8B,EAAE,OAAO,KAAK,CAAC;AAChG,QAAI,SAAS,OAAO,MAAM,KAAK;AAC7B,YAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAI,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,GAAG;AACrD,eAAO,EAAE,IAAI,KAAK,IAAI,KAAK;AAAA,MAC7B;AAAA,IACF;AACA,iBAAa,SAAS,OAAO;AAAA,EAC/B;AACA,QAAM,IAAI,MAAM,2EAA2E,UAAU,GAAG;AAC1G;AAEA,eAAsB,yBACpB,SACA,OACA,OACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,iCAAiC;AAAA,IAClF;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACD,SAAO,SAAS,GAAG,GAAG,0CAA0C,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAChG,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,QAAM,KAAK,KAAK,QAAQ;AACxB,SAAO,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC,EAAE,WAAW;AAC3D,SAAO;AACT;AAEA,eAAsB,+BACpB,SACA,OACA,MACA,IACe;AACf,MAAI,CAAC,SAAS,CAAC,GAAI;AACnB,MAAI;AACF,UAAM,WAAW,SAAS,UAAU,GAAG,IAAI,OAAO,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EACvF,QAAQ;AACN;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "@playwright/test";
|
|
2
|
+
import { randomInt } from "node:crypto";
|
|
2
3
|
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
3
4
|
import { apiRequest } from "./api.js";
|
|
4
5
|
import { expectId, readJsonSafe } from "./generalFixtures.js";
|
|
@@ -102,7 +103,7 @@ async function deleteEntity(request, token, entity, id) {
|
|
|
102
103
|
return apiRequest(request, "DELETE", path, { token });
|
|
103
104
|
}
|
|
104
105
|
async function runCrudUndoRoundTrip(request, token, entity) {
|
|
105
|
-
const stamp = `${Date.now()}${
|
|
106
|
+
const stamp = `${Date.now()}${randomInt(1e3)}`;
|
|
106
107
|
let createUndoId = null;
|
|
107
108
|
let cycleId = null;
|
|
108
109
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/helpers/integration/undoHarness.ts"],
|
|
4
|
-
"sourcesContent": ["import { type APIRequestContext, type APIResponse, expect, test } from '@playwright/test'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { apiRequest } from './api'\nimport { expectId, readJsonSafe } from './generalFixtures'\n\n/**\n * Shared harness for verifying Undo/Redo correctness against the real command bus.\n *\n * Every mutating Open Mercato API response carries the operation metadata in the\n * `x-om-operation` header (`omop:<urlencoded JSON>`) containing the `undoToken` and the\n * audit log `id` (used as `logId` for redo). These helpers extract that envelope and drive\n * the real undo/redo endpoints so tests can assert full state restoration per TC-UNDO-001.\n */\n\nconst HEADER_PREFIX = 'omop:'\nconst UNDO_PATH = '/api/audit_logs/audit-logs/actions/undo'\nconst REDO_PATH = '/api/audit_logs/audit-logs/actions/redo'\nconst ACTIONS_PATH = '/api/audit_logs/audit-logs/actions'\nexport const UNDO_TESTS_DISABLED_ENV = 'OM_INTEGRATION_UNDO_TESTS_DISABLED'\n\nexport type Operation = {\n logId: string\n undoToken: string\n commandId: string\n resourceKind: string | null\n resourceId: string | null\n}\n\nexport type CrudUndoEntityConfig = {\n label: string\n collectionPath: string\n field: string\n createPayload: (stamp: string) => Record<string, unknown>\n updatePayload: (id: string, stamp: string) => Record<string, unknown>\n readPath?: (id: string) => string\n deletePath?: (id: string) => string\n createStatus?: number\n updateStatus?: number\n}\n\nexport function undoTestsDisabled(env: NodeJS.ProcessEnv = process.env): boolean {\n return parseBooleanWithDefault(env[UNDO_TESTS_DISABLED_ENV], false)\n}\n\nexport function skipIfUndoTestsDisabled(): void {\n test.skip(undoTestsDisabled(), `${UNDO_TESTS_DISABLED_ENV} is set \u2014 undo/redo integration tests skipped`)\n}\n\n/** Parse the `x-om-operation` header into a structured operation, or null when absent/malformed. */\nexport function extractOperation(response: APIResponse): Operation | null {\n const header = response.headers()['x-om-operation']\n if (!header || typeof header !== 'string') return null\n const trimmed = header.startsWith(HEADER_PREFIX) ? header.slice(HEADER_PREFIX.length) : header\n try {\n const parsed = JSON.parse(decodeURIComponent(trimmed)) as Record<string, unknown>\n if (typeof parsed.id !== 'string' || typeof parsed.commandId !== 'string') return null\n if (typeof parsed.undoToken !== 'string' || !parsed.undoToken) return null\n return {\n logId: parsed.id,\n undoToken: parsed.undoToken,\n commandId: parsed.commandId,\n resourceKind: (parsed.resourceKind as string) ?? null,\n resourceId: (parsed.resourceId as string) ?? null,\n }\n } catch {\n return null\n }\n}\n\n/** Like extractOperation but fails the test if no undo token was issued. */\nexport function expectOperation(response: APIResponse, context: string): Operation {\n const op = extractOperation(response)\n expect(op, `Expected an undo token (x-om-operation header) for ${context}, got none`).toBeTruthy()\n return op as Operation\n}\n\nexport async function undoByToken(request: APIRequestContext, token: string, undoToken: string): Promise<APIResponse> {\n return apiRequest(request, 'POST', UNDO_PATH, { token, data: { undoToken } })\n}\n\nexport async function redoByLogId(request: APIRequestContext, token: string, logId: string): Promise<APIResponse> {\n return apiRequest(request, 'POST', REDO_PATH, { token, data: { logId } })\n}\n\n/** Undo and assert success; returns the resolved logId. */\nexport async function undoOk(request: APIRequestContext, token: string, undoToken: string, context: string): Promise<string> {\n const res = await undoByToken(request, token, undoToken)\n const body = (await res.json().catch(() => null)) as { ok?: boolean; logId?: string } | null\n expect(res.ok(), `Undo failed for ${context}: status ${res.status()} body ${JSON.stringify(body)}`).toBeTruthy()\n expect(body?.ok, `Undo not ok for ${context}: ${JSON.stringify(body)}`).toBeTruthy()\n return body?.logId as string\n}\n\n/** Redo and assert success; returns the new operation (new undoToken + logId). */\nexport async function redoOk(request: APIRequestContext, token: string, logId: string, context: string): Promise<{ logId: string; undoToken: string | null }> {\n const res = await redoByLogId(request, token, logId)\n const body = (await res.json().catch(() => null)) as { ok?: boolean; logId?: string; undoToken?: string } | null\n expect(res.ok(), `Redo failed for ${context}: status ${res.status()} body ${JSON.stringify(body)}`).toBeTruthy()\n expect(body?.ok, `Redo not ok for ${context}: ${JSON.stringify(body)}`).toBeTruthy()\n return { logId: body?.logId as string, undoToken: body?.undoToken ?? null }\n}\n\n/** Assert that undoing an already-consumed token is rejected (token consumption / no double-undo). */\nexport async function expectTokenConsumed(request: APIRequestContext, token: string, undoToken: string, context: string): Promise<void> {\n const res = await undoByToken(request, token, undoToken)\n expect(res.ok(), `Expected double-undo to be rejected for ${context}, but it succeeded`).toBeFalsy()\n}\n\n/** Fetch undoable actions list (for Version History assertions). */\nexport async function listUndoable(request: APIRequestContext, token: string, params: Record<string, string> = {}): Promise<unknown> {\n const qs = new URLSearchParams({ undoableOnly: 'true', ...params }).toString()\n const res = await apiRequest(request, 'GET', `${ACTIONS_PATH}?${qs}`, { token })\n return res.json().catch(() => null)\n}\n\n/**\n * Deep-equality assertion for a selected set of fields between two entity snapshots.\n * Reports the first mismatching field with context for clear bug triage.\n */\nexport function assertFieldsEqual(\n actual: Record<string, unknown> | null | undefined,\n expected: Record<string, unknown> | null | undefined,\n fields: string[],\n context: string,\n): void {\n expect(actual, `${context}: actual entity missing`).toBeTruthy()\n expect(expected, `${context}: expected entity missing`).toBeTruthy()\n for (const field of fields) {\n expect(\n JSON.stringify((actual as Record<string, unknown>)[field]),\n `${context}: field \"${field}\" not restored (expected ${JSON.stringify((expected as Record<string, unknown>)[field])}, got ${JSON.stringify((actual as Record<string, unknown>)[field])})`,\n ).toBe(JSON.stringify((expected as Record<string, unknown>)[field]))\n }\n}\n\nfunction findRecord(body: unknown, id: string): Record<string, unknown> | null {\n if (!body || typeof body !== 'object') return null\n if (!Array.isArray(body) && (body as Record<string, unknown>).id === id) {\n return body as Record<string, unknown>\n }\n for (const value of Array.isArray(body) ? body : Object.values(body)) {\n const found = findRecord(value, id)\n if (found) return found\n }\n return null\n}\n\nasync function readRecord(\n request: APIRequestContext,\n token: string,\n entity: CrudUndoEntityConfig,\n id: string,\n): Promise<Record<string, unknown> | null> {\n const path = entity.readPath?.(id) ?? `${entity.collectionPath}?id=${encodeURIComponent(id)}`\n const response = await apiRequest(request, 'GET', path, { token })\n const body = await readJsonSafe(response)\n if (!response.ok()) return null\n return findRecord(body, id)\n}\n\nfunction fieldValue(record: Record<string, unknown> | null, field: string): unknown {\n return record?.[field]\n}\n\nasync function deleteEntity(\n request: APIRequestContext,\n token: string,\n entity: CrudUndoEntityConfig,\n id: string,\n): Promise<APIResponse> {\n const path = entity.deletePath?.(id) ?? `${entity.collectionPath}?id=${encodeURIComponent(id)}`\n return apiRequest(request, 'DELETE', path, { token })\n}\n\nexport async function runCrudUndoRoundTrip(\n request: APIRequestContext,\n token: string,\n entity: CrudUndoEntityConfig,\n): Promise<void> {\n const stamp = `${Date.now()}${Math.floor(Math.random() * 1000)}`\n let createUndoId: string | null = null\n let cycleId: string | null = null\n\n try {\n const createUndoRes = await apiRequest(request, 'POST', entity.collectionPath, {\n token,\n data: entity.createPayload(`${stamp}a`),\n })\n expect(createUndoRes.status(), `${entity.label} create-for-undo status`).toBe(entity.createStatus ?? 201)\n const createUndoOp = expectOperation(createUndoRes, `${entity.label}.create`)\n createUndoId = createUndoOp.resourceId || expectId((await readJsonSafe<Record<string, unknown>>(createUndoRes))?.id, `${entity.label} create id`)\n expect(fieldValue(await readRecord(request, token, entity, createUndoId), entity.field), `${entity.label} field readable after create`).toBeDefined()\n\n await undoOk(request, token, createUndoOp.undoToken, `${entity.label} undo create`)\n expect(await readRecord(request, token, entity, createUndoId), `${entity.label} create\u2192undo soft-deletes/removes the record (I3)`).toBeNull()\n await expectTokenConsumed(request, token, createUndoOp.undoToken, `${entity.label} create token consumed (I5)`)\n\n const createRes = await apiRequest(request, 'POST', entity.collectionPath, {\n token,\n data: entity.createPayload(`${stamp}b`),\n })\n expect(createRes.status(), `${entity.label} create status`).toBe(entity.createStatus ?? 201)\n const createOp = expectOperation(createRes, `${entity.label}.create`)\n cycleId = createOp.resourceId || expectId((await readJsonSafe<Record<string, unknown>>(createRes))?.id, `${entity.label} cycle id`)\n\n const beforeUpdate = await readRecord(request, token, entity, cycleId)\n const beforeValue = fieldValue(beforeUpdate, entity.field)\n expect(beforeValue, `${entity.label} field readable before update`).toBeDefined()\n\n const updateRes = await apiRequest(request, 'PUT', entity.collectionPath, {\n token,\n data: entity.updatePayload(cycleId, stamp),\n })\n expect(updateRes.status(), `${entity.label} update status`).toBe(entity.updateStatus ?? 200)\n const updateOp = expectOperation(updateRes, `${entity.label}.update`)\n const afterUpdate = await readRecord(request, token, entity, cycleId)\n const afterUpdateValue = fieldValue(afterUpdate, entity.field)\n expect(JSON.stringify(afterUpdateValue), `${entity.label} field changed by update`).not.toBe(JSON.stringify(beforeValue))\n\n await new Promise((resolve) => setTimeout(resolve, 10))\n await undoOk(request, token, updateOp.undoToken, `${entity.label} undo update`)\n const afterUndo = await readRecord(request, token, entity, cycleId)\n expect(JSON.stringify(fieldValue(afterUndo, entity.field)), `${entity.label} update\u2192undo restores ${entity.field} (I1)`).toBe(JSON.stringify(beforeValue))\n if (typeof beforeUpdate?.updatedAt === 'string' && typeof afterUndo?.updatedAt === 'string') {\n expect(afterUndo.updatedAt, `${entity.label} undo bumps updatedAt`).not.toBe(beforeUpdate.updatedAt)\n }\n\n await redoOk(request, token, updateOp.logId, `${entity.label} redo update`)\n expect(JSON.stringify(fieldValue(await readRecord(request, token, entity, cycleId), entity.field)), `${entity.label} redo re-applies update (I6)`).toBe(JSON.stringify(afterUpdateValue))\n\n const deleteRes = await deleteEntity(request, token, entity, cycleId)\n expect(deleteRes.ok(), `${entity.label} delete status ${deleteRes.status()}`).toBeTruthy()\n const deleteOp = expectOperation(deleteRes, `${entity.label}.delete`)\n expect(await readRecord(request, token, entity, cycleId), `${entity.label} deleted record should not read`).toBeNull()\n\n await undoOk(request, token, deleteOp.undoToken, `${entity.label} undo delete`)\n expect(fieldValue(await readRecord(request, token, entity, cycleId), entity.field), `${entity.label} delete\u2192undo re-materializes (I2)`).toBeDefined()\n } finally {\n if (createUndoId) await deleteEntity(request, token, entity, createUndoId).catch(() => {})\n if (cycleId) await deleteEntity(request, token, entity, cycleId).catch(() => {})\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { type APIRequestContext, type APIResponse, expect, test } from '@playwright/test'\nimport { randomInt } from 'node:crypto'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { apiRequest } from './api'\nimport { expectId, readJsonSafe } from './generalFixtures'\n\n/**\n * Shared harness for verifying Undo/Redo correctness against the real command bus.\n *\n * Every mutating Open Mercato API response carries the operation metadata in the\n * `x-om-operation` header (`omop:<urlencoded JSON>`) containing the `undoToken` and the\n * audit log `id` (used as `logId` for redo). These helpers extract that envelope and drive\n * the real undo/redo endpoints so tests can assert full state restoration per TC-UNDO-001.\n */\n\nconst HEADER_PREFIX = 'omop:'\nconst UNDO_PATH = '/api/audit_logs/audit-logs/actions/undo'\nconst REDO_PATH = '/api/audit_logs/audit-logs/actions/redo'\nconst ACTIONS_PATH = '/api/audit_logs/audit-logs/actions'\nexport const UNDO_TESTS_DISABLED_ENV = 'OM_INTEGRATION_UNDO_TESTS_DISABLED'\n\nexport type Operation = {\n logId: string\n undoToken: string\n commandId: string\n resourceKind: string | null\n resourceId: string | null\n}\n\nexport type CrudUndoEntityConfig = {\n label: string\n collectionPath: string\n field: string\n createPayload: (stamp: string) => Record<string, unknown>\n updatePayload: (id: string, stamp: string) => Record<string, unknown>\n readPath?: (id: string) => string\n deletePath?: (id: string) => string\n createStatus?: number\n updateStatus?: number\n}\n\nexport function undoTestsDisabled(env: NodeJS.ProcessEnv = process.env): boolean {\n return parseBooleanWithDefault(env[UNDO_TESTS_DISABLED_ENV], false)\n}\n\nexport function skipIfUndoTestsDisabled(): void {\n test.skip(undoTestsDisabled(), `${UNDO_TESTS_DISABLED_ENV} is set \u2014 undo/redo integration tests skipped`)\n}\n\n/** Parse the `x-om-operation` header into a structured operation, or null when absent/malformed. */\nexport function extractOperation(response: APIResponse): Operation | null {\n const header = response.headers()['x-om-operation']\n if (!header || typeof header !== 'string') return null\n const trimmed = header.startsWith(HEADER_PREFIX) ? header.slice(HEADER_PREFIX.length) : header\n try {\n const parsed = JSON.parse(decodeURIComponent(trimmed)) as Record<string, unknown>\n if (typeof parsed.id !== 'string' || typeof parsed.commandId !== 'string') return null\n if (typeof parsed.undoToken !== 'string' || !parsed.undoToken) return null\n return {\n logId: parsed.id,\n undoToken: parsed.undoToken,\n commandId: parsed.commandId,\n resourceKind: (parsed.resourceKind as string) ?? null,\n resourceId: (parsed.resourceId as string) ?? null,\n }\n } catch {\n return null\n }\n}\n\n/** Like extractOperation but fails the test if no undo token was issued. */\nexport function expectOperation(response: APIResponse, context: string): Operation {\n const op = extractOperation(response)\n expect(op, `Expected an undo token (x-om-operation header) for ${context}, got none`).toBeTruthy()\n return op as Operation\n}\n\nexport async function undoByToken(request: APIRequestContext, token: string, undoToken: string): Promise<APIResponse> {\n return apiRequest(request, 'POST', UNDO_PATH, { token, data: { undoToken } })\n}\n\nexport async function redoByLogId(request: APIRequestContext, token: string, logId: string): Promise<APIResponse> {\n return apiRequest(request, 'POST', REDO_PATH, { token, data: { logId } })\n}\n\n/** Undo and assert success; returns the resolved logId. */\nexport async function undoOk(request: APIRequestContext, token: string, undoToken: string, context: string): Promise<string> {\n const res = await undoByToken(request, token, undoToken)\n const body = (await res.json().catch(() => null)) as { ok?: boolean; logId?: string } | null\n expect(res.ok(), `Undo failed for ${context}: status ${res.status()} body ${JSON.stringify(body)}`).toBeTruthy()\n expect(body?.ok, `Undo not ok for ${context}: ${JSON.stringify(body)}`).toBeTruthy()\n return body?.logId as string\n}\n\n/** Redo and assert success; returns the new operation (new undoToken + logId). */\nexport async function redoOk(request: APIRequestContext, token: string, logId: string, context: string): Promise<{ logId: string; undoToken: string | null }> {\n const res = await redoByLogId(request, token, logId)\n const body = (await res.json().catch(() => null)) as { ok?: boolean; logId?: string; undoToken?: string } | null\n expect(res.ok(), `Redo failed for ${context}: status ${res.status()} body ${JSON.stringify(body)}`).toBeTruthy()\n expect(body?.ok, `Redo not ok for ${context}: ${JSON.stringify(body)}`).toBeTruthy()\n return { logId: body?.logId as string, undoToken: body?.undoToken ?? null }\n}\n\n/** Assert that undoing an already-consumed token is rejected (token consumption / no double-undo). */\nexport async function expectTokenConsumed(request: APIRequestContext, token: string, undoToken: string, context: string): Promise<void> {\n const res = await undoByToken(request, token, undoToken)\n expect(res.ok(), `Expected double-undo to be rejected for ${context}, but it succeeded`).toBeFalsy()\n}\n\n/** Fetch undoable actions list (for Version History assertions). */\nexport async function listUndoable(request: APIRequestContext, token: string, params: Record<string, string> = {}): Promise<unknown> {\n const qs = new URLSearchParams({ undoableOnly: 'true', ...params }).toString()\n const res = await apiRequest(request, 'GET', `${ACTIONS_PATH}?${qs}`, { token })\n return res.json().catch(() => null)\n}\n\n/**\n * Deep-equality assertion for a selected set of fields between two entity snapshots.\n * Reports the first mismatching field with context for clear bug triage.\n */\nexport function assertFieldsEqual(\n actual: Record<string, unknown> | null | undefined,\n expected: Record<string, unknown> | null | undefined,\n fields: string[],\n context: string,\n): void {\n expect(actual, `${context}: actual entity missing`).toBeTruthy()\n expect(expected, `${context}: expected entity missing`).toBeTruthy()\n for (const field of fields) {\n expect(\n JSON.stringify((actual as Record<string, unknown>)[field]),\n `${context}: field \"${field}\" not restored (expected ${JSON.stringify((expected as Record<string, unknown>)[field])}, got ${JSON.stringify((actual as Record<string, unknown>)[field])})`,\n ).toBe(JSON.stringify((expected as Record<string, unknown>)[field]))\n }\n}\n\nfunction findRecord(body: unknown, id: string): Record<string, unknown> | null {\n if (!body || typeof body !== 'object') return null\n if (!Array.isArray(body) && (body as Record<string, unknown>).id === id) {\n return body as Record<string, unknown>\n }\n for (const value of Array.isArray(body) ? body : Object.values(body)) {\n const found = findRecord(value, id)\n if (found) return found\n }\n return null\n}\n\nasync function readRecord(\n request: APIRequestContext,\n token: string,\n entity: CrudUndoEntityConfig,\n id: string,\n): Promise<Record<string, unknown> | null> {\n const path = entity.readPath?.(id) ?? `${entity.collectionPath}?id=${encodeURIComponent(id)}`\n const response = await apiRequest(request, 'GET', path, { token })\n const body = await readJsonSafe(response)\n if (!response.ok()) return null\n return findRecord(body, id)\n}\n\nfunction fieldValue(record: Record<string, unknown> | null, field: string): unknown {\n return record?.[field]\n}\n\nasync function deleteEntity(\n request: APIRequestContext,\n token: string,\n entity: CrudUndoEntityConfig,\n id: string,\n): Promise<APIResponse> {\n const path = entity.deletePath?.(id) ?? `${entity.collectionPath}?id=${encodeURIComponent(id)}`\n return apiRequest(request, 'DELETE', path, { token })\n}\n\nexport async function runCrudUndoRoundTrip(\n request: APIRequestContext,\n token: string,\n entity: CrudUndoEntityConfig,\n): Promise<void> {\n const stamp = `${Date.now()}${randomInt(1000)}`\n let createUndoId: string | null = null\n let cycleId: string | null = null\n\n try {\n const createUndoRes = await apiRequest(request, 'POST', entity.collectionPath, {\n token,\n data: entity.createPayload(`${stamp}a`),\n })\n expect(createUndoRes.status(), `${entity.label} create-for-undo status`).toBe(entity.createStatus ?? 201)\n const createUndoOp = expectOperation(createUndoRes, `${entity.label}.create`)\n createUndoId = createUndoOp.resourceId || expectId((await readJsonSafe<Record<string, unknown>>(createUndoRes))?.id, `${entity.label} create id`)\n expect(fieldValue(await readRecord(request, token, entity, createUndoId), entity.field), `${entity.label} field readable after create`).toBeDefined()\n\n await undoOk(request, token, createUndoOp.undoToken, `${entity.label} undo create`)\n expect(await readRecord(request, token, entity, createUndoId), `${entity.label} create\u2192undo soft-deletes/removes the record (I3)`).toBeNull()\n await expectTokenConsumed(request, token, createUndoOp.undoToken, `${entity.label} create token consumed (I5)`)\n\n const createRes = await apiRequest(request, 'POST', entity.collectionPath, {\n token,\n data: entity.createPayload(`${stamp}b`),\n })\n expect(createRes.status(), `${entity.label} create status`).toBe(entity.createStatus ?? 201)\n const createOp = expectOperation(createRes, `${entity.label}.create`)\n cycleId = createOp.resourceId || expectId((await readJsonSafe<Record<string, unknown>>(createRes))?.id, `${entity.label} cycle id`)\n\n const beforeUpdate = await readRecord(request, token, entity, cycleId)\n const beforeValue = fieldValue(beforeUpdate, entity.field)\n expect(beforeValue, `${entity.label} field readable before update`).toBeDefined()\n\n const updateRes = await apiRequest(request, 'PUT', entity.collectionPath, {\n token,\n data: entity.updatePayload(cycleId, stamp),\n })\n expect(updateRes.status(), `${entity.label} update status`).toBe(entity.updateStatus ?? 200)\n const updateOp = expectOperation(updateRes, `${entity.label}.update`)\n const afterUpdate = await readRecord(request, token, entity, cycleId)\n const afterUpdateValue = fieldValue(afterUpdate, entity.field)\n expect(JSON.stringify(afterUpdateValue), `${entity.label} field changed by update`).not.toBe(JSON.stringify(beforeValue))\n\n await new Promise((resolve) => setTimeout(resolve, 10))\n await undoOk(request, token, updateOp.undoToken, `${entity.label} undo update`)\n const afterUndo = await readRecord(request, token, entity, cycleId)\n expect(JSON.stringify(fieldValue(afterUndo, entity.field)), `${entity.label} update\u2192undo restores ${entity.field} (I1)`).toBe(JSON.stringify(beforeValue))\n if (typeof beforeUpdate?.updatedAt === 'string' && typeof afterUndo?.updatedAt === 'string') {\n expect(afterUndo.updatedAt, `${entity.label} undo bumps updatedAt`).not.toBe(beforeUpdate.updatedAt)\n }\n\n await redoOk(request, token, updateOp.logId, `${entity.label} redo update`)\n expect(JSON.stringify(fieldValue(await readRecord(request, token, entity, cycleId), entity.field)), `${entity.label} redo re-applies update (I6)`).toBe(JSON.stringify(afterUpdateValue))\n\n const deleteRes = await deleteEntity(request, token, entity, cycleId)\n expect(deleteRes.ok(), `${entity.label} delete status ${deleteRes.status()}`).toBeTruthy()\n const deleteOp = expectOperation(deleteRes, `${entity.label}.delete`)\n expect(await readRecord(request, token, entity, cycleId), `${entity.label} deleted record should not read`).toBeNull()\n\n await undoOk(request, token, deleteOp.undoToken, `${entity.label} undo delete`)\n expect(fieldValue(await readRecord(request, token, entity, cycleId), entity.field), `${entity.label} delete\u2192undo re-materializes (I2)`).toBeDefined()\n } finally {\n if (createUndoId) await deleteEntity(request, token, entity, createUndoId).catch(() => {})\n if (cycleId) await deleteEntity(request, token, entity, cycleId).catch(() => {})\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.5-develop.
|
|
3
|
+
"version": "0.6.5-develop.4718.1.56d834bb34",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -245,16 +245,16 @@
|
|
|
245
245
|
"zod": "^4.4.3"
|
|
246
246
|
},
|
|
247
247
|
"peerDependencies": {
|
|
248
|
-
"@open-mercato/ai-assistant": "0.6.5-develop.
|
|
249
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
250
|
-
"@open-mercato/ui": "0.6.5-develop.
|
|
248
|
+
"@open-mercato/ai-assistant": "0.6.5-develop.4718.1.56d834bb34",
|
|
249
|
+
"@open-mercato/shared": "0.6.5-develop.4718.1.56d834bb34",
|
|
250
|
+
"@open-mercato/ui": "0.6.5-develop.4718.1.56d834bb34",
|
|
251
251
|
"react": "^19.0.0",
|
|
252
252
|
"react-dom": "^19.0.0"
|
|
253
253
|
},
|
|
254
254
|
"devDependencies": {
|
|
255
|
-
"@open-mercato/ai-assistant": "0.6.5-develop.
|
|
256
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
257
|
-
"@open-mercato/ui": "0.6.5-develop.
|
|
255
|
+
"@open-mercato/ai-assistant": "0.6.5-develop.4718.1.56d834bb34",
|
|
256
|
+
"@open-mercato/shared": "0.6.5-develop.4718.1.56d834bb34",
|
|
257
|
+
"@open-mercato/ui": "0.6.5-develop.4718.1.56d834bb34",
|
|
258
258
|
"@testing-library/dom": "^10.4.1",
|
|
259
259
|
"@testing-library/jest-dom": "^6.9.1",
|
|
260
260
|
"@testing-library/react": "^16.3.1",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expect, type APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { randomInt } from 'node:crypto';
|
|
2
3
|
import { apiRequest } from './api';
|
|
3
4
|
import { getTokenContext } from './generalFixtures';
|
|
4
5
|
|
|
@@ -28,7 +29,7 @@ const reservedCurrencyCodes = new Set<string>();
|
|
|
28
29
|
|
|
29
30
|
/** Draws an ISO-style three-letter code unused by seeds or earlier fixtures. */
|
|
30
31
|
export function generateUniqueCurrencyCode(): string {
|
|
31
|
-
const letter = () => String.fromCharCode(65 +
|
|
32
|
+
const letter = () => String.fromCharCode(65 + randomInt(26));
|
|
32
33
|
for (let attempt = 0; attempt < 200; attempt += 1) {
|
|
33
34
|
const code = `${letter()}${letter()}${letter()}`;
|
|
34
35
|
if (!SEEDED_CURRENCY_CODES.has(code) && !reservedCurrencyCodes.has(code)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type APIRequestContext, type APIResponse, expect, test } from '@playwright/test'
|
|
2
|
+
import { randomInt } from 'node:crypto'
|
|
2
3
|
import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
3
4
|
import { apiRequest } from './api'
|
|
4
5
|
import { expectId, readJsonSafe } from './generalFixtures'
|
|
@@ -177,7 +178,7 @@ export async function runCrudUndoRoundTrip(
|
|
|
177
178
|
token: string,
|
|
178
179
|
entity: CrudUndoEntityConfig,
|
|
179
180
|
): Promise<void> {
|
|
180
|
-
const stamp = `${Date.now()}${
|
|
181
|
+
const stamp = `${Date.now()}${randomInt(1000)}`
|
|
181
182
|
let createUndoId: string | null = null
|
|
182
183
|
let cycleId: string | null = null
|
|
183
184
|
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"exchangeRates.flash.updated": "Wechselkurs erfolgreich aktualisiert",
|
|
96
96
|
"exchangeRates.form.action.create": "Wechselkurs erstellen",
|
|
97
97
|
"exchangeRates.form.action.save": "Änderungen speichern",
|
|
98
|
+
"exchangeRates.form.actions.backToList": "Zurück zu Wechselkursen",
|
|
98
99
|
"exchangeRates.form.errors.currencyCodeFormat": "Muss genau 3 Großbuchstaben sein",
|
|
99
100
|
"exchangeRates.form.errors.fromCurrencyFormat": "Ungültiger Von-Währungscode",
|
|
100
101
|
"exchangeRates.form.errors.invalidDate": "Datum und Uhrzeit sind erforderlich",
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"exchangeRates.flash.updated": "Exchange rate updated successfully",
|
|
96
96
|
"exchangeRates.form.action.create": "Create Exchange Rate",
|
|
97
97
|
"exchangeRates.form.action.save": "Save Changes",
|
|
98
|
+
"exchangeRates.form.actions.backToList": "Back to exchange rates",
|
|
98
99
|
"exchangeRates.form.errors.currencyCodeFormat": "Must be exactly 3 uppercase letters",
|
|
99
100
|
"exchangeRates.form.errors.fromCurrencyFormat": "Invalid from currency code",
|
|
100
101
|
"exchangeRates.form.errors.invalidDate": "Date and time is required",
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"exchangeRates.flash.updated": "Tipo de cambio actualizado correctamente",
|
|
96
96
|
"exchangeRates.form.action.create": "Crear Tipo de Cambio",
|
|
97
97
|
"exchangeRates.form.action.save": "Guardar Cambios",
|
|
98
|
+
"exchangeRates.form.actions.backToList": "Volver a tipos de cambio",
|
|
98
99
|
"exchangeRates.form.errors.currencyCodeFormat": "Debe ser exactamente 3 letras mayúsculas",
|
|
99
100
|
"exchangeRates.form.errors.fromCurrencyFormat": "Código de moneda de origen inválido",
|
|
100
101
|
"exchangeRates.form.errors.invalidDate": "La fecha y hora son requeridas",
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"exchangeRates.flash.updated": "Kurs wymiany zaktualizowany pomyślnie",
|
|
96
96
|
"exchangeRates.form.action.create": "Utwórz Kurs Wymiany",
|
|
97
97
|
"exchangeRates.form.action.save": "Zapisz Zmiany",
|
|
98
|
+
"exchangeRates.form.actions.backToList": "Wróć do kursów walut",
|
|
98
99
|
"exchangeRates.form.errors.currencyCodeFormat": "Musi składać się z dokładnie 3 wielkich liter",
|
|
99
100
|
"exchangeRates.form.errors.fromCurrencyFormat": "Nieprawidłowy kod waluty źródłowej",
|
|
100
101
|
"exchangeRates.form.errors.invalidDate": "Data i czas są wymagane",
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"data_sync.runs.detail.logs.message": "Nachricht",
|
|
111
111
|
"data_sync.runs.detail.logs.time": "Zeit",
|
|
112
112
|
"data_sync.runs.detail.noLogs": "Keine Protokolleinträge",
|
|
113
|
+
"data_sync.runs.detail.notFound": "Synchronisierungslauf nicht gefunden.",
|
|
113
114
|
"data_sync.runs.detail.progress": "Fortschritt",
|
|
114
115
|
"data_sync.runs.detail.progress.batches": "{count} Stapel",
|
|
115
116
|
"data_sync.runs.detail.progress.eta": "{eta} verbleibend",
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"data_sync.runs.detail.logs.message": "Message",
|
|
111
111
|
"data_sync.runs.detail.logs.time": "Time",
|
|
112
112
|
"data_sync.runs.detail.noLogs": "No log entries",
|
|
113
|
+
"data_sync.runs.detail.notFound": "Sync run not found.",
|
|
113
114
|
"data_sync.runs.detail.progress": "Progress",
|
|
114
115
|
"data_sync.runs.detail.progress.batches": "{count} batches",
|
|
115
116
|
"data_sync.runs.detail.progress.eta": "{eta} remaining",
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"data_sync.runs.detail.logs.message": "Mensaje",
|
|
111
111
|
"data_sync.runs.detail.logs.time": "Hora",
|
|
112
112
|
"data_sync.runs.detail.noLogs": "Sin entradas de registro",
|
|
113
|
+
"data_sync.runs.detail.notFound": "Ejecución de sincronización no encontrada.",
|
|
113
114
|
"data_sync.runs.detail.progress": "Progreso",
|
|
114
115
|
"data_sync.runs.detail.progress.batches": "{count} lotes",
|
|
115
116
|
"data_sync.runs.detail.progress.eta": "Quedan {eta}",
|
|
@@ -110,6 +110,7 @@
|
|
|
110
110
|
"data_sync.runs.detail.logs.message": "Wiadomość",
|
|
111
111
|
"data_sync.runs.detail.logs.time": "Czas",
|
|
112
112
|
"data_sync.runs.detail.noLogs": "Brak wpisów w logach",
|
|
113
|
+
"data_sync.runs.detail.notFound": "Nie znaleziono przebiegu synchronizacji.",
|
|
113
114
|
"data_sync.runs.detail.progress": "Postęp",
|
|
114
115
|
"data_sync.runs.detail.progress.batches": "{count} partii",
|
|
115
116
|
"data_sync.runs.detail.progress.eta": "Pozostało {eta}",
|
|
@@ -155,6 +155,8 @@
|
|
|
155
155
|
"inbox_ops.preview.sku": "SKU",
|
|
156
156
|
"inbox_ops.processing_log": "Verarbeitungsprotokoll",
|
|
157
157
|
"inbox_ops.proposal": "Vorschlag",
|
|
158
|
+
"inbox_ops.proposal.backToList": "Zurück zum Posteingang",
|
|
159
|
+
"inbox_ops.proposal.notFound": "Vorschlag nicht gefunden.",
|
|
158
160
|
"inbox_ops.recategorize": "Kategorie \u00e4ndern",
|
|
159
161
|
"inbox_ops.received_at": "Empfangen",
|
|
160
162
|
"inbox_ops.reply.send": "Antwort senden",
|
|
@@ -155,6 +155,8 @@
|
|
|
155
155
|
"inbox_ops.preview.sku": "SKU",
|
|
156
156
|
"inbox_ops.processing_log": "Processing Log",
|
|
157
157
|
"inbox_ops.proposal": "Proposal",
|
|
158
|
+
"inbox_ops.proposal.backToList": "Back to inbox",
|
|
159
|
+
"inbox_ops.proposal.notFound": "Proposal not found.",
|
|
158
160
|
"inbox_ops.recategorize": "Change Category",
|
|
159
161
|
"inbox_ops.received_at": "Received",
|
|
160
162
|
"inbox_ops.reply.send": "Send Reply",
|
|
@@ -155,6 +155,8 @@
|
|
|
155
155
|
"inbox_ops.preview.sku": "SKU",
|
|
156
156
|
"inbox_ops.processing_log": "Registro de procesamiento",
|
|
157
157
|
"inbox_ops.proposal": "Propuesta",
|
|
158
|
+
"inbox_ops.proposal.backToList": "Volver a la bandeja de entrada",
|
|
159
|
+
"inbox_ops.proposal.notFound": "Propuesta no encontrada.",
|
|
158
160
|
"inbox_ops.recategorize": "Cambiar categor\u00eda",
|
|
159
161
|
"inbox_ops.received_at": "Recibido",
|
|
160
162
|
"inbox_ops.reply.send": "Enviar respuesta",
|
|
@@ -155,6 +155,8 @@
|
|
|
155
155
|
"inbox_ops.preview.sku": "SKU",
|
|
156
156
|
"inbox_ops.processing_log": "Dziennik przetwarzania",
|
|
157
157
|
"inbox_ops.proposal": "Propozycja",
|
|
158
|
+
"inbox_ops.proposal.backToList": "Wróć do skrzynki",
|
|
159
|
+
"inbox_ops.proposal.notFound": "Nie znaleziono propozycji.",
|
|
158
160
|
"inbox_ops.recategorize": "Zmie\u0144 kategori\u0119",
|
|
159
161
|
"inbox_ops.received_at": "Odebrano",
|
|
160
162
|
"inbox_ops.reply.send": "Wy\u015blij odpowied\u017a",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"integrations.detail.analytics.title": "Nutzung (30 Tage)",
|
|
86
86
|
"integrations.detail.analytics.totalEvents": "{count} Log-Ereignisse",
|
|
87
87
|
"integrations.detail.back": "Zurück zu Integrationen",
|
|
88
|
+
"integrations.detail.backToList": "Zurück zu Integrationen",
|
|
88
89
|
"integrations.detail.credentials.bundleShared": "Gemeinsame Zugangsdaten aus Paket: {bundle}",
|
|
89
90
|
"integrations.detail.credentials.notConfigured": "Noch keine Zugangsdaten konfiguriert",
|
|
90
91
|
"integrations.detail.credentials.save": "Zugangsdaten speichern",
|
|
@@ -126,6 +127,7 @@
|
|
|
126
127
|
"integrations.detail.logs.level.info": "Info",
|
|
127
128
|
"integrations.detail.logs.level.warn": "Warnung",
|
|
128
129
|
"integrations.detail.logs.title": "Betriebsprotokolle",
|
|
130
|
+
"integrations.detail.notFound": "Integration nicht gefunden.",
|
|
129
131
|
"integrations.detail.runActivity.lastRefreshed": "Zuletzt aktualisiert",
|
|
130
132
|
"integrations.detail.runActivity.processed": "Verarbeitet",
|
|
131
133
|
"integrations.detail.runActivity.progress": "Fortschritt",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"integrations.detail.analytics.title": "Usage (30 days)",
|
|
86
86
|
"integrations.detail.analytics.totalEvents": "{count} log events",
|
|
87
87
|
"integrations.detail.back": "Back to Integrations",
|
|
88
|
+
"integrations.detail.backToList": "Back to integrations",
|
|
88
89
|
"integrations.detail.credentials.bundleShared": "Shared credentials from bundle: {bundle}",
|
|
89
90
|
"integrations.detail.credentials.notConfigured": "No credentials configured yet",
|
|
90
91
|
"integrations.detail.credentials.save": "Save Credentials",
|
|
@@ -126,6 +127,7 @@
|
|
|
126
127
|
"integrations.detail.logs.level.info": "Info",
|
|
127
128
|
"integrations.detail.logs.level.warn": "Warning",
|
|
128
129
|
"integrations.detail.logs.title": "Operation Logs",
|
|
130
|
+
"integrations.detail.notFound": "Integration not found.",
|
|
129
131
|
"integrations.detail.runActivity.lastRefreshed": "Last refreshed",
|
|
130
132
|
"integrations.detail.runActivity.processed": "Processed",
|
|
131
133
|
"integrations.detail.runActivity.progress": "Progress",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"integrations.detail.analytics.title": "Uso (30 días)",
|
|
86
86
|
"integrations.detail.analytics.totalEvents": "{count} eventos de registro",
|
|
87
87
|
"integrations.detail.back": "Volver a integraciones",
|
|
88
|
+
"integrations.detail.backToList": "Volver a integraciones",
|
|
88
89
|
"integrations.detail.credentials.bundleShared": "Credenciales compartidas del paquete: {bundle}",
|
|
89
90
|
"integrations.detail.credentials.notConfigured": "No hay credenciales configuradas",
|
|
90
91
|
"integrations.detail.credentials.save": "Guardar credenciales",
|
|
@@ -126,6 +127,7 @@
|
|
|
126
127
|
"integrations.detail.logs.level.info": "Info",
|
|
127
128
|
"integrations.detail.logs.level.warn": "Advertencia",
|
|
128
129
|
"integrations.detail.logs.title": "Registros de operación",
|
|
130
|
+
"integrations.detail.notFound": "Integración no encontrada.",
|
|
129
131
|
"integrations.detail.runActivity.lastRefreshed": "Última actualización",
|
|
130
132
|
"integrations.detail.runActivity.processed": "Procesados",
|
|
131
133
|
"integrations.detail.runActivity.progress": "Progreso",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"integrations.detail.analytics.title": "Użycie (30 dni)",
|
|
86
86
|
"integrations.detail.analytics.totalEvents": "{count} wpisów dziennika",
|
|
87
87
|
"integrations.detail.back": "Powrót do integracji",
|
|
88
|
+
"integrations.detail.backToList": "Wróć do integracji",
|
|
88
89
|
"integrations.detail.credentials.bundleShared": "Współdzielone dane z pakietu: {bundle}",
|
|
89
90
|
"integrations.detail.credentials.notConfigured": "Brak skonfigurowanych danych",
|
|
90
91
|
"integrations.detail.credentials.save": "Zapisz dane",
|
|
@@ -126,6 +127,7 @@
|
|
|
126
127
|
"integrations.detail.logs.level.info": "Info",
|
|
127
128
|
"integrations.detail.logs.level.warn": "Ostrzeżenie",
|
|
128
129
|
"integrations.detail.logs.title": "Logi operacji",
|
|
130
|
+
"integrations.detail.notFound": "Nie znaleziono integracji.",
|
|
129
131
|
"integrations.detail.runActivity.lastRefreshed": "Ostatnio odświeżono",
|
|
130
132
|
"integrations.detail.runActivity.processed": "Przetworzone",
|
|
131
133
|
"integrations.detail.runActivity.progress": "Postęp",
|