@substrat-run/contract-tests 0.147.0 → 0.149.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/bind-module.d.ts +4 -0
- package/dist/bind-module.d.ts.map +1 -1
- package/dist/capability-expiry-suite.d.ts.map +1 -1
- package/dist/capability-expiry-suite.js +6 -4
- package/dist/capability-expiry-suite.js.map +1 -1
- package/dist/counter-runner.d.ts +22 -0
- package/dist/counter-runner.d.ts.map +1 -0
- package/dist/counter-runner.js +96 -0
- package/dist/counter-runner.js.map +1 -0
- package/dist/derived-module.d.ts +4 -0
- package/dist/derived-module.d.ts.map +1 -1
- package/dist/directory-restore-suite.d.ts.map +1 -1
- package/dist/directory-restore-suite.js +66 -2
- package/dist/directory-restore-suite.js.map +1 -1
- package/dist/directory-shapes.d.ts +6 -0
- package/dist/directory-shapes.d.ts.map +1 -1
- package/dist/directory-shapes.js +42 -0
- package/dist/directory-shapes.js.map +1 -1
- package/dist/entity-state-module.d.ts +8 -0
- package/dist/entity-state-module.d.ts.map +1 -1
- package/dist/entity-state-module.js +3 -0
- package/dist/entity-state-module.js.map +1 -1
- package/dist/entity-trash-module.d.ts +4 -0
- package/dist/entity-trash-module.d.ts.map +1 -1
- package/dist/entity-trash-suite.d.ts.map +1 -1
- package/dist/entity-trash-suite.js +20 -0
- package/dist/entity-trash-suite.js.map +1 -1
- package/dist/erasure-module.d.ts +4 -0
- package/dist/erasure-module.d.ts.map +1 -1
- package/dist/held-migrations.d.ts +27 -0
- package/dist/held-migrations.d.ts.map +1 -0
- package/dist/held-migrations.js +39 -0
- package/dist/held-migrations.js.map +1 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/migration-fence-suite.d.ts +3 -0
- package/dist/migration-fence-suite.d.ts.map +1 -0
- package/dist/migration-fence-suite.js +173 -0
- package/dist/migration-fence-suite.js.map +1 -0
- package/dist/migration-fence.d.ts +42 -0
- package/dist/migration-fence.d.ts.map +1 -0
- package/dist/migration-fence.js +66 -0
- package/dist/migration-fence.js.map +1 -0
- package/dist/modules.d.ts +126 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +23 -0
- package/dist/modules.js.map +1 -1
- package/dist/orphan-sweep-suite.d.ts +23 -0
- package/dist/orphan-sweep-suite.d.ts.map +1 -0
- package/dist/orphan-sweep-suite.js +411 -0
- package/dist/orphan-sweep-suite.js.map +1 -0
- package/dist/permission-suite.d.ts.map +1 -1
- package/dist/permission-suite.js +19 -0
- package/dist/permission-suite.js.map +1 -1
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +465 -10
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/scope-store-runner-suite.d.ts +46 -0
- package/dist/scope-store-runner-suite.d.ts.map +1 -0
- package/dist/scope-store-runner-suite.js +187 -0
- package/dist/scope-store-runner-suite.js.map +1 -0
- package/dist/scope-store-suite.d.ts.map +1 -1
- package/dist/scope-store-suite.js +55 -103
- package/dist/scope-store-suite.js.map +1 -1
- package/dist/stamps-module.d.ts +4 -0
- package/dist/stamps-module.d.ts.map +1 -1
- package/dist/store-driver-release-suite.d.ts +12 -0
- package/dist/store-driver-release-suite.d.ts.map +1 -0
- package/dist/store-driver-release-suite.js +561 -0
- package/dist/store-driver-release-suite.js.map +1 -0
- package/dist/upload-link-module.d.ts +172 -0
- package/dist/upload-link-module.d.ts.map +1 -0
- package/dist/upload-link-module.js +157 -0
- package/dist/upload-link-module.js.map +1 -0
- package/dist/upload-link-replay-suite.d.ts +3 -0
- package/dist/upload-link-replay-suite.d.ts.map +1 -0
- package/dist/upload-link-replay-suite.js +291 -0
- package/dist/upload-link-replay-suite.js.map +1 -0
- package/dist/upload-link-suite.d.ts +3 -0
- package/dist/upload-link-suite.d.ts.map +1 -0
- package/dist/upload-link-suite.js +485 -0
- package/dist/upload-link-suite.js.map +1 -0
- package/dist/vertical-events-suite.d.ts +8 -0
- package/dist/vertical-events-suite.d.ts.map +1 -1
- package/dist/vertical-events-suite.js +46 -4
- package/dist/vertical-events-suite.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type StoreRequest } from '@substrat-run/contracts';
|
|
2
|
+
import { type ScopeStoreRunner } from '@substrat-run/kernel';
|
|
3
|
+
/**
|
|
4
|
+
* What a driver hands the runner suite. The suite knows nothing about the driver beyond
|
|
5
|
+
* two calls of its own catalog.
|
|
6
|
+
*/
|
|
7
|
+
export interface ScopeStoreRunnerFixture {
|
|
8
|
+
/** A runner nobody has started. Called once per check. */
|
|
9
|
+
makeRunner(): ScopeStoreRunner | Promise<ScopeStoreRunner>;
|
|
10
|
+
/** A driver operation that changes the object. Each call must change what `read` answers. */
|
|
11
|
+
readonly write: StoreRequest;
|
|
12
|
+
/** A driver operation that reads the object back. Equal answers mean equal objects. */
|
|
13
|
+
readonly read: StoreRequest;
|
|
14
|
+
}
|
|
15
|
+
/** One check of the runner contract. It throws when the runner breaks it. */
|
|
16
|
+
export interface ScopeStoreRunnerCheck {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The runner contract (#2213, RFC §7) as a list of checks, so a broken runner can be
|
|
22
|
+
* shown to fail the one it breaks. {@link scopeStoreRunnerContractSuite} runs them all.
|
|
23
|
+
*
|
|
24
|
+
* Every call goes through the kernel's callers both hosts use (`callScopeStore`,
|
|
25
|
+
* `callReservedStoreOperation`), so an answer is held to the same envelope and result
|
|
26
|
+
* schema a host would hold it to.
|
|
27
|
+
*
|
|
28
|
+
* A restart is `stop()` then `start()` on the same runner instance: what a host does
|
|
29
|
+
* between a `close` and the next first store call. A driver's repositories must survive
|
|
30
|
+
* it. Blob cells in an export are not asserted (their spelling over JSON is not settled).
|
|
31
|
+
*/
|
|
32
|
+
export declare function scopeStoreRunnerChecks(fixture: ScopeStoreRunnerFixture): ScopeStoreRunnerCheck[];
|
|
33
|
+
/**
|
|
34
|
+
* The runner contract a scope-store driver's runner passes (#2213). A driver runs it in its
|
|
35
|
+
* own repository, against its own runner: the platform runs only the trivial driver.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* scopeStoreRunnerContractSuite('graph', {
|
|
39
|
+
* makeRunner: () => graphRunner({ dir }),
|
|
40
|
+
* write: { operation: 'apply', input: { … } },
|
|
41
|
+
* read: { operation: 'inspect' },
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function scopeStoreRunnerContractSuite(driver: string, fixture: ScopeStoreRunnerFixture): void;
|
|
46
|
+
//# sourceMappingURL=scope-store-runner-suite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-store-runner-suite.d.ts","sourceRoot":"","sources":["../src/scope-store-runner-suite.ts"],"names":[],"mappings":"AACA,OAAO,EAA8C,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACxG,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAE9B;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,0DAA0D;IAC1D,UAAU,IAAI,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3D,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;CAC7B;AAED,6EAA6E;AAC7E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,qBAAqB,EAAE,CAuJhG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAMpG"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { storeResponse } from '@substrat-run/contracts';
|
|
3
|
+
import { callReservedStoreOperation, callScopeStore, ulid, } from '@substrat-run/kernel';
|
|
4
|
+
/** An address no other check, and no earlier run, has used. */
|
|
5
|
+
function freshAddress() {
|
|
6
|
+
return { tenantId: ulid(), vertical: 'conformance', scopeId: ulid(), binding: 'STORE' };
|
|
7
|
+
}
|
|
8
|
+
/** An operation name no driver declares, and still a valid label. */
|
|
9
|
+
const UNKNOWN_OPERATION = 'conformance-unknown-operation';
|
|
10
|
+
/**
|
|
11
|
+
* The runner contract (#2213, RFC §7) as a list of checks, so a broken runner can be
|
|
12
|
+
* shown to fail the one it breaks. {@link scopeStoreRunnerContractSuite} runs them all.
|
|
13
|
+
*
|
|
14
|
+
* Every call goes through the kernel's callers both hosts use (`callScopeStore`,
|
|
15
|
+
* `callReservedStoreOperation`), so an answer is held to the same envelope and result
|
|
16
|
+
* schema a host would hold it to.
|
|
17
|
+
*
|
|
18
|
+
* A restart is `stop()` then `start()` on the same runner instance: what a host does
|
|
19
|
+
* between a `close` and the next first store call. A driver's repositories must survive
|
|
20
|
+
* it. Blob cells in an export are not asserted (their spelling over JSON is not settled).
|
|
21
|
+
*/
|
|
22
|
+
export function scopeStoreRunnerChecks(fixture) {
|
|
23
|
+
/** A started runner for one check, stopped after it whatever happens. */
|
|
24
|
+
const withRunner = async (body) => {
|
|
25
|
+
const runner = await fixture.makeRunner();
|
|
26
|
+
await runner.start();
|
|
27
|
+
try {
|
|
28
|
+
await body(runner);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await runner.stop();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const call = (runner, address, request) => callScopeStore((r) => runner.invoke(address, r), request.operation, request.input);
|
|
35
|
+
const read = (runner, address) => call(runner, address, fixture.read);
|
|
36
|
+
const write = (runner, address) => call(runner, address, fixture.write);
|
|
37
|
+
const reserved = (runner, address, operation, input) => callReservedStoreOperation((r) => runner.invoke(address, r), operation, input);
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
name: 'a stop without a start resolves',
|
|
41
|
+
async run() {
|
|
42
|
+
const runner = await fixture.makeRunner();
|
|
43
|
+
await runner.stop();
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'a driver call answers the envelope, and an unknown operation answers its error',
|
|
48
|
+
async run() {
|
|
49
|
+
await withRunner(async (runner) => {
|
|
50
|
+
const address = freshAddress();
|
|
51
|
+
expect(storeResponse.safeParse(await runner.invoke(address, fixture.read)).success).toBe(true);
|
|
52
|
+
const unknown = storeResponse.parse(await runner.invoke(address, { operation: UNKNOWN_OPERATION }));
|
|
53
|
+
expect('error' in unknown).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'a write is read back on its own address',
|
|
59
|
+
async run() {
|
|
60
|
+
await withRunner(async (runner) => {
|
|
61
|
+
const address = freshAddress();
|
|
62
|
+
const before = await read(runner, address);
|
|
63
|
+
await write(runner, address);
|
|
64
|
+
expect(await read(runner, address)).not.toEqual(before);
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'the object is keyed on the whole address: tenant, vertical, scope and binding',
|
|
70
|
+
async run() {
|
|
71
|
+
await withRunner(async (runner) => {
|
|
72
|
+
const address = freshAddress();
|
|
73
|
+
const empty = await read(runner, freshAddress());
|
|
74
|
+
await write(runner, address);
|
|
75
|
+
// Each neighbour differs from the written address in exactly one field.
|
|
76
|
+
const neighbours = [
|
|
77
|
+
{ ...address, tenantId: ulid() },
|
|
78
|
+
{ ...address, vertical: 'conformance-other' },
|
|
79
|
+
{ ...address, scopeId: ulid() },
|
|
80
|
+
{ ...address, binding: 'OTHER' },
|
|
81
|
+
];
|
|
82
|
+
for (const neighbour of neighbours) {
|
|
83
|
+
expect(await read(runner, neighbour), JSON.stringify(neighbour)).toEqual(empty);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'a stop and a start serve the same repositories',
|
|
90
|
+
async run() {
|
|
91
|
+
await withRunner(async (runner) => {
|
|
92
|
+
const address = freshAddress();
|
|
93
|
+
await write(runner, address);
|
|
94
|
+
const written = await read(runner, address);
|
|
95
|
+
await runner.stop();
|
|
96
|
+
await runner.start();
|
|
97
|
+
expect(await read(runner, address)).toEqual(written);
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'substrat.size answers { bytes }',
|
|
103
|
+
async run() {
|
|
104
|
+
await withRunner(async (runner) => {
|
|
105
|
+
const address = freshAddress();
|
|
106
|
+
await write(runner, address);
|
|
107
|
+
const { bytes } = await reserved(runner, address, 'substrat.size');
|
|
108
|
+
expect(Number.isInteger(bytes)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'substrat.export answers { tables } in the scope-dump table shape',
|
|
114
|
+
async run() {
|
|
115
|
+
await withRunner(async (runner) => {
|
|
116
|
+
const address = freshAddress();
|
|
117
|
+
await write(runner, address);
|
|
118
|
+
const { tables } = await reserved(runner, address, 'substrat.export');
|
|
119
|
+
expect(tables.length).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'substrat.import replaces the whole object and answers the tables it wrote',
|
|
125
|
+
async run() {
|
|
126
|
+
await withRunner(async (runner) => {
|
|
127
|
+
const source = freshAddress();
|
|
128
|
+
const target = freshAddress();
|
|
129
|
+
await write(runner, source);
|
|
130
|
+
await write(runner, target);
|
|
131
|
+
await write(runner, target);
|
|
132
|
+
// The target holds more than the source, so a merge cannot pass for a replace.
|
|
133
|
+
expect(await read(runner, target)).not.toEqual(await read(runner, source));
|
|
134
|
+
const { tables } = await reserved(runner, source, 'substrat.export');
|
|
135
|
+
const imported = await reserved(runner, target, 'substrat.import', { tables });
|
|
136
|
+
expect(imported.tables).toBe(tables.length);
|
|
137
|
+
expect(await read(runner, target)).toEqual(await read(runner, source));
|
|
138
|
+
});
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'substrat.wipe answers {} and leaves an empty object, also where nothing was written',
|
|
143
|
+
async run() {
|
|
144
|
+
await withRunner(async (runner) => {
|
|
145
|
+
const address = freshAddress();
|
|
146
|
+
const empty = await read(runner, freshAddress());
|
|
147
|
+
await write(runner, address);
|
|
148
|
+
expect(await reserved(runner, address, 'substrat.wipe')).toEqual({});
|
|
149
|
+
expect(await read(runner, address)).toEqual(empty);
|
|
150
|
+
expect(await reserved(runner, freshAddress(), 'substrat.wipe')).toEqual({});
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'substrat.erase answers { erased }, and a repeat erases nothing more',
|
|
156
|
+
async run() {
|
|
157
|
+
await withRunner(async (runner) => {
|
|
158
|
+
const address = freshAddress();
|
|
159
|
+
const subjectId = ulid();
|
|
160
|
+
await write(runner, address);
|
|
161
|
+
await reserved(runner, address, 'substrat.erase', { subjectId });
|
|
162
|
+
expect(await reserved(runner, address, 'substrat.erase', { subjectId })).toEqual({ erased: 0 });
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The runner contract a scope-store driver's runner passes (#2213). A driver runs it in its
|
|
170
|
+
* own repository, against its own runner: the platform runs only the trivial driver.
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* scopeStoreRunnerContractSuite('graph', {
|
|
174
|
+
* makeRunner: () => graphRunner({ dir }),
|
|
175
|
+
* write: { operation: 'apply', input: { … } },
|
|
176
|
+
* read: { operation: 'inspect' },
|
|
177
|
+
* });
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export function scopeStoreRunnerContractSuite(driver, fixture) {
|
|
181
|
+
describe(`scope-store runner (#2213): ${driver}`, () => {
|
|
182
|
+
for (const check of scopeStoreRunnerChecks(fixture)) {
|
|
183
|
+
it(check.name, () => check.run());
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=scope-store-runner-suite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-store-runner-suite.js","sourceRoot":"","sources":["../src/scope-store-runner-suite.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-store-suite.d.ts","sourceRoot":"","sources":["../src/scope-store-suite.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"scope-store-suite.d.ts","sourceRoot":"","sources":["../src/scope-store-suite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,gBAAgB,EAAwB,MAAM,sBAAsB,CAAC;AAGzG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAiD9D;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,CACX,WAAW,EAAE;IAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAA;KAAE,EAAE,CAAA;CAAE,KACtE,OAAO,CAAC,gBAAgB,CAAC,GAC7B,IAAI,CA8jBN"}
|
|
@@ -1,104 +1,53 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
-
import { errorCodeOf, platformActorId, principalId, scopeId, tenantId } from '@substrat-run/contracts';
|
|
2
|
+
import { errorCodeOf, platformActorId, principalId, scopeId, storeEraseInput, tenantId } from '@substrat-run/contracts';
|
|
3
3
|
import { ulid } from '@substrat-run/kernel';
|
|
4
4
|
import { STORE_CALL_SINK_KEY, tallyStoreCall } from '@substrat-run/kernel/invocation-line';
|
|
5
|
+
import { counterScopeStoreRunner, invalidStoreInput, objectNameOf } from './counter-runner.js';
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* The trivial driver, with what this suite reads back about the host's calls:
|
|
8
|
+
* `address` (what the host handed in), `erased` (the subjects erased on this object),
|
|
9
|
+
* `wiped` (every object wiped), and `fail-wipe` (`true` makes each later `substrat.wipe`
|
|
10
|
+
* answer 503, until `false`).
|
|
8
11
|
*/
|
|
9
|
-
function
|
|
10
|
-
const
|
|
12
|
+
function instrumentedCounterRunner() {
|
|
13
|
+
const counter = counterScopeStoreRunner();
|
|
11
14
|
const erased = new Map();
|
|
12
15
|
const wiped = new Set();
|
|
13
|
-
// `fail-wipe` makes every later `substrat.wipe` fail, until it is turned off again.
|
|
14
16
|
let failWipe = false;
|
|
15
|
-
const key = (address) => `${address.tenantId}/${address.vertical}/${address.scopeId}/${address.binding}`;
|
|
16
17
|
return {
|
|
17
|
-
|
|
18
|
-
async start() { },
|
|
19
|
-
async stop() { },
|
|
18
|
+
...counter,
|
|
20
19
|
async invoke(address, request) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
result: {
|
|
27
|
-
tenantId: address.tenantId,
|
|
28
|
-
vertical: address.vertical,
|
|
29
|
-
scopeId: address.scopeId,
|
|
30
|
-
binding: address.binding,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
if (request.operation === 'increment') {
|
|
35
|
-
const by = typeof request.input === 'object' &&
|
|
36
|
-
request.input !== null &&
|
|
37
|
-
typeof Reflect.get(request.input, 'by') === 'number'
|
|
38
|
-
? Number(Reflect.get(request.input, 'by'))
|
|
39
|
-
: 1;
|
|
40
|
-
const next = current + by;
|
|
41
|
-
objects.set(key(address), next);
|
|
42
|
-
return { result: next };
|
|
43
|
-
}
|
|
44
|
-
if (request.operation === 'substrat.size')
|
|
45
|
-
return { result: { bytes: current } };
|
|
46
|
-
if (request.operation === 'fail-wipe') {
|
|
47
|
-
failWipe = request.input === true;
|
|
48
|
-
return { result: failWipe };
|
|
49
|
-
}
|
|
50
|
-
if (request.operation === 'substrat.wipe') {
|
|
51
|
-
if (failWipe)
|
|
52
|
-
return { error: { code: 'unavailable', status: 503 } };
|
|
53
|
-
objects.set(key(address), 0);
|
|
54
|
-
wiped.add(key(address));
|
|
55
|
-
return { result: {} };
|
|
56
|
-
}
|
|
57
|
-
if (request.operation === 'wiped')
|
|
58
|
-
return { result: [...wiped] };
|
|
59
|
-
if (request.operation === 'substrat.erase') {
|
|
60
|
-
const subjectId = typeof request.input === 'object' && request.input !== null
|
|
61
|
-
? Reflect.get(request.input, 'subjectId')
|
|
62
|
-
: undefined;
|
|
63
|
-
if (typeof subjectId !== 'string' || subjectId.length === 0) {
|
|
64
|
-
return { error: { code: 'validation_failed', status: 400 } };
|
|
20
|
+
const id = objectNameOf(address);
|
|
21
|
+
switch (request.operation) {
|
|
22
|
+
case 'address': {
|
|
23
|
+
const { tenantId, vertical, scopeId, binding } = address;
|
|
24
|
+
return { result: { tenantId, vertical, scopeId, binding } };
|
|
65
25
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const tables = typeof request.input === 'object' && request.input !== null
|
|
91
|
-
? Reflect.get(request.input, 'tables')
|
|
92
|
-
: undefined;
|
|
93
|
-
const count = Array.isArray(tables) ? tables.length : 0;
|
|
94
|
-
const first = Array.isArray(tables) ? tables[0] : undefined;
|
|
95
|
-
const rows = typeof first === 'object' && first !== null ? Reflect.get(first, 'rows') : undefined;
|
|
96
|
-
const row = Array.isArray(rows) ? rows[0] : undefined;
|
|
97
|
-
const cell = Array.isArray(row) ? row[0] : undefined;
|
|
98
|
-
objects.set(key(address), typeof cell === 'number' ? cell : 0);
|
|
99
|
-
return { result: { tables: count } };
|
|
26
|
+
case 'erased':
|
|
27
|
+
return { result: [...(erased.get(id) ?? [])] };
|
|
28
|
+
case 'wiped':
|
|
29
|
+
return { result: [...wiped] };
|
|
30
|
+
case 'fail-wipe':
|
|
31
|
+
if (typeof request.input !== 'boolean')
|
|
32
|
+
return invalidStoreInput('fail-wipe takes a boolean');
|
|
33
|
+
failWipe = request.input;
|
|
34
|
+
return { result: failWipe };
|
|
35
|
+
case 'substrat.wipe':
|
|
36
|
+
if (failWipe)
|
|
37
|
+
return { error: { code: 'unavailable', status: 503 } };
|
|
38
|
+
wiped.add(id);
|
|
39
|
+
return counter.invoke(address, request);
|
|
40
|
+
case 'substrat.erase': {
|
|
41
|
+
const answer = await counter.invoke(address, request);
|
|
42
|
+
if ('result' in answer) {
|
|
43
|
+
const { subjectId } = storeEraseInput.parse(request.input);
|
|
44
|
+
erased.set(id, (erased.get(id) ?? new Set()).add(subjectId));
|
|
45
|
+
}
|
|
46
|
+
return answer;
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
return counter.invoke(address, request);
|
|
100
50
|
}
|
|
101
|
-
return { error: { code: 'unknown_operation', status: 400 } };
|
|
102
51
|
},
|
|
103
52
|
};
|
|
104
53
|
}
|
|
@@ -117,7 +66,7 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
117
66
|
const scopeB = scopeId.parse(ulid());
|
|
118
67
|
const otherVertical = scopeId.parse(ulid());
|
|
119
68
|
beforeAll(async () => {
|
|
120
|
-
fixture = await makeFixture({ runners: [{ binding: 'GRAPH', runner:
|
|
69
|
+
fixture = await makeFixture({ runners: [{ binding: 'GRAPH', runner: instrumentedCounterRunner() }] });
|
|
121
70
|
host = fixture.host;
|
|
122
71
|
await host.admin.createTenant(staff, { id: tenant, slug: `t-${tenant.toLowerCase()}`, name: 'T' });
|
|
123
72
|
await host.provisionScope(staff, { tenantId: tenant, scopeId: scopeA, vertical: 'docs' });
|
|
@@ -149,7 +98,7 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
149
98
|
release = resolve;
|
|
150
99
|
});
|
|
151
100
|
const slow = {
|
|
152
|
-
|
|
101
|
+
driver: 'slow',
|
|
153
102
|
async start() {
|
|
154
103
|
started += 1;
|
|
155
104
|
await gate;
|
|
@@ -222,12 +171,15 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
222
171
|
const refusal = await host.admin.scopeStoreSizes(staff, tenant, scopeId.parse(ulid())).then(() => undefined, (err) => err);
|
|
223
172
|
expect(errorCodeOf(refusal)).toBe('not_found');
|
|
224
173
|
});
|
|
225
|
-
it(
|
|
174
|
+
it("an erasure reaches the store and takes the subject's part, and a second one counts zero", async () => {
|
|
226
175
|
const subject = ulid();
|
|
176
|
+
const store = await host.getScopeStore(principal, tenant, scopeA, 'GRAPH');
|
|
177
|
+
const before = Number(await store.invoke('read'));
|
|
178
|
+
await store.invoke('increment', { by: 2, subjectId: subject });
|
|
227
179
|
const first = await host.admin.shredSubject(staff, tenant, scopeA, subject);
|
|
228
180
|
expect(first.storeErasures).toEqual([{ binding: 'GRAPH', erased: 1 }]);
|
|
229
|
-
const store = await host.getScopeStore(principal, tenant, scopeA, 'GRAPH');
|
|
230
181
|
expect(await store.invoke('erased')).toEqual([subject]);
|
|
182
|
+
expect(await store.invoke('read')).toBe(before);
|
|
231
183
|
const again = await host.admin.shredSubject(staff, tenant, scopeA, subject);
|
|
232
184
|
expect(again.storeErasures).toEqual([{ binding: 'GRAPH', erased: 0 }]);
|
|
233
185
|
});
|
|
@@ -246,7 +198,7 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
246
198
|
await store.invoke('increment', { by: 4 });
|
|
247
199
|
const dump = await host.admin.exportScope(staff, tenant, scopeB);
|
|
248
200
|
expect(dump.stores).toEqual([
|
|
249
|
-
expect.objectContaining({ binding: 'GRAPH',
|
|
201
|
+
expect.objectContaining({ binding: 'GRAPH', driver: 'counter' }),
|
|
250
202
|
]);
|
|
251
203
|
await store.invoke('increment', { by: 1 });
|
|
252
204
|
await host.restoreScope(staff, tenant, scopeB, dump);
|
|
@@ -262,16 +214,16 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
262
214
|
const refusal = await host.restoreScope(staff, tenant, scopeB, {
|
|
263
215
|
...dump,
|
|
264
216
|
tables: [...dump.tables, PROBE_TABLE],
|
|
265
|
-
stores: [...dump.stores, { binding: 'MISSING',
|
|
217
|
+
stores: [...dump.stores, { binding: 'MISSING', driver: 'counter', tables: [] }],
|
|
266
218
|
}).then(() => undefined, (err) => err);
|
|
267
219
|
expect(errorCodeOf(refusal)).toBe('not_found');
|
|
268
220
|
// Neither the restore of the dump's GRAPH (before + 3) nor its tables were written.
|
|
269
221
|
expect(await store.invoke('read')).toBe(before + 4);
|
|
270
222
|
expect(await tableNames(scopeB)).not.toContain(PROBE_TABLE.name);
|
|
271
223
|
});
|
|
272
|
-
// L8: a store of another
|
|
224
|
+
// L8: a store of another driver would be handed to this driver's import, and a destination
|
|
273
225
|
// with no vertical has no store object to load into. Both refuse before any write.
|
|
274
|
-
it('a restore whose store is of another
|
|
226
|
+
it('a restore whose store is of another driver writes nothing', async () => {
|
|
275
227
|
const store = await host.getScopeStore(principal, tenant, scopeB, 'GRAPH');
|
|
276
228
|
const before = Number(await store.invoke('read'));
|
|
277
229
|
const dump = await host.admin.exportScope(staff, tenant, scopeB);
|
|
@@ -279,15 +231,15 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
279
231
|
const refusal = await host.restoreScope(staff, tenant, scopeB, {
|
|
280
232
|
...dump,
|
|
281
233
|
tables: [...dump.tables, PROBE_TABLE],
|
|
282
|
-
stores: dump.stores.map((one) => ({ ...one,
|
|
234
|
+
stores: dump.stores.map((one) => ({ ...one, driver: 'other' })),
|
|
283
235
|
}).then(() => undefined, (err) => err);
|
|
284
236
|
expect(errorCodeOf(refusal)).toBe('conflict');
|
|
285
237
|
expect(await store.invoke('read')).toBe(before + 2);
|
|
286
238
|
expect(await tableNames(scopeB)).not.toContain(PROBE_TABLE.name);
|
|
287
239
|
});
|
|
288
240
|
// CodeRabbit on #2244: the host does not parse the dump it is handed, so a store with no
|
|
289
|
-
//
|
|
290
|
-
it('a restore whose store names no
|
|
241
|
+
// driver skipped the driver comparison above and was imported.
|
|
242
|
+
it('a restore whose store names no driver writes nothing', async () => {
|
|
291
243
|
const store = await host.getScopeStore(principal, tenant, scopeB, 'GRAPH');
|
|
292
244
|
const before = Number(await store.invoke('read'));
|
|
293
245
|
const dump = await host.admin.exportScope(staff, tenant, scopeB);
|
|
@@ -336,7 +288,7 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
336
288
|
tenantId: tenant,
|
|
337
289
|
scopeId: dest,
|
|
338
290
|
vertical: 'docs',
|
|
339
|
-
}, { ...dump, stores: [...dump.stores, { binding: 'MISSING',
|
|
291
|
+
}, { ...dump, stores: [...dump.stores, { binding: 'MISSING', driver: 'counter', tables: [] }] }).then(() => undefined, (err) => err);
|
|
340
292
|
expect(errorCodeOf(refusal)).toBe('not_found');
|
|
341
293
|
expect(await host.admin.getScopeRecord(staff, tenant, dest)).toBeUndefined();
|
|
342
294
|
});
|
|
@@ -383,7 +335,7 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
383
335
|
expect(refusal).toBeDefined();
|
|
384
336
|
expect((await host.admin.getScopeRecord(staff, tenant, doomed))?.status).toBe('archived');
|
|
385
337
|
const dump = await host.admin.exportScope(staff, tenant, doomed);
|
|
386
|
-
expect(dump.stores.find((one) => one.binding === 'GRAPH')?.tables[0]?.rows).toEqual([[7]]);
|
|
338
|
+
expect(dump.stores.find((one) => one.binding === 'GRAPH')?.tables[0]?.rows).toEqual([[null, 7]]);
|
|
387
339
|
expect(dump.tables.map((table) => table.name)).toContain(PROBE_TABLE.name);
|
|
388
340
|
}
|
|
389
341
|
finally {
|
|
@@ -423,7 +375,7 @@ export function scopeStoreContractSuite(adapterName, makeFixture) {
|
|
|
423
375
|
expect(await loud.invoke('read')).toEqual(expect.any(Number));
|
|
424
376
|
});
|
|
425
377
|
it('with no observer, a call lands on the request now running; a passed one wins (#2245)', async () => {
|
|
426
|
-
// The platform entry installs this slot (vertical-host's `
|
|
378
|
+
// The platform entry installs this slot (vertical-host's `runRecording`);
|
|
427
379
|
// here a fixed tally stands in for the request's record.
|
|
428
380
|
const key = Symbol.for(STORE_CALL_SINK_KEY);
|
|
429
381
|
const g = globalThis;
|