@substrat-run/contract-tests 0.138.0 → 0.140.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-text-fixtures.d.ts +17 -0
- package/dist/attachment-text-fixtures.d.ts.map +1 -1
- package/dist/attachment-text-fixtures.js +344 -4
- package/dist/attachment-text-fixtures.js.map +1 -1
- package/dist/attachment-text-suite.d.ts +16 -1
- package/dist/attachment-text-suite.d.ts.map +1 -1
- package/dist/attachment-text-suite.js +212 -28
- package/dist/attachment-text-suite.js.map +1 -1
- package/dist/capability-suite.d.ts.map +1 -1
- package/dist/capability-suite.js +5 -0
- package/dist/capability-suite.js.map +1 -1
- package/dist/caused-by-module.d.ts +23 -0
- package/dist/caused-by-module.d.ts.map +1 -0
- package/dist/caused-by-module.js +56 -0
- package/dist/caused-by-module.js.map +1 -0
- package/dist/caused-by-suite.d.ts +28 -0
- package/dist/caused-by-suite.d.ts.map +1 -0
- package/dist/caused-by-suite.js +223 -0
- package/dist/caused-by-suite.js.map +1 -0
- package/dist/connect-link-suite.d.ts +19 -0
- package/dist/connect-link-suite.d.ts.map +1 -0
- package/dist/connect-link-suite.js +215 -0
- package/dist/connect-link-suite.js.map +1 -0
- package/dist/entity-state-migration-suite.d.ts +16 -0
- package/dist/entity-state-migration-suite.d.ts.map +1 -0
- package/dist/entity-state-migration-suite.js +305 -0
- package/dist/entity-state-migration-suite.js.map +1 -0
- package/dist/entity-state-module.d.ts +317 -0
- package/dist/entity-state-module.d.ts.map +1 -0
- package/dist/entity-state-module.js +278 -0
- package/dist/entity-state-module.js.map +1 -0
- package/dist/entity-state-suite.d.ts +9 -0
- package/dist/entity-state-suite.d.ts.map +1 -0
- package/dist/entity-state-suite.js +557 -0
- package/dist/entity-state-suite.js.map +1 -0
- package/dist/entity-trash-module.d.ts +439 -0
- package/dist/entity-trash-module.d.ts.map +1 -0
- package/dist/entity-trash-module.js +287 -0
- package/dist/entity-trash-module.js.map +1 -0
- package/dist/entity-trash-suite.d.ts +59 -0
- package/dist/entity-trash-suite.d.ts.map +1 -0
- package/dist/entity-trash-suite.js +568 -0
- package/dist/entity-trash-suite.js.map +1 -0
- package/dist/erasure-module.d.ts +259 -0
- package/dist/erasure-module.d.ts.map +1 -0
- package/dist/erasure-module.js +251 -0
- package/dist/erasure-module.js.map +1 -0
- package/dist/findings-atomic-suite.d.ts +21 -0
- package/dist/findings-atomic-suite.d.ts.map +1 -0
- package/dist/findings-atomic-suite.js +131 -0
- package/dist/findings-atomic-suite.js.map +1 -0
- package/dist/findings-suite.d.ts +11 -0
- package/dist/findings-suite.d.ts.map +1 -0
- package/dist/findings-suite.js +391 -0
- package/dist/findings-suite.js.map +1 -0
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/list-suite.d.ts.map +1 -1
- package/dist/list-suite.js +101 -1
- package/dist/list-suite.js.map +1 -1
- package/dist/membership-executor-suite.d.ts +6 -2
- package/dist/membership-executor-suite.d.ts.map +1 -1
- package/dist/membership-executor-suite.js +318 -21
- package/dist/membership-executor-suite.js.map +1 -1
- package/dist/migration-comments-suite.d.ts +3 -0
- package/dist/migration-comments-suite.d.ts.map +1 -0
- package/dist/migration-comments-suite.js +92 -0
- package/dist/migration-comments-suite.js.map +1 -0
- package/dist/migration-comments.d.ts +3 -0
- package/dist/migration-comments.d.ts.map +1 -0
- package/dist/migration-comments.js +50 -0
- package/dist/migration-comments.js.map +1 -0
- package/dist/migration-digest-suite.d.ts +3 -0
- package/dist/migration-digest-suite.d.ts.map +1 -0
- package/dist/migration-digest-suite.js +175 -0
- package/dist/migration-digest-suite.js.map +1 -0
- package/dist/modules.d.ts +759 -0
- package/dist/modules.d.ts.map +1 -1
- package/dist/modules.js +158 -6
- package/dist/modules.js.map +1 -1
- package/dist/peer-suite.d.ts +2 -1
- package/dist/peer-suite.d.ts.map +1 -1
- package/dist/peer-suite.js +103 -1
- package/dist/peer-suite.js.map +1 -1
- package/dist/permission-suite.d.ts.map +1 -1
- package/dist/permission-suite.js +590 -2
- package/dist/permission-suite.js.map +1 -1
- package/dist/scope-host-suite.d.ts.map +1 -1
- package/dist/scope-host-suite.js +102 -1
- package/dist/scope-host-suite.js.map +1 -1
- package/dist/split-cases.d.ts +12 -0
- package/dist/split-cases.d.ts.map +1 -0
- package/dist/split-cases.js +59 -0
- package/dist/split-cases.js.map +1 -0
- package/dist/subject-erasure-suite.d.ts +6 -0
- package/dist/subject-erasure-suite.d.ts.map +1 -0
- package/dist/subject-erasure-suite.js +287 -0
- package/dist/subject-erasure-suite.js.map +1 -0
- package/dist/switch-audit-fault.d.ts +48 -0
- package/dist/switch-audit-fault.d.ts.map +1 -0
- package/dist/switch-audit-fault.js +80 -0
- package/dist/switch-audit-fault.js.map +1 -0
- package/dist/system-switch-suite.d.ts +2 -1
- package/dist/system-switch-suite.d.ts.map +1 -1
- package/dist/system-switch-suite.js +90 -0
- package/dist/system-switch-suite.js.map +1 -1
- package/dist/vertical-events-suite.d.ts +55 -0
- package/dist/vertical-events-suite.d.ts.map +1 -1
- package/dist/vertical-events-suite.js +55 -0
- package/dist/vertical-events-suite.js.map +1 -1
- package/package.json +3 -3
package/dist/scope-host-suite.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
2
|
import { connectorCalls, connectorTestFetch, resetConnectorCalls } from './connector-fixture.js';
|
|
3
|
-
import { connectionId, dataSubjectId, domainEvent, errorCodeOf, moduleId, SCOPE_GATE_REASONS, eventId, instant, moduleManifest, orgId, AUTO_ADMISSION_NOTE, permissionKey, platformActorId, platformRequestId, principalId, scopeId, tenantId, SCOPE_QUERY_ROW_MAX, KERNEL_AUTHORED_EVENT_TYPES, SWEEP_RUNS_KIND, SCOPE_TABLE_PAGE_MAX, } from '@substrat-run/contracts';
|
|
3
|
+
import { connectionId, dataSubjectId, domainEvent, errorCodeOf, moduleId, SCOPE_GATE_REASONS, eventId, instant, moduleManifest, orgId, AUTO_ADMISSION_NOTE, permissionKey, platformActorId, platformRequestId, principalId, scopeId, tenantId, SCOPE_QUERY_ROW_MAX, KERNEL_AUTHORED_EVENT_TYPES, EMAIL_SENT, PLATFORM_REQUEST_ENTITY_TYPE, SEND_EMAIL_KIND, SWEEP_RUNS_KIND, SCOPE_TABLE_PAGE_MAX, } from '@substrat-run/contracts';
|
|
4
4
|
import { INERT_SCOPE_REASON, isSearchIndexTable, platformIntentFailureMessage, REDACTED_DELIVERY_NOTE, REDACTED_FAILURE_NOTE, REDACTED_INTENT_MARKER, runPlatformSweep, startPlatformSweeper, createUlid, ulid, } from '@substrat-run/kernel';
|
|
5
5
|
import { billedMod, contractTestBareOps, contractTestInitialModules, gateModManifest, lateMod, TEST_LIFECYCLE, testModManifest, victimModManifest, } from './modules.js';
|
|
6
6
|
/**
|
|
@@ -273,6 +273,45 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
273
273
|
expect(settled.status).toBe('done');
|
|
274
274
|
expect(JSON.parse(settled.result)).toEqual({ scopeId: 'NEWSITE' });
|
|
275
275
|
});
|
|
276
|
+
it('a settle carrying an outcome event writes it once, as the kernel, with the settle (#2102)', async () => {
|
|
277
|
+
const stub = await host.getScope(alice, t1, s1);
|
|
278
|
+
const mail = { to: 'ada@example.com', subject: 'Hej', html: '<p>Hej</p>', text: 'Hej' };
|
|
279
|
+
const id = await stub.invoke('platform/request', { kind: SEND_EMAIL_KIND, payload: mail });
|
|
280
|
+
const mine = () => stub
|
|
281
|
+
.invoke('platform/outcome-events')
|
|
282
|
+
.then((rows) => rows.filter((r) => JSON.parse(r.payload).request === id));
|
|
283
|
+
const event = (request) => ({
|
|
284
|
+
type: EMAIL_SENT,
|
|
285
|
+
entity: { entityType: PLATFORM_REQUEST_ENTITY_TYPE, entityId: id },
|
|
286
|
+
payload: {
|
|
287
|
+
request: platformRequestId.parse(request),
|
|
288
|
+
about: null,
|
|
289
|
+
sender: 'platform',
|
|
290
|
+
messageId: 'msg-1',
|
|
291
|
+
attempts: 1,
|
|
292
|
+
code: null,
|
|
293
|
+
status: null,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
// Refused before anything is written: an event on a `pending` settle, or one naming
|
|
297
|
+
// another request. The row stays exactly as it was.
|
|
298
|
+
await expect(host.settlePlatformRequest(t1, s1, platformRequestId.parse(id), { status: 'pending', event: event(id) })).rejects.toThrow();
|
|
299
|
+
await expect(host.settlePlatformRequest(t1, s1, platformRequestId.parse(id), { status: 'done', event: event(ulid()) })).rejects.toThrow();
|
|
300
|
+
expect((await host.listPlatformRequests(t1, s1)).find((r) => r.id === id)?.attempts).toBe(0);
|
|
301
|
+
expect(await mine()).toEqual([]);
|
|
302
|
+
await host.settlePlatformRequest(t1, s1, platformRequestId.parse(id), { status: 'done', event: event(id) });
|
|
303
|
+
const written = await mine();
|
|
304
|
+
expect(written).toHaveLength(1);
|
|
305
|
+
expect(written[0].type).toBe(EMAIL_SENT);
|
|
306
|
+
expect(JSON.parse(written[0].actor)).toEqual({ system: '@substrat-run/kernel' });
|
|
307
|
+
expect([written[0].entity_type, written[0].entity_id]).toEqual([PLATFORM_REQUEST_ENTITY_TYPE, id]);
|
|
308
|
+
expect(JSON.parse(written[0].payload)).toMatchObject({ request: id, messageId: 'msg-1', sender: 'platform' });
|
|
309
|
+
// A repeated settle finds the row terminal: it moves nothing, so it writes nothing.
|
|
310
|
+
await host.settlePlatformRequest(t1, s1, platformRequestId.parse(id), { status: 'failed', event: event(id) });
|
|
311
|
+
expect(await mine()).toHaveLength(1);
|
|
312
|
+
const row = (await stub.invoke('platform/read-requests')).find((r) => r.id === id);
|
|
313
|
+
expect(row.status).toBe('done');
|
|
314
|
+
});
|
|
276
315
|
it('a transient failure keeps the intent pending and preserves a two-phase result (retry)', async () => {
|
|
277
316
|
const stub = await host.getScope(alice, t1, s1);
|
|
278
317
|
const id = await stub.invoke('platform/request', { kind: 'provision-sibling', payload: {} });
|
|
@@ -4400,6 +4439,20 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
4400
4439
|
expect(rotated?.after).toMatchObject({ id: row.id, rotatedBy: officeAdmin });
|
|
4401
4440
|
expect(JSON.stringify(log)).not.toContain('tok-relay-rotated');
|
|
4402
4441
|
});
|
|
4442
|
+
it('a rotation sets the expiry, keeps it when omitted, and clears it with null (#2100)', async () => {
|
|
4443
|
+
const [row] = await host.admin.listConnections(staff, { tenantId: t1, vertical: 'callout' });
|
|
4444
|
+
const expiry = async () => (await host.admin.listConnections(staff, { tenantId: t1, vertical: 'callout' })).find((c) => c.id === row.id)
|
|
4445
|
+
.expiresAt;
|
|
4446
|
+
const at = '2027-10-07T12:00:00.000Z';
|
|
4447
|
+
await host.admin.updateConnectionSecret(staff, row.id, { accessToken: 'tok-dated' }, at);
|
|
4448
|
+
expect(await expiry()).toBe(at);
|
|
4449
|
+
await host.admin.updateConnectionSecret(staff, row.id, { accessToken: 'tok-kept' });
|
|
4450
|
+
expect(await expiry()).toBe(at);
|
|
4451
|
+
// The credential that had an end was replaced by one that has none (a certificate → a
|
|
4452
|
+
// client secret): the old end must not stay on the connection, reading as this one's.
|
|
4453
|
+
await host.admin.updateConnectionSecret(staff, row.id, { accessToken: 'tok-undated' }, null);
|
|
4454
|
+
expect(await expiry()).toBeNull();
|
|
4455
|
+
});
|
|
4403
4456
|
it('records health, and a success clears a prior error', async () => {
|
|
4404
4457
|
const [row] = await host.admin.listConnections(staff, {
|
|
4405
4458
|
tenantId: t1,
|
|
@@ -6335,6 +6388,43 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
6335
6388
|
]);
|
|
6336
6389
|
expect(await relinked(item('rl6'))).toHaveLength(2);
|
|
6337
6390
|
});
|
|
6391
|
+
/**
|
|
6392
|
+
* The documented way to DETACH one parent of a multi-parent child (#2044): relink it
|
|
6393
|
+
* onto a parent it already has. ticket0 takes a conversation's public thread off a
|
|
6394
|
+
* widget session this way, so it is contract, not an accident of `writeEdge`. The
|
|
6395
|
+
* expiries are planted (only a restore can hold one) to pin what the verb does to
|
|
6396
|
+
* them: `to` is left permanent, as every edge a verb leaves live is, and a bystander
|
|
6397
|
+
* parent keeps its own.
|
|
6398
|
+
*/
|
|
6399
|
+
it('relink onto a live parent detaches `from` only: `to` left live and permanent, others untouched', async () => {
|
|
6400
|
+
const FUTURE = '2999-01-01T00:00:00.000Z';
|
|
6401
|
+
await plant([
|
|
6402
|
+
{ subject: 'item:rl-dt', object: 'box:rb1' }, // from
|
|
6403
|
+
{ subject: 'item:rl-dt', object: 'box:rb2', expires_at: FUTURE }, // to, already live
|
|
6404
|
+
{ subject: 'item:rl-dt', object: 'box:rb3', expires_at: FUTURE }, // a bystander
|
|
6405
|
+
]);
|
|
6406
|
+
await move(item('rl-dt'), box('rb1'), box('rb2'));
|
|
6407
|
+
const table = (await host.admin.exportScope(staff, t1, s1)).tables.find((t) => t.name === '_substrat_tuples');
|
|
6408
|
+
const col = (name) => table.columns.indexOf(name);
|
|
6409
|
+
const rows = table.rows
|
|
6410
|
+
.filter((r) => r[col('subject')] === 'item:rl-dt')
|
|
6411
|
+
.map((r) => ({
|
|
6412
|
+
object: r[col('object')],
|
|
6413
|
+
revoked: r[col('revoked_at')] !== null,
|
|
6414
|
+
expires: r[col('expires_at')],
|
|
6415
|
+
}))
|
|
6416
|
+
.sort((x, y) => String(x.object).localeCompare(String(y.object)));
|
|
6417
|
+
expect(rows).toEqual([
|
|
6418
|
+
{ object: 'box:rb1', revoked: true, expires: null },
|
|
6419
|
+
{ object: 'box:rb2', revoked: false, expires: null },
|
|
6420
|
+
{ object: 'box:rb3', revoked: false, expires: FUTURE },
|
|
6421
|
+
]);
|
|
6422
|
+
// One move, recorded as one; the live `to` was neither revived nor re-announced.
|
|
6423
|
+
const events = await relinked(item('rl-dt'));
|
|
6424
|
+
expect(events).toHaveLength(1);
|
|
6425
|
+
expect(events[0]).toMatchObject({ payload: { child: item('rl-dt'), from: box('rb1'), to: box('rb2') } });
|
|
6426
|
+
expect(await relinked(item('rl-dt'), 'entity.linked')).toEqual([]);
|
|
6427
|
+
});
|
|
6338
6428
|
it('link revives an edge relink moved away from — INSERT OR IGNORE would have kept it dead', async () => {
|
|
6339
6429
|
await link(item('rl7'), box('rb1'));
|
|
6340
6430
|
// The twin first: a first-time link records nothing, as it never has.
|
|
@@ -7070,6 +7160,15 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
7070
7160
|
expect(await host.admin.listEntitlements(staff, t)).toHaveLength(1);
|
|
7071
7161
|
expect((await host.admin.listOrgs(staff, t)).filter((o) => o.id === org)).toHaveLength(1);
|
|
7072
7162
|
expect((await host.admin.resolveIdentity(t, `oidc:reap-${t.toLowerCase()}`, 'x1'))?.principal).toBe(person);
|
|
7163
|
+
// #1748: a finding and a suppress rule are the tenant's too.
|
|
7164
|
+
await host.admin.recordOpsFailure({ actor: staff, operation: 'reap.finding', tenantId: t, message: 'x' });
|
|
7165
|
+
await host.admin.createFindingRule(staff, t, {
|
|
7166
|
+
operation: 'reap.other',
|
|
7167
|
+
expiresAt: instant.parse(new Date(Date.now() + 86_400_000).toISOString()),
|
|
7168
|
+
reason: 'reap fixture',
|
|
7169
|
+
});
|
|
7170
|
+
expect(await host.admin.listFindings(staff, { tenantId: t })).toHaveLength(1);
|
|
7171
|
+
expect(await host.admin.listFindingRules(staff, t)).toHaveLength(1);
|
|
7073
7172
|
// Reap only follows the reversible delete state.
|
|
7074
7173
|
await expectRefusal(host.admin.reapTenant(staff, t), 'conflict', /not deleting/);
|
|
7075
7174
|
// The caller reaps the scope first (the sweep/route orchestrates this above the
|
|
@@ -7088,6 +7187,8 @@ export function scopeHostContractSuite(adapterName, makeFixture, opts = {}) {
|
|
|
7088
7187
|
expect(await host.admin.listEntitlements(staff, t)).toHaveLength(0);
|
|
7089
7188
|
expect((await host.admin.listOrgs(staff, t)).filter((o) => o.id === org)).toHaveLength(0);
|
|
7090
7189
|
expect(await host.admin.resolveIdentity(t, `oidc:reap-${t.toLowerCase()}`, 'x1')).toBeUndefined();
|
|
7190
|
+
expect(await host.admin.listFindings(staff, { tenantId: t })).toHaveLength(0);
|
|
7191
|
+
expect(await host.admin.listFindingRules(staff, t)).toHaveLength(0);
|
|
7091
7192
|
// The admin log is KEPT WHOLE — the witness outlives the tenant — and records the reap.
|
|
7092
7193
|
const log = await host.admin.auditLog(staff, { tenantId: t });
|
|
7093
7194
|
expect(log.some((r) => r.action === 'createTenant')).toBe(true);
|