Skip to content

Commit fd24546

Browse files
committed
test(service-settings): pin each half of the user-rung pick on its own
An ownerless user row (the shape a write with no user stored before the refusal) makes the presence check load-bearing; a store that answers more rows than asked for makes the owner comparison load-bearing. Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5597ed9 commit fd24546

1 file changed

Lines changed: 27 additions & 11 deletions

File tree

‎packages/services/service-settings/src/settings-user-rung-owner.pin.test.ts‎

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,14 @@ const MANIFEST = {
4444
],
4545
} as any;
4646

47-
// The service's stored row shape, per rung.
48-
const userRow = (userId: string, value: string): Row => ({
47+
// The service's stored row shape, per rung. A user row with a `null` owner is
48+
// the shape an ownerless write stored before the write half refused it; a
49+
// database may still hold one.
50+
const userRow = (userId: string | null, value: string): Row => ({
4951
namespace: NS, key: 'theme', scope: 'user', user_id: userId, value,
5052
value_enc: null, encrypted: false, locked: false, locked_reason: null,
5153
});
54+
const PEOPLE = [userRow(null, 'ownerless'), userRow(USER_A, 'dark'), userRow(USER_B, 'light')];
5255
const tenantRow = (key: string, value: string): Row => ({
5356
namespace: NS, key, scope: 'tenant', user_id: null, value,
5457
value_enc: null, encrypted: false, locked: false, locked_reason: null,
@@ -74,17 +77,20 @@ function matches(row: Row, where: Row): boolean {
7477
* tenant and user rungs. Every user id resolves in `sys_user`, so the
7578
* service's own user-reference refusal never decides a case here.
7679
*/
77-
function engineStack(seed: Row[]) {
80+
function engineStack(seed: Row[], opts: { overAnswer?: boolean } = {}) {
7881
const stores: Record<string, Row[]> = {
7982
sys_platform_setting: seed
8083
.filter((r) => r.scope === 'global')
8184
.map(({ scope: _scope, user_id: _userId, ...stored }) => ({ ...stored })),
8285
sys_setting: seed.filter((r) => r.scope !== 'global').map((r) => ({ ...r })),
8386
};
84-
const find = vi.fn(async (object: string, opts: any) => {
85-
if (object === 'sys_user') return [{ id: opts?.where?.id }];
86-
const hits = (stores[object] ?? []).filter((r) => matches(r, opts?.where ?? {}));
87-
return typeof opts?.limit === 'number' ? hits.slice(0, opts.limit) : hits;
87+
const find = vi.fn(async (object: string, query: any) => {
88+
if (object === 'sys_user') return [{ id: query?.where?.id }];
89+
// `overAnswer`: a store that hands back every row of the namespace to any
90+
// load, whatever else the predicate asked for.
91+
const where = opts.overAnswer ? { namespace: query?.where?.namespace } : query?.where ?? {};
92+
const hits = (stores[object] ?? []).filter((r) => matches(r, where));
93+
return typeof query?.limit === 'number' ? hits.slice(0, query.limit) : hits;
8894
});
8995
const insert = vi.fn(async (object: string, data: Row) => {
9096
(stores[object] ??= []).push({ ...data });
@@ -150,20 +156,20 @@ describe('the user rung answers only its owner', () => {
150156
describe.each(STACKS)('%s', (_label, stack) => {
151157
describe.each(READ_PATHS)('%s', (_path, read) => {
152158
it.each(NO_USER)('a resolve with no user id never answers a user-scoped row (%s)', async (_c, ctx) => {
153-
const { svc } = stack([userRow(USER_A, 'dark'), userRow(USER_B, 'light')]);
159+
const { svc } = stack(PEOPLE);
154160
expect(await read(svc, ctx)).toBe('system');
155161
});
156162

157163
it('control: a caller with a user id reads only their own row', async () => {
158-
const { svc } = stack([userRow(USER_A, 'dark'), userRow(USER_B, 'light')]);
164+
const { svc } = stack(PEOPLE);
159165
expect(await read(svc, { userId: USER_A })).toBe('dark');
160166
expect(await read(svc, { userId: USER_B })).toBe('light');
161167
expect(await read(svc, { userId: 'usr_owner_c' })).toBe('system');
162168
});
163169
});
164170

165171
it('with no user id, a user-scoped key falls through to the tenant rung, then global, then the default', async () => {
166-
const users = [userRow(USER_A, 'dark'), userRow(USER_B, 'light')];
172+
const users = PEOPLE;
167173

168174
const withTenant = await stack([...users, tenantRow('theme', 'tenant-theme')]).svc.get(NS, 'theme');
169175
expect(withTenant).toMatchObject({ value: 'tenant-theme', source: 'tenant' });
@@ -186,7 +192,7 @@ describe('the user rung answers only its owner', () => {
186192
});
187193

188194
it('a reset with no user id counts no user row and writes nothing', async () => {
189-
const { svc, wrote, snapshot } = stack([userRow(USER_A, 'dark'), userRow(USER_B, 'light')]);
195+
const { svc, wrote, snapshot } = stack(PEOPLE);
190196
const before = snapshot();
191197
expect(await svc.resetNamespace(NS)).toBe(0);
192198
expect(wrote()).toBe(0);
@@ -203,6 +209,16 @@ describe('the user rung answers only its owner', () => {
203209
});
204210
});
205211

212+
describe('the user rung compares the owner itself, whatever rows the store answers', () => {
213+
it.each(READ_PATHS)('%s', async (_path, read) => {
214+
const { svc } = engineStack(PEOPLE, { overAnswer: true });
215+
expect(await read(svc, { userId: USER_B })).toBe('light');
216+
expect(await read(svc, { userId: USER_A })).toBe('dark');
217+
expect(await read(svc, { userId: 'usr_owner_c' })).toBe('system');
218+
expect(await read(svc, {})).toBe('system');
219+
});
220+
});
221+
206222
describe('a user-scoped write names its owner', () => {
207223
describe.each(STACKS)('%s', (_label, stack) => {
208224
it.each(NO_USER)('a write of a user-scoped key with no user id is refused and stores nothing (%s)', async (_c, ctx) => {

0 commit comments

Comments
 (0)