@substrat-run/contract-tests 0.159.0 → 0.161.0
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/attachment-bytes-suite.d.ts +9 -0
- package/dist/attachment-bytes-suite.d.ts.map +1 -0
- package/dist/attachment-bytes-suite.js +97 -0
- package/dist/attachment-bytes-suite.js.map +1 -0
- package/dist/connector-fixture.d.ts +2 -2
- package/dist/connector-fixture.d.ts.map +1 -1
- package/dist/directory-audit-suite.d.ts +23 -0
- package/dist/directory-audit-suite.d.ts.map +1 -0
- package/dist/directory-audit-suite.js +560 -0
- package/dist/directory-audit-suite.js.map +1 -0
- package/dist/directory-restore-suite.d.ts.map +1 -1
- package/dist/directory-restore-suite.js +99 -0
- package/dist/directory-restore-suite.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/introspection-reads-suite.d.ts +35 -0
- package/dist/introspection-reads-suite.d.ts.map +1 -0
- package/dist/introspection-reads-suite.js +114 -0
- package/dist/introspection-reads-suite.js.map +1 -0
- package/dist/many-migrations-suite.d.ts +9 -0
- package/dist/many-migrations-suite.d.ts.map +1 -0
- package/dist/many-migrations-suite.js +76 -0
- package/dist/many-migrations-suite.js.map +1 -0
- package/dist/many-migrations.d.ts +16 -0
- package/dist/many-migrations.d.ts.map +1 -0
- package/dist/many-migrations.js +50 -0
- package/dist/many-migrations.js.map +1 -0
- package/dist/membership-executor-suite.d.ts.map +1 -1
- package/dist/membership-executor-suite.js +8 -5
- package/dist/membership-executor-suite.js.map +1 -1
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +10 -0
- package/dist/modules.js.map +1 -1
- package/dist/reserved-role-suite.d.ts +6 -0
- package/dist/reserved-role-suite.d.ts.map +1 -1
- package/dist/reserved-role-suite.js +176 -8
- package/dist/reserved-role-suite.js.map +1 -1
- package/dist/restore-role-suite.d.ts +17 -0
- package/dist/restore-role-suite.d.ts.map +1 -0
- package/dist/restore-role-suite.js +107 -0
- package/dist/restore-role-suite.js.map +1 -0
- package/dist/role-state-suite.d.ts +17 -0
- package/dist/role-state-suite.d.ts.map +1 -0
- package/dist/role-state-suite.js +80 -0
- package/dist/role-state-suite.js.map +1 -0
- package/dist/schedule-platform-requests-suite.d.ts +7 -1
- package/dist/schedule-platform-requests-suite.d.ts.map +1 -1
- package/dist/schedule-platform-requests-suite.js +349 -2
- package/dist/schedule-platform-requests-suite.js.map +1 -1
- package/dist/schedule-purge-request-module.d.ts +88 -0
- package/dist/schedule-purge-request-module.d.ts.map +1 -0
- package/dist/schedule-purge-request-module.js +114 -0
- package/dist/schedule-purge-request-module.js.map +1 -0
- package/dist/schedule-purge-two-module.d.ts +141 -0
- package/dist/schedule-purge-two-module.d.ts.map +1 -0
- package/dist/schedule-purge-two-module.js +153 -0
- package/dist/schedule-purge-two-module.js.map +1 -0
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +1329 -8
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/size-reads-suite.d.ts +20 -0
- package/dist/size-reads-suite.d.ts.map +1 -0
- package/dist/size-reads-suite.js +77 -0
- package/dist/size-reads-suite.js.map +1 -0
- package/dist/stored-vertical-copy-suite.d.ts +15 -0
- package/dist/stored-vertical-copy-suite.d.ts.map +1 -0
- package/dist/stored-vertical-copy-suite.js +68 -0
- package/dist/stored-vertical-copy-suite.js.map +1 -0
- package/dist/unreadable-scope-row-suite.d.ts +16 -0
- package/dist/unreadable-scope-row-suite.d.ts.map +1 -0
- package/dist/unreadable-scope-row-suite.js +152 -0
- package/dist/unreadable-scope-row-suite.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ScopeId, type TenantId } from '@substrat-run/contracts';
|
|
2
|
+
import type { ScopeHostFixture } from './scope-host-suite.js';
|
|
3
|
+
/**
|
|
4
|
+
* The vertical-side size reads (#1524, #2501), which take a scope id and nothing else, hold the
|
|
5
|
+
* scope before they open its store (#2541): `/internal/database-size` and `/internal/attachment-size`
|
|
6
|
+
* reach them. A scope this deployment does not hold (never provisioned here, or reaped) is refused
|
|
7
|
+
* `conflict` with one sentence on both reads and both adapters, never answered as an empty store
|
|
8
|
+
* reading zero. The admin twins (`scopeDatabaseSize`, `scopeAttachmentBytes`) resolve the scope
|
|
9
|
+
* through the directory and are held by the shared host suite.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sizeReadsContractSuite(adapterName: string, makeFixture: () => Promise<ScopeHostFixture & {
|
|
12
|
+
databaseSizeLocal(scopeId: ScopeId): Promise<number>;
|
|
13
|
+
attachmentSizeLocal(scopeId: ScopeId): Promise<number>;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the scope's store holds nothing: no file on disk, or a Durable Object holding no row
|
|
16
|
+
* of state. An adapter that must construct an object to ask is allowed an empty one.
|
|
17
|
+
*/
|
|
18
|
+
storeHoldsNothing(tenantId: TenantId, scopeId: ScopeId): Promise<boolean>;
|
|
19
|
+
}>): void;
|
|
20
|
+
//# sourceMappingURL=size-reads-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"size-reads-suite.d.ts","sourceRoot":"","sources":["../src/size-reads-suite.ts"],"names":[],"mappings":"AACA,OAAO,EAAoE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExI,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CACxB,gBAAgB,GAAG;IACjB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD;;;OAGG;IACH,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3E,CACF,GACA,IAAI,CA2EN"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import { errorCodeOf, platformActorId, scopeId as scopeIdSchema, tenantId } from '@substrat-run/contracts';
|
|
3
|
+
import { scopeNotHeldRefusal, ulid } from '@substrat-run/kernel';
|
|
4
|
+
/**
|
|
5
|
+
* The vertical-side size reads (#1524, #2501), which take a scope id and nothing else, hold the
|
|
6
|
+
* scope before they open its store (#2541): `/internal/database-size` and `/internal/attachment-size`
|
|
7
|
+
* reach them. A scope this deployment does not hold (never provisioned here, or reaped) is refused
|
|
8
|
+
* `conflict` with one sentence on both reads and both adapters, never answered as an empty store
|
|
9
|
+
* reading zero. The admin twins (`scopeDatabaseSize`, `scopeAttachmentBytes`) resolve the scope
|
|
10
|
+
* through the directory and are held by the shared host suite.
|
|
11
|
+
*/
|
|
12
|
+
export function sizeReadsContractSuite(adapterName, makeFixture) {
|
|
13
|
+
describe(`vertical-side size reads refuse a scope the deployment does not hold (#2541): ${adapterName}`, () => {
|
|
14
|
+
let fixture;
|
|
15
|
+
const staff = platformActorId.parse(ulid());
|
|
16
|
+
const t = tenantId.parse(ulid());
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
fixture = await makeFixture();
|
|
19
|
+
await fixture.host.admin.createTenant(staff, { id: t, slug: `size-reads-${t.slice(-8).toLowerCase()}`, name: 'Size reads' });
|
|
20
|
+
});
|
|
21
|
+
afterAll(async () => fixture?.cleanup());
|
|
22
|
+
const newScope = async () => {
|
|
23
|
+
const s = scopeIdSchema.parse(ulid());
|
|
24
|
+
await fixture.host.provisionScope(staff, { tenantId: t, scopeId: s, vertical: 'size-reads' });
|
|
25
|
+
await fixture.host.admin.activateScope(staff, t, s);
|
|
26
|
+
return s;
|
|
27
|
+
};
|
|
28
|
+
const reap = async (s) => {
|
|
29
|
+
await fixture.host.admin.archiveScope(staff, t, s);
|
|
30
|
+
await fixture.host.admin.reapScope(staff, t, s, { force: true });
|
|
31
|
+
};
|
|
32
|
+
const refusal = (p) => p.then(() => undefined, (e) => ({ code: errorCodeOf(e), message: e.message }));
|
|
33
|
+
const refusedAs = (s) => ({ code: 'conflict', message: scopeNotHeldRefusal(s) });
|
|
34
|
+
it('reads a held scope: a database with bytes, and attachments that sum to nothing yet', async () => {
|
|
35
|
+
const s = await newScope();
|
|
36
|
+
expect(await fixture.databaseSizeLocal(s)).toBeGreaterThan(0);
|
|
37
|
+
expect(await fixture.attachmentSizeLocal(s)).toBe(0);
|
|
38
|
+
});
|
|
39
|
+
it('refuses a reaped scope on both reads, in the same words, and leaves nothing stored for it', async () => {
|
|
40
|
+
const s = await newScope();
|
|
41
|
+
await reap(s);
|
|
42
|
+
const database = await refusal(fixture.databaseSizeLocal(s));
|
|
43
|
+
expect(database).toEqual(refusedAs(s));
|
|
44
|
+
expect(await refusal(fixture.attachmentSizeLocal(s))).toEqual(database);
|
|
45
|
+
// Asked again, it is still refused: the first refusal did not make the scope held.
|
|
46
|
+
expect(await refusal(fixture.databaseSizeLocal(s))).toEqual(database);
|
|
47
|
+
expect(await fixture.storeHoldsNothing(t, s)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it('refuses an id nothing provisioned here, on both reads', async () => {
|
|
50
|
+
const s = scopeIdSchema.parse(ulid());
|
|
51
|
+
expect(await refusal(fixture.databaseSizeLocal(s))).toEqual(refusedAs(s));
|
|
52
|
+
expect(await refusal(fixture.attachmentSizeLocal(s))).toEqual(refusedAs(s));
|
|
53
|
+
expect(await fixture.storeHoldsNothing(t, s)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it('reads a scope archived straight from provisioning the same way on both adapters: provisioning built its store, so it is held', async () => {
|
|
56
|
+
const s = scopeIdSchema.parse(ulid());
|
|
57
|
+
await fixture.host.provisionScope(staff, { tenantId: t, scopeId: s, vertical: 'size-reads' });
|
|
58
|
+
await fixture.host.admin.archiveScope(staff, t, s);
|
|
59
|
+
expect(await fixture.databaseSizeLocal(s)).toBeGreaterThan(0);
|
|
60
|
+
expect(await fixture.attachmentSizeLocal(s)).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
it('a scope listed as due and reaped before it is read is refused, while the held one beside it reads', async () => {
|
|
63
|
+
const [gone, kept] = [await newScope(), await newScope()];
|
|
64
|
+
// The sweep lists its due scopes first…
|
|
65
|
+
const due = (await fixture.host.admin.listScopes(staff, { tenantId: t, status: 'active' })).map((x) => x.id);
|
|
66
|
+
expect(due).toEqual(expect.arrayContaining([gone, kept]));
|
|
67
|
+
// …a reap lands between the listing and the read…
|
|
68
|
+
await reap(gone);
|
|
69
|
+
// …and the read of each listed scope meets the world as it is now.
|
|
70
|
+
const read = async (s) => refusal(fixture.databaseSizeLocal(s).then(() => fixture.attachmentSizeLocal(s)));
|
|
71
|
+
expect(await read(gone)).toEqual(refusedAs(gone));
|
|
72
|
+
expect(await read(kept)).toBeUndefined();
|
|
73
|
+
expect(await fixture.storeHoldsNothing(t, gone)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=size-reads-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"size-reads-suite.js","sourceRoot":"","sources":["../src/size-reads-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DirectoryExec } from './findings-atomic-suite.js';
|
|
2
|
+
import { type ScopeHostFixture } from './scope-host-suite.js';
|
|
3
|
+
/**
|
|
4
|
+
* Provisioning holds a vertical a caller names to the registry's grammar (`verticalSource:
|
|
5
|
+
* 'input'`, the default), and a copy takes the vertical its source already stores
|
|
6
|
+
* (`'copy'`), held only to what the directory reads. So a scope whose stored vertical reads but
|
|
7
|
+
* is not a slug — one stored before the grammar was held at provisioning — is snapshotted as
|
|
8
|
+
* before, and the same value named by a caller is refused.
|
|
9
|
+
*
|
|
10
|
+
* `exec` writes the stored value past the provisioning boundary, the only way such a row is
|
|
11
|
+
* reached now. Every value is written back before the suite ends: an adapter's directory may be
|
|
12
|
+
* shared with other suites.
|
|
13
|
+
*/
|
|
14
|
+
export declare function storedVerticalCopyContractSuite(adapterName: string, makeFixture: () => Promise<ScopeHostFixture>, exec: DirectoryExec): void;
|
|
15
|
+
//# sourceMappingURL=stored-vertical-copy-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stored-vertical-copy-suite.d.ts","sourceRoot":"","sources":["../src/stored-vertical-copy-suite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAiB,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE7E;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,IAAI,EAAE,aAAa,GAClB,IAAI,CAgEN"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { platformActorId, scopeId, tenantId } from '@substrat-run/contracts';
|
|
3
|
+
import { ulid } from '@substrat-run/kernel';
|
|
4
|
+
import { expectRefusal } from './scope-host-suite.js';
|
|
5
|
+
/**
|
|
6
|
+
* Provisioning holds a vertical a caller names to the registry's grammar (`verticalSource:
|
|
7
|
+
* 'input'`, the default), and a copy takes the vertical its source already stores
|
|
8
|
+
* (`'copy'`), held only to what the directory reads. So a scope whose stored vertical reads but
|
|
9
|
+
* is not a slug — one stored before the grammar was held at provisioning — is snapshotted as
|
|
10
|
+
* before, and the same value named by a caller is refused.
|
|
11
|
+
*
|
|
12
|
+
* `exec` writes the stored value past the provisioning boundary, the only way such a row is
|
|
13
|
+
* reached now. Every value is written back before the suite ends: an adapter's directory may be
|
|
14
|
+
* shared with other suites.
|
|
15
|
+
*/
|
|
16
|
+
export function storedVerticalCopyContractSuite(adapterName, makeFixture, exec) {
|
|
17
|
+
describe(`a stored vertical that is not a slug is copied as stored: ${adapterName}`, () => {
|
|
18
|
+
let fixture;
|
|
19
|
+
let host;
|
|
20
|
+
const staff = platformActorId.parse(ulid());
|
|
21
|
+
const t = tenantId.parse(ulid());
|
|
22
|
+
/** Values the `scope` schema reads (non-empty) that the registry grammar does not admit. */
|
|
23
|
+
const STORED = ['Todo', 'my_vertical', 'a/b/c', ' spaced '];
|
|
24
|
+
const written = [];
|
|
25
|
+
/** An active scope whose row stores `vertical` as given. Every value is a literal of this suite. */
|
|
26
|
+
const storedWith = async (vertical) => {
|
|
27
|
+
const s = scopeId.parse(ulid());
|
|
28
|
+
await host.provisionScope(staff, { tenantId: t, scopeId: s, vertical: 'todo' });
|
|
29
|
+
await host.admin.activateScope(staff, t, s);
|
|
30
|
+
await exec(host, `UPDATE scopes SET vertical = '${vertical}' WHERE scope_id = '${s}'`);
|
|
31
|
+
written.push(s);
|
|
32
|
+
return s;
|
|
33
|
+
};
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
fixture = await makeFixture();
|
|
36
|
+
host = fixture.host;
|
|
37
|
+
await host.admin.createTenant(staff, { id: t, slug: `stored-vertical-${t.slice(-10).toLowerCase()}`, name: 'Stored vertical' });
|
|
38
|
+
});
|
|
39
|
+
afterAll(async () => {
|
|
40
|
+
for (const s of written)
|
|
41
|
+
await exec(host, `UPDATE scopes SET vertical = 'todo' WHERE scope_id = '${s}'`);
|
|
42
|
+
await fixture.cleanup();
|
|
43
|
+
});
|
|
44
|
+
it.each(STORED)('snapshots a scope storing %j, and the snapshot carries it', async (vertical) => {
|
|
45
|
+
const source = await storedWith(vertical);
|
|
46
|
+
const snap = await host.snapshotScope(staff, t, source);
|
|
47
|
+
written.push(snap);
|
|
48
|
+
expect(await host.admin.getScopeRecord(staff, t, snap)).toMatchObject({ forkedFrom: source, vertical });
|
|
49
|
+
});
|
|
50
|
+
it.each(STORED)('provisions %j given as a copy', async (vertical) => {
|
|
51
|
+
const s = scopeId.parse(ulid());
|
|
52
|
+
await host.provisionScope(staff, { tenantId: t, scopeId: s, vertical, verticalSource: 'copy' });
|
|
53
|
+
written.push(s);
|
|
54
|
+
expect((await host.admin.getScopeRecord(staff, t, s))?.vertical).toBe(vertical);
|
|
55
|
+
});
|
|
56
|
+
it.each([...STORED.map((v) => [v, undefined]), ...STORED.map((v) => [v, 'input'])])('refuses %j named by a caller (source %s)', async (vertical, verticalSource) => {
|
|
57
|
+
const s = scopeId.parse(ulid());
|
|
58
|
+
await expectRefusal(host.provisionScope(staff, { tenantId: t, scopeId: s, vertical, ...(verticalSource ? { verticalSource } : {}) }), 'validation_failed', /^provisionScope: vertical /);
|
|
59
|
+
expect(await host.admin.getScopeRecord(staff, t, s)).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
it('refuses an empty vertical even as a copy', async () => {
|
|
62
|
+
const s = scopeId.parse(ulid());
|
|
63
|
+
await expectRefusal(host.provisionScope(staff, { tenantId: t, scopeId: s, vertical: '', verticalSource: 'copy' }), 'validation_failed', /^provisionScope: vertical /);
|
|
64
|
+
expect(await host.admin.getScopeRecord(staff, t, s)).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=stored-vertical-copy-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stored-vertical-copy-suite.js","sourceRoot":"","sources":["../src/stored-vertical-copy-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DirectoryExec } from './findings-atomic-suite.js';
|
|
2
|
+
import type { ScopeHostFixture } from './scope-host-suite.js';
|
|
3
|
+
/**
|
|
4
|
+
* One directory row that does not read as a scope must not stop a fleet-wide pass. The strict
|
|
5
|
+
* `listScopes` is unchanged and still refuses the list; `listScopesReadable` answers the rest of
|
|
6
|
+
* it and names the row, and `runPlatformSweep` reads through it, so every phase after the first
|
|
7
|
+
* still runs and the row is named once.
|
|
8
|
+
*
|
|
9
|
+
* The pass is narrowed to this suite's own scopes: an adapter's directory may hold other suites'
|
|
10
|
+
* scopes, and a whole-fleet pass over them would reap and drain things this suite does not own.
|
|
11
|
+
* Every planted row is written back before the suite ends, for the same reason. `exec` writes
|
|
12
|
+
* the directory past the provisioning boundary, which is how such a row is reached at all (one
|
|
13
|
+
* stored before the refusal, or a restore).
|
|
14
|
+
*/
|
|
15
|
+
export declare function unreadableScopeRowContractSuite(adapterName: string, makeFixture: () => Promise<ScopeHostFixture>, exec: DirectoryExec): void;
|
|
16
|
+
//# sourceMappingURL=unreadable-scope-row-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unreadable-scope-row-suite.d.ts","sourceRoot":"","sources":["../src/unreadable-scope-row-suite.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAI9D;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,EAC5C,IAAI,EAAE,aAAa,GAClB,IAAI,CA0GN"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { platformActorId, scopeId, tenantId } from '@substrat-run/contracts';
|
|
3
|
+
import { runPlatformSweep, ulid } from '@substrat-run/kernel';
|
|
4
|
+
const FETCH = async () => new Response(null, { status: 599 });
|
|
5
|
+
/**
|
|
6
|
+
* One directory row that does not read as a scope must not stop a fleet-wide pass. The strict
|
|
7
|
+
* `listScopes` is unchanged and still refuses the list; `listScopesReadable` answers the rest of
|
|
8
|
+
* it and names the row, and `runPlatformSweep` reads through it, so every phase after the first
|
|
9
|
+
* still runs and the row is named once.
|
|
10
|
+
*
|
|
11
|
+
* The pass is narrowed to this suite's own scopes: an adapter's directory may hold other suites'
|
|
12
|
+
* scopes, and a whole-fleet pass over them would reap and drain things this suite does not own.
|
|
13
|
+
* Every planted row is written back before the suite ends, for the same reason. `exec` writes
|
|
14
|
+
* the directory past the provisioning boundary, which is how such a row is reached at all (one
|
|
15
|
+
* stored before the refusal, or a restore).
|
|
16
|
+
*/
|
|
17
|
+
export function unreadableScopeRowContractSuite(adapterName, makeFixture, exec) {
|
|
18
|
+
describe(`an unreadable scope row in a fleet-wide pass: ${adapterName}`, () => {
|
|
19
|
+
let fixture;
|
|
20
|
+
let host;
|
|
21
|
+
const staff = platformActorId.parse(ulid());
|
|
22
|
+
const t = tenantId.parse(ulid());
|
|
23
|
+
const mine = new Set();
|
|
24
|
+
let live;
|
|
25
|
+
let preview;
|
|
26
|
+
let archived;
|
|
27
|
+
/** One row per column the `scope` schema reads as non-empty, each stored empty below. */
|
|
28
|
+
const planted = { vertical: '', kind: '', name: '' };
|
|
29
|
+
const columns = ['vertical', 'kind', 'name'];
|
|
30
|
+
/** One column of one row, written as given. Every value is a literal of this suite. */
|
|
31
|
+
const write = (scope, column, value) => exec(host, `UPDATE scopes SET ${column} = '${value}' WHERE scope_id = '${scope}'`);
|
|
32
|
+
const provision = async (extra = {}) => {
|
|
33
|
+
const s = scopeId.parse(ulid());
|
|
34
|
+
await host.provisionScope(staff, { tenantId: t, scopeId: s, vertical: 'todo', ...extra });
|
|
35
|
+
await host.admin.activateScope(staff, t, s);
|
|
36
|
+
mine.add(s);
|
|
37
|
+
return s;
|
|
38
|
+
};
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
fixture = await makeFixture();
|
|
41
|
+
host = fixture.host;
|
|
42
|
+
await host.admin.createTenant(staff, { id: t, slug: `unreadable-${t.slice(-10).toLowerCase()}`, name: 'Unreadable' });
|
|
43
|
+
live = await provision();
|
|
44
|
+
preview = await provision({ kind: 'preview', expiresAt: '2000-01-01T00:00:00.000Z' });
|
|
45
|
+
archived = await provision();
|
|
46
|
+
await host.admin.archiveScope(staff, t, archived);
|
|
47
|
+
for (const column of columns)
|
|
48
|
+
planted[column] = await provision();
|
|
49
|
+
});
|
|
50
|
+
afterAll(async () => {
|
|
51
|
+
for (const column of columns)
|
|
52
|
+
await write(planted[column], column, 'restored');
|
|
53
|
+
await fixture.cleanup();
|
|
54
|
+
});
|
|
55
|
+
it('answers a healthy directory exactly as listScopes does', async () => {
|
|
56
|
+
const strict = await host.admin.listScopes(staff, { tenantId: t });
|
|
57
|
+
const readable = await host.admin.listScopesReadable(staff, { tenantId: t });
|
|
58
|
+
expect(readable).toEqual({ scopes: strict, unreadable: [] });
|
|
59
|
+
});
|
|
60
|
+
describe('with rows that do not read as scopes', () => {
|
|
61
|
+
beforeAll(async () => {
|
|
62
|
+
// The stored shapes provisioning used to accept, which the `scope` schema reads back as a
|
|
63
|
+
// parse error: an empty vertical, kind or name. Several, so "named once" is about phases
|
|
64
|
+
// and not about there being one.
|
|
65
|
+
for (const column of columns)
|
|
66
|
+
await write(planted[column], column, '');
|
|
67
|
+
});
|
|
68
|
+
it('leaves listScopes strict', async () => {
|
|
69
|
+
await expect(host.admin.listScopes(staff, { tenantId: t })).rejects.toThrow();
|
|
70
|
+
});
|
|
71
|
+
it('names each by its ids and failed field in listScopesReadable, and answers the rest', async () => {
|
|
72
|
+
const { scopes, unreadable } = await host.admin.listScopesReadable(staff, { tenantId: t });
|
|
73
|
+
expect(scopes.map((s) => s.id).sort()).toEqual([live, preview, archived].sort());
|
|
74
|
+
expect(unreadable.sort((a, b) => a.scopeId.localeCompare(b.scopeId))).toEqual(columns
|
|
75
|
+
.map((column) => ({ scopeId: planted[column], tenantId: t, fields: [column] }))
|
|
76
|
+
.sort((a, b) => a.scopeId.localeCompare(b.scopeId)));
|
|
77
|
+
});
|
|
78
|
+
it('runs every later phase of a pass and names each row once', async () => {
|
|
79
|
+
const drained = [];
|
|
80
|
+
const platformDrained = [];
|
|
81
|
+
const gc = [];
|
|
82
|
+
const reaped = [];
|
|
83
|
+
const shipped = [];
|
|
84
|
+
const report = await runPlatformSweep(narrowedTo(host, mine, drained), {
|
|
85
|
+
actor: staff,
|
|
86
|
+
fetch: FETCH,
|
|
87
|
+
sweepers: {},
|
|
88
|
+
runSchedules: false,
|
|
89
|
+
drainPlatformRequestsFn: async (_t, s) => (platformDrained.push(s), { drained: 0, done: 0, failed: 0, pending: 0, attempted: [] }),
|
|
90
|
+
reconcileScopeFn: async () => { },
|
|
91
|
+
deleteSnapshotFn: async (_t, s) => void gc.push(s),
|
|
92
|
+
reapArchivedAfterDays: 0,
|
|
93
|
+
reapScopeFn: async (_t, s) => void reaped.push(s),
|
|
94
|
+
eventSink: { ship: async (scope) => (shipped.push(scope.scopeId), { ref: 'r' }) },
|
|
95
|
+
});
|
|
96
|
+
// The migration phase reads first; every phase below it ran.
|
|
97
|
+
expect(report.migrations).not.toBeNull();
|
|
98
|
+
expect(drained.sort()).toEqual([live, preview].sort());
|
|
99
|
+
expect(platformDrained.sort()).toEqual([live, preview].sort());
|
|
100
|
+
expect(report.provisionReconcile).not.toBeNull();
|
|
101
|
+
expect(gc).toEqual([preview]);
|
|
102
|
+
expect(reaped).toEqual([archived]);
|
|
103
|
+
expect(report.eventDrain).not.toBeNull();
|
|
104
|
+
// Each row once, however many phases met it, by id and field and nothing of the row.
|
|
105
|
+
const rows = report.errors.filter((e) => e.kind === 'scope-row');
|
|
106
|
+
expect(rows.map((e) => e.id).sort()).toEqual(columns.map((c) => planted[c]).sort());
|
|
107
|
+
for (const column of columns) {
|
|
108
|
+
expect(rows.find((e) => e.id === planted[column])?.error).toMatch(new RegExp(`tenant ${t} .*\\(${column}\\)`));
|
|
109
|
+
}
|
|
110
|
+
expect(report.errors.filter((e) => e.kind !== 'scope-row')).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The host as a pass sees it, narrowed to `mine`: both directory reads answer only these scopes,
|
|
117
|
+
* no connection is listed, and `drainDue` records the scope instead of waking it.
|
|
118
|
+
*/
|
|
119
|
+
function narrowedTo(host, mine, drained) {
|
|
120
|
+
const admin = new Proxy(host.admin, {
|
|
121
|
+
get(target, p) {
|
|
122
|
+
if (p === 'listScopes') {
|
|
123
|
+
return async (...args) => (await target.listScopes(...args)).filter((s) => mine.has(s.id));
|
|
124
|
+
}
|
|
125
|
+
if (p === 'listScopesReadable') {
|
|
126
|
+
return async (...args) => {
|
|
127
|
+
const l = await target.listScopesReadable(...args);
|
|
128
|
+
return { scopes: l.scopes.filter((s) => mine.has(s.id)), unreadable: l.unreadable.filter((u) => mine.has(u.scopeId)) };
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (p === 'listConnections')
|
|
132
|
+
return async () => [];
|
|
133
|
+
return forward(target, p);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
return new Proxy(host, {
|
|
137
|
+
get(target, p) {
|
|
138
|
+
if (p === 'admin')
|
|
139
|
+
return admin;
|
|
140
|
+
if (p === 'drainDue') {
|
|
141
|
+
return async (_t, s) => (drained.push(s), { attempted: 0, delivered: 0, retrying: 0, deadLettered: 0 });
|
|
142
|
+
}
|
|
143
|
+
return forward(target, p);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/** Everything a narrowed view does not answer itself, from the object it wraps. */
|
|
148
|
+
function forward(target, p) {
|
|
149
|
+
const v = Reflect.get(target, p);
|
|
150
|
+
return typeof v === 'function' ? v.bind(target) : v;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=unreadable-scope-row-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unreadable-scope-row-suite.js","sourceRoot":"","sources":["../src/unreadable-scope-row-suite.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@substrat-run/contract-tests",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.161.0",
|
|
4
4
|
"description": "The suite every scope-host adapter must pass, forever (D-14)",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"dist"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@substrat-run/contracts": "^0.
|
|
34
|
-
"@substrat-run/kernel": "^0.
|
|
33
|
+
"@substrat-run/contracts": "^0.161.0",
|
|
34
|
+
"@substrat-run/kernel": "^0.161.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22.20.5",
|