Skip to content

Commit a4ba9d5

Browse files
committed
test(plugin-auth): the SMS test doubles honour the engine-double and where-matcher contracts
The boot harness routes findOne and update through ObjectQL's own dispatch predicates (ledger recorded by check-engine-double-contract --write), and both row stores refuse a where combinator instead of reading it as a field name. Claude-Session: https://claude.ai/code/session_01WMQprn46CND82KmY8sZWBu Co-authored-by: Claude <noreply@anthropic.com>
1 parent 362f6c4 commit a4ba9d5

3 files changed

Lines changed: 45 additions & 6 deletions

File tree

‎packages/plugins/plugin-auth/src/phone-sms-seed-retired.test.ts‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import { describe, it, expect, vi } from 'vitest';
2323
import type { PluginContext } from '@objectstack/core';
24+
import { assertEngineFindOnePredicate, assertEngineUpdateDispatch } from '@objectstack/objectql';
2425
import { AuthPlugin } from './auth-plugin';
2526
import {
2627
BUILTIN_PHONE_SMS_TEMPLATES,
@@ -37,13 +38,26 @@ const SECRET = 'test-secret-at-least-32-chars-long';
3738

3839
type Row = Record<string, unknown>;
3940

40-
/** A row store answering `find` by every key in `where`, honouring `limit`. */
41+
/**
42+
* A row store answering `find` by every key in `where` (plain equality),
43+
* honouring `limit`. It REFUSES a combinator it does not implement rather than
44+
* reading `$and` / `$or` as a field name (`check:where-matcher`).
45+
*/
46+
function matchesWhere(row: Row, where: Record<string, unknown> = {}): boolean {
47+
return Object.entries(where).every(([k, v]) => {
48+
if (k.startsWith('$') || (v !== null && typeof v === 'object')) {
49+
throw new Error(`store(): unsupported where clause on '${k}' — this double implements plain equality only`);
50+
}
51+
return row[k] === v;
52+
});
53+
}
54+
4155
function store(rows: Row[]) {
4256
const all = rows.map((r) => ({ ...r }));
4357
return {
4458
all,
4559
async find(_object: string, q: any) {
46-
const hit = all.filter((r) => Object.entries(q?.where ?? {}).every(([k, v]) => r[k] === v));
60+
const hit = all.filter((r) => matchesWhere(r, q?.where));
4761
return typeof q?.limit === 'number' ? hit.slice(0, q.limit) : hit;
4862
},
4963
async insert(_object: string, row: Row) { all.push({ ...row }); return row; },
@@ -181,8 +195,14 @@ describe('a fresh boot with phone sign-in on', () => {
181195
unregisterHooksByPackage: vi.fn(() => 0),
182196
count: vi.fn(async () => 0),
183197
find: vi.fn(async () => []),
184-
findOne: vi.fn(async () => null),
185-
update: vi.fn(async () => ({})),
198+
findOne: vi.fn(async (object: string, query: unknown = {}) => {
199+
assertEngineFindOnePredicate(object, query as never);
200+
return null;
201+
}),
202+
update: vi.fn(async (_object: string, doc: Record<string, unknown>, options?: unknown) => {
203+
assertEngineUpdateDispatch(doc, options as never);
204+
return {};
205+
}),
186206
insert: vi.fn(async (object: string, row: Row) => { inserted.push({ object, row }); return row; }),
187207
};
188208
const hooks: Array<() => Promise<void>> = [];

‎packages/plugins/plugin-auth/src/phone-sms-texts.test.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,19 @@ describe('interpolatePhoneSms', () => {
5151
});
5252

5353
describe('resolvePhoneSmsTemplateBody', () => {
54-
/** Rows answered as the engine answers `find`: by every key in `where`. */
54+
/**
55+
* Rows answered as the engine answers `find`: by every key in `where`, plain
56+
* equality only. A combinator is REFUSED, never read as a field name
57+
* (`check:where-matcher`).
58+
*/
5559
const engineWith = (rows: Array<Record<string, unknown>>) => ({
5660
find: vi.fn(async (_obj: string, q: any) =>
57-
rows.filter((r) => Object.entries(q.where).every(([k, v]) => r[k] === v)).slice(0, q.limit ?? Infinity),
61+
rows.filter((r) => Object.entries(q.where).every(([k, v]) => {
62+
if (k.startsWith('$') || (v !== null && typeof v === 'object')) {
63+
throw new Error(`engineWith(): unsupported where clause on '${k}'`);
64+
}
65+
return r[k] === v;
66+
})).slice(0, q.limit ?? Infinity),
5867
),
5968
});
6069

‎scripts/engine-double-contract.pinned.json‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2891,6 +2891,16 @@
28912891
"verb": "update",
28922892
"pinned": 1
28932893
},
2894+
{
2895+
"file": "packages/plugins/plugin-auth/src/phone-sms-seed-retired.test.ts",
2896+
"verb": "findOne",
2897+
"pinned": 1
2898+
},
2899+
{
2900+
"file": "packages/plugins/plugin-auth/src/phone-sms-seed-retired.test.ts",
2901+
"verb": "update",
2902+
"pinned": 1
2903+
},
28942904
{
28952905
"file": "packages/plugins/plugin-auth/src/remove-member-permission-guard.test.ts",
28962906
"verb": "delete",

0 commit comments

Comments
 (0)