Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions packages/services/service-settings/src/settings-getmany.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,10 +55,19 @@ const MANIFEST = {
],
} as any;

// [#12172] Values are stored DECODED, exactly as the persist path writes them
// and as a real driver hands them back — not as JSON text. `sys_setting.value`
// is a `Field.json` column, but the SERVICE is verbatim in both directions
// (`setMany` writes `storedValue = rawValue`; `materialiseRow`'s non-encrypted
// branch is `return row.value ?? null`), so the DRIVER owns the codec and the
// round trip lands back on the raw value on both dialects. The fake engine
// below skips that codec and returns rows exactly as written, so anything
// spelled '"America/New_York"' here would resolve one JSON encoding deep.
// (Matches the sibling fixture in `settings-loadrows-scope.test.ts`.)
const ROWS = [
{ namespace: 'localization', key: 'timezone', scope: 'global', value: '"America/New_York"', user_id: null },
{ namespace: 'localization', key: 'locale', scope: 'user', value: '"zh-CN"', user_id: 'u1' },
{ namespace: 'localization', key: 'currency', scope: 'tenant', value: '"USD"', user_id: null },
{ namespace: 'localization', key: 'timezone', scope: 'global', value: 'America/New_York', user_id: null },
{ namespace: 'localization', key: 'locale', scope: 'user', value: 'zh-CN', user_id: 'u1' },
{ namespace: 'localization', key: 'currency', scope: 'tenant', value: 'USD', user_id: null },
];

async function makeService(rows = ROWS) {
Expand DownExpand Up@@ -147,9 +156,13 @@ describe('[#10826] SettingsService.getMany', () => {

// ...and here is the non-equivalence: per-key `get()` still answers every
// declared key on the same input; only the undeclared one throws. Asserted
// on the resolved cascade LAYER, not on the literal — this fixture stores
// JSON text in `value` while the service persists values verbatim, so a
// literal here would pin the fixture's encoding rather than the rule.
// on the resolved cascade LAYER because the LAYER is this test's subject —
// the rule under test is "which keys still answer", not what they answer.
// [#12172] This once read as a workaround: the fixture stored JSON text in
// `value`, so a literal here would have pinned the fixture's encoding
// rather than the rule. The fixture now stores decoded values and the
// encoding has its own pin at the bottom of this file; the assertions on
// this line are unchanged, only the reason for their shape is.
expect((await svc.get('localization', 'timezone')).source).toBe('global');
expect((await svc.get('localization', 'currency')).source).toBe('tenant');
await expect(svc.get('localization', 'nope')).rejects.toBeInstanceOf(UnknownKeyError);
Expand All@@ -166,4 +179,39 @@ describe('[#10826] SettingsService.getMany', () => {
await svc.getNamespace('localization', ctx);
expect(engine.find.mock.calls.length).toBeLessThanOrEqual(2);
});

// [#12172] The ENCODING pin — the one assertion in this file that reads a
// LITERAL resolved value.
//
// Every other assertion here compares `getMany` against `get`, so both sides
// move together and a fixture stored one JSON encoding deep stays invisible:
// the same "a fake that lies consistently" shape this file's header already
// warns about for `$or` matchers. That insensitivity is BY CONSTRUCTION, so
// "the suite is green" proves nothing about the encoding — only a literal can.
//
// Why the literal is the decoded string and not JSON text: `sys_setting.value`
// is a `Field.json` column, and the SERVICE is verbatim in both directions
// (`setMany` writes `storedValue = rawValue`; `materialiseRow`'s non-encrypted
// branch is `return row.value ?? null`). The DRIVER owns the JSON codec and it
// round-trips to the RAW value on both dialects — SQLite stores primitives
// as-is and re-parses on read, Postgres stringifies into the `jsonb` column and
// the client parses it back. So a row handed back by a real driver carries
// 'America/New_York', never '"America/New_York"'. The fake engine skips that
// codec, which is why the fixture must already hold the decoded value.
it('[#12172] resolves the LITERAL stored value — no doubled JSON encoding', async () => {
const { svc } = await makeService();
const ctx = { userId: 'u1', tenantId: 't1' };
const many = await svc.getMany('localization', ['timezone', 'locale', 'currency'], ctx);

// Global row, user-scope key, no user/tenant override.
expect(many.timezone.value).toBe('America/New_York');
// User row for u1.
expect(many.locale.value).toBe('zh-CN');
// Tenant row.
expect(many.currency.value).toBe('USD');

// ...and the per-key path agrees on the same literal, so the pin holds the
// ENCODING rather than a `getMany`-only quirk.
expect((await svc.get('localization', 'timezone', ctx)).value).toBe('America/New_York');
});
});
Loading