@electric-ax/agents-server-conformance-tests 0.1.5 → 0.1.7
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/index.cjs +43 -29
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +43 -29
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -678,7 +678,13 @@ async function executeStep(ctx, step) {
|
|
|
678
678
|
let entityUrl = ctx.currentEntityUrl;
|
|
679
679
|
if (step.kind === `killUrl`) entityUrl = step.url;
|
|
680
680
|
if (!entityUrl) throw new Error(`No current entity — did you spawn first?`);
|
|
681
|
-
const res = await electricAgentsFetch$1(ctx.baseUrl, entityUrl
|
|
681
|
+
const res = await electricAgentsFetch$1(ctx.baseUrl, `${entityUrl}/signal`, {
|
|
682
|
+
method: `POST`,
|
|
683
|
+
body: JSON.stringify({
|
|
684
|
+
signal: `SIGKILL`,
|
|
685
|
+
reason: `Killed by conformance test`
|
|
686
|
+
})
|
|
687
|
+
});
|
|
682
688
|
(0, vitest.expect)(res.status).toBe(200);
|
|
683
689
|
ctx.history.push({
|
|
684
690
|
type: `entity_killed`,
|
|
@@ -1195,8 +1201,8 @@ function checkStreamPathsMatchEntityUrl(history) {
|
|
|
1195
1201
|
/**
|
|
1196
1202
|
* Spec S4 — Safety: entity status transitions must be valid.
|
|
1197
1203
|
* spawning → running is valid (at spawn time)
|
|
1198
|
-
* running/idle →
|
|
1199
|
-
*
|
|
1204
|
+
* running/idle → killed is valid (at kill time)
|
|
1205
|
+
* killed → running is NOT valid
|
|
1200
1206
|
* Soundness: Sound | Completeness: Incomplete (only checks observed status reads)
|
|
1201
1207
|
*/
|
|
1202
1208
|
function checkStatusTransitionsValid(history) {
|
|
@@ -1205,7 +1211,7 @@ function checkStatusTransitionsValid(history) {
|
|
|
1205
1211
|
if (event.type === `entity_spawned`) lastStatus.set(event.entityUrl, event.status);
|
|
1206
1212
|
if (event.type === `entity_status_checked`) {
|
|
1207
1213
|
const prev = lastStatus.get(event.entityUrl);
|
|
1208
|
-
if (prev === `
|
|
1214
|
+
if (prev === `killed`) (0, vitest.expect)(event.status, `Safety: entity ${event.entityUrl} transitioned from killed to ${event.status}`).toBe(`killed`);
|
|
1209
1215
|
lastStatus.set(event.entityUrl, event.status);
|
|
1210
1216
|
}
|
|
1211
1217
|
}
|
|
@@ -1324,14 +1330,14 @@ function enabledElectricAgentsActions(model) {
|
|
|
1324
1330
|
* Next relation — pure state transition for the model.
|
|
1325
1331
|
* The real server execution happens separately in the property test.
|
|
1326
1332
|
*/
|
|
1327
|
-
function applyElectricAgentsAction(model, action, targetIdx) {
|
|
1333
|
+
function applyElectricAgentsAction(model, action, targetIdx, opts) {
|
|
1328
1334
|
switch (action) {
|
|
1329
1335
|
case `register_type`: {
|
|
1330
1336
|
const typeNum = model.entityTypes.length;
|
|
1331
1337
|
return {
|
|
1332
1338
|
...model,
|
|
1333
1339
|
entityTypes: [...model.entityTypes, {
|
|
1334
|
-
name: `prop-type-${typeNum}`,
|
|
1340
|
+
name: opts?.typeName ?? `prop-type-${typeNum}`,
|
|
1335
1341
|
hasCreationSchema: false,
|
|
1336
1342
|
hasInputSchemas: false,
|
|
1337
1343
|
hasOutputSchemas: false
|
|
@@ -1381,7 +1387,7 @@ function applyElectricAgentsAction(model, action, targetIdx) {
|
|
|
1381
1387
|
const e = entities[targetIdx];
|
|
1382
1388
|
entities[targetIdx] = {
|
|
1383
1389
|
...e,
|
|
1384
|
-
status: `
|
|
1390
|
+
status: `killed`
|
|
1385
1391
|
};
|
|
1386
1392
|
return {
|
|
1387
1393
|
...model,
|
|
@@ -1902,8 +1908,8 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1902
1908
|
name: `status-filter-agent`,
|
|
1903
1909
|
description: `Test entity type for status filter`,
|
|
1904
1910
|
creation_schema: { type: `object` }
|
|
1905
|
-
}).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `
|
|
1906
|
-
(0, vitest.expect)(ctx.lastListResult.every((e) => e.status === `
|
|
1911
|
+
}).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `killed` }).custom(async (ctx) => {
|
|
1912
|
+
(0, vitest.expect)(ctx.lastListResult.every((e) => e.status === `killed`)).toBe(true);
|
|
1907
1913
|
}).list({ status: `running` }).custom(async (ctx) => {
|
|
1908
1914
|
(0, vitest.expect)(ctx.lastListResult.every((e) => [`running`, `idle`].includes(e.status))).toBe(true);
|
|
1909
1915
|
}).run();
|
|
@@ -1918,7 +1924,7 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1918
1924
|
name: `kill-test-agent`,
|
|
1919
1925
|
description: `Test entity type for kill`,
|
|
1920
1926
|
creation_schema: { type: `object` }
|
|
1921
|
-
}).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`
|
|
1927
|
+
}).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`killed`).expectSendError(`NOT_RUNNING`, 409).run());
|
|
1922
1928
|
(0, vitest.test)(`stream data persists after kill`, () => electricAgents(config.baseUrl).subscription(`/kill-persist-agent/**`, `kill-persist-sub`).registerType({
|
|
1923
1929
|
name: `kill-persist-agent`,
|
|
1924
1930
|
description: `Test entity type for kill persistence`,
|
|
@@ -1927,8 +1933,9 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1927
1933
|
const msgs = ctx.lastStreamMessages;
|
|
1928
1934
|
const msgReceived = msgs.find((m) => m.type === `inbox`);
|
|
1929
1935
|
(0, vitest.expect)(msgReceived.value?.payload).toEqual({ before: `kill` });
|
|
1930
|
-
const
|
|
1931
|
-
(0, vitest.expect)(
|
|
1936
|
+
const signal = msgs.find((m) => m.type === `signal`);
|
|
1937
|
+
(0, vitest.expect)(signal).toBeDefined();
|
|
1938
|
+
(0, vitest.expect)(signal.value?.signal).toBe(`SIGKILL`);
|
|
1932
1939
|
}).run());
|
|
1933
1940
|
vitest.test.skip(`multiple entities under same subscription`, () => {
|
|
1934
1941
|
let firstEntityUrl = null;
|
|
@@ -1942,7 +1949,10 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1942
1949
|
}).spawn(`multi-test-worker`, `entity-2`).custom(async (ctx) => {
|
|
1943
1950
|
secondEntityUrl = ctx.currentEntityUrl;
|
|
1944
1951
|
}).custom(async (ctx) => {
|
|
1945
|
-
const res = await electricAgentsFetch(ctx.baseUrl, firstEntityUrl
|
|
1952
|
+
const res = await electricAgentsFetch(ctx.baseUrl, `${firstEntityUrl}/signal`, {
|
|
1953
|
+
method: `POST`,
|
|
1954
|
+
body: JSON.stringify({ signal: `SIGKILL` })
|
|
1955
|
+
});
|
|
1946
1956
|
(0, vitest.expect)(res.status).toBe(200);
|
|
1947
1957
|
ctx.history.push({
|
|
1948
1958
|
type: `entity_killed`,
|
|
@@ -1964,8 +1974,8 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1964
1974
|
description: `Test entity type for E2E lifecycle`,
|
|
1965
1975
|
creation_schema: { type: `object` }
|
|
1966
1976
|
}).spawn(`e2e-test-agent`, `entity-1`).expectStatus(`running`).send({ task: `do-something` }).expectWebhook().expectEntityContext({ type: `e2e-test-agent` }).respondDone().readStream().expectStreamContains(`inbox`).kill().custom(async (ctx) => {
|
|
1967
|
-
const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`
|
|
1968
|
-
(0, vitest.expect)(entity.status).toBe(`
|
|
1977
|
+
const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`killed`]);
|
|
1978
|
+
(0, vitest.expect)(entity.status).toBe(`killed`);
|
|
1969
1979
|
}).expectSendError(`NOT_RUNNING`, 409).run());
|
|
1970
1980
|
});
|
|
1971
1981
|
(0, vitest.describe)(`Electric Agents Entity Type Registration`, () => {
|
|
@@ -2563,7 +2573,6 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2563
2573
|
const runId = `prop-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2564
2574
|
const baseUrl = config.baseUrl;
|
|
2565
2575
|
const entityUrls = [];
|
|
2566
|
-
const registeredTypeNames = [];
|
|
2567
2576
|
const scenario = electricAgents(baseUrl);
|
|
2568
2577
|
let model = {
|
|
2569
2578
|
entityTypes: [],
|
|
@@ -2571,21 +2580,21 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2571
2580
|
nextEntityNum: 0
|
|
2572
2581
|
};
|
|
2573
2582
|
let entityCounter = 0;
|
|
2583
|
+
let typeCounter = 0;
|
|
2574
2584
|
for (const action of actions) {
|
|
2575
2585
|
const valid = enabledElectricAgentsActions(model);
|
|
2576
2586
|
if (!valid.includes(action)) continue;
|
|
2577
2587
|
switch (action) {
|
|
2578
2588
|
case `register_type`: {
|
|
2579
|
-
const typeNum =
|
|
2589
|
+
const typeNum = typeCounter++;
|
|
2580
2590
|
const typeName = `prop-type-${runId}-${typeNum}`;
|
|
2581
|
-
registeredTypeNames.push(typeName);
|
|
2582
2591
|
scenario.subscription(`/${typeName}/**`, `prop-sub-${typeName}`);
|
|
2583
2592
|
scenario.registerType({
|
|
2584
2593
|
name: typeName,
|
|
2585
2594
|
description: `Property-based test type ${typeNum}`,
|
|
2586
2595
|
creation_schema: { type: `object` }
|
|
2587
2596
|
});
|
|
2588
|
-
model = applyElectricAgentsAction(model, `register_type
|
|
2597
|
+
model = applyElectricAgentsAction(model, `register_type`, void 0, { typeName });
|
|
2589
2598
|
break;
|
|
2590
2599
|
}
|
|
2591
2600
|
case `delete_type`: {
|
|
@@ -2593,14 +2602,13 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2593
2602
|
const deletableIndices = model.entityTypes.map((t, i) => !runningModelTypeNames.has(t.name) ? i : -1).filter((i) => i >= 0);
|
|
2594
2603
|
if (deletableIndices.length === 0) break;
|
|
2595
2604
|
const deleteIdx = deletableIndices[0];
|
|
2596
|
-
scenario.deleteType(
|
|
2597
|
-
registeredTypeNames.splice(deleteIdx, 1);
|
|
2605
|
+
scenario.deleteType(model.entityTypes[deleteIdx].name);
|
|
2598
2606
|
model = applyElectricAgentsAction(model, `delete_type`);
|
|
2599
2607
|
break;
|
|
2600
2608
|
}
|
|
2601
2609
|
case `spawn`: {
|
|
2602
|
-
if (
|
|
2603
|
-
const typeName =
|
|
2610
|
+
if (model.entityTypes.length === 0) break;
|
|
2611
|
+
const typeName = model.entityTypes[0].name;
|
|
2604
2612
|
const instanceId = `entity-${entityCounter++}`;
|
|
2605
2613
|
scenario.spawn(typeName, instanceId);
|
|
2606
2614
|
scenario.custom(async (ctx) => {
|
|
@@ -2645,7 +2653,10 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2645
2653
|
scenario.custom(async (ctx) => {
|
|
2646
2654
|
const url = entityUrls[targetIdx];
|
|
2647
2655
|
if (!url) return;
|
|
2648
|
-
const res = await electricAgentsFetch(ctx.baseUrl, url
|
|
2656
|
+
const res = await electricAgentsFetch(ctx.baseUrl, `${url}/signal`, {
|
|
2657
|
+
method: `POST`,
|
|
2658
|
+
body: JSON.stringify({ signal: `SIGKILL` })
|
|
2659
|
+
});
|
|
2649
2660
|
(0, vitest.expect)(res.status).toBe(200);
|
|
2650
2661
|
ctx.history.push({
|
|
2651
2662
|
type: `entity_killed`,
|
|
@@ -3019,7 +3030,10 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
3019
3030
|
description: `test`
|
|
3020
3031
|
})
|
|
3021
3032
|
});
|
|
3022
|
-
const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345`, {
|
|
3033
|
+
const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345/signal`, {
|
|
3034
|
+
method: `POST`,
|
|
3035
|
+
body: JSON.stringify({ signal: `SIGKILL` })
|
|
3036
|
+
});
|
|
3023
3037
|
(0, vitest.expect)(res.status).toBe(404);
|
|
3024
3038
|
const body = await res.json();
|
|
3025
3039
|
(0, vitest.expect)(body.error.code).toBe(`NOT_FOUND`);
|
|
@@ -3452,8 +3466,8 @@ function runCliConformanceTests(config) {
|
|
|
3452
3466
|
name: `cli-kill-type`,
|
|
3453
3467
|
description: `Type for CLI kill test`
|
|
3454
3468
|
}).setupSubscription(`/cli-kill-type/**`, `cli-kill-sub`).exec(`spawn`, `/cli-kill-type/${id}`).expectExitCode(0).exec(`kill`, `/cli-kill-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
|
|
3455
|
-
const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`
|
|
3456
|
-
(0, vitest.expect)(entity.status).toBe(`
|
|
3469
|
+
const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`killed`]);
|
|
3470
|
+
(0, vitest.expect)(entity.status).toBe(`killed`);
|
|
3457
3471
|
}).run();
|
|
3458
3472
|
}, 15e3);
|
|
3459
3473
|
(0, vitest.test)(`kill nonexistent entity fails`, async () => {
|
|
@@ -3472,8 +3486,8 @@ function runCliConformanceTests(config) {
|
|
|
3472
3486
|
const entity = await res.json();
|
|
3473
3487
|
(0, vitest.expect)([`running`, `idle`]).toContain(entity.status);
|
|
3474
3488
|
}).exec(`send`, `/cli-lifecycle-type/${id}`, `test message`).expectExitCode(0).expectStdout(/Message sent/).exec(`inspect`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/running|idle/).exec(`kill`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
|
|
3475
|
-
const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`
|
|
3476
|
-
(0, vitest.expect)(entity.status).toBe(`
|
|
3489
|
+
const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`killed`]);
|
|
3490
|
+
(0, vitest.expect)(entity.status).toBe(`killed`);
|
|
3477
3491
|
}).exec(`ps`, `--status`, `running`).expectExitCode(0).expectStdoutNot(new RegExp(id)).run();
|
|
3478
3492
|
}, 15e3);
|
|
3479
3493
|
(0, vitest.test)(`send to stopped entity fails`, async () => {
|
package/dist/index.d.cts
CHANGED
|
@@ -49,7 +49,7 @@ declare function runMockAgentCliTests(config: MockAgentCliTestOptions): void; //
|
|
|
49
49
|
* .respondDone()
|
|
50
50
|
* .expectStatus('running')
|
|
51
51
|
* .kill()
|
|
52
|
-
* .expectStatus('
|
|
52
|
+
* .expectStatus('killed')
|
|
53
53
|
* .run()
|
|
54
54
|
*/
|
|
55
55
|
type HistoryEvent = {
|
|
@@ -410,7 +410,7 @@ interface EntityTypeModel {
|
|
|
410
410
|
interface EntityModel {
|
|
411
411
|
url: string;
|
|
412
412
|
typeName: string;
|
|
413
|
-
status: `running` | `
|
|
413
|
+
status: `running` | `killed`;
|
|
414
414
|
messageCount: number;
|
|
415
415
|
}
|
|
416
416
|
/**
|
|
@@ -438,7 +438,9 @@ declare function enabledElectricAgentsActions(model: ElectricAgentsWorldModel):
|
|
|
438
438
|
* Next relation — pure state transition for the model.
|
|
439
439
|
* The real server execution happens separately in the property test.
|
|
440
440
|
*/
|
|
441
|
-
declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number
|
|
441
|
+
declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number, opts?: {
|
|
442
|
+
typeName?: string;
|
|
443
|
+
}): ElectricAgentsWorldModel;
|
|
442
444
|
declare function electricAgents(baseUrl: string): ElectricAgentsScenario;
|
|
443
445
|
declare function checkStateProtocolInvariants(events: Array<Record<string, unknown>>): void; //#endregion
|
|
444
446
|
//#region src/cli-dsl.d.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -49,7 +49,7 @@ declare function runMockAgentCliTests(config: MockAgentCliTestOptions): void; //
|
|
|
49
49
|
* .respondDone()
|
|
50
50
|
* .expectStatus('running')
|
|
51
51
|
* .kill()
|
|
52
|
-
* .expectStatus('
|
|
52
|
+
* .expectStatus('killed')
|
|
53
53
|
* .run()
|
|
54
54
|
*/
|
|
55
55
|
type HistoryEvent = {
|
|
@@ -410,7 +410,7 @@ interface EntityTypeModel {
|
|
|
410
410
|
interface EntityModel {
|
|
411
411
|
url: string;
|
|
412
412
|
typeName: string;
|
|
413
|
-
status: `running` | `
|
|
413
|
+
status: `running` | `killed`;
|
|
414
414
|
messageCount: number;
|
|
415
415
|
}
|
|
416
416
|
/**
|
|
@@ -438,7 +438,9 @@ declare function enabledElectricAgentsActions(model: ElectricAgentsWorldModel):
|
|
|
438
438
|
* Next relation — pure state transition for the model.
|
|
439
439
|
* The real server execution happens separately in the property test.
|
|
440
440
|
*/
|
|
441
|
-
declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number
|
|
441
|
+
declare function applyElectricAgentsAction(model: ElectricAgentsWorldModel, action: ElectricAgentsAction, targetIdx?: number, opts?: {
|
|
442
|
+
typeName?: string;
|
|
443
|
+
}): ElectricAgentsWorldModel;
|
|
442
444
|
declare function electricAgents(baseUrl: string): ElectricAgentsScenario;
|
|
443
445
|
declare function checkStateProtocolInvariants(events: Array<Record<string, unknown>>): void; //#endregion
|
|
444
446
|
//#region src/cli-dsl.d.ts
|
package/dist/index.js
CHANGED
|
@@ -654,7 +654,13 @@ async function executeStep(ctx, step) {
|
|
|
654
654
|
let entityUrl = ctx.currentEntityUrl;
|
|
655
655
|
if (step.kind === `killUrl`) entityUrl = step.url;
|
|
656
656
|
if (!entityUrl) throw new Error(`No current entity — did you spawn first?`);
|
|
657
|
-
const res = await electricAgentsFetch$1(ctx.baseUrl, entityUrl
|
|
657
|
+
const res = await electricAgentsFetch$1(ctx.baseUrl, `${entityUrl}/signal`, {
|
|
658
|
+
method: `POST`,
|
|
659
|
+
body: JSON.stringify({
|
|
660
|
+
signal: `SIGKILL`,
|
|
661
|
+
reason: `Killed by conformance test`
|
|
662
|
+
})
|
|
663
|
+
});
|
|
658
664
|
expect(res.status).toBe(200);
|
|
659
665
|
ctx.history.push({
|
|
660
666
|
type: `entity_killed`,
|
|
@@ -1171,8 +1177,8 @@ function checkStreamPathsMatchEntityUrl(history) {
|
|
|
1171
1177
|
/**
|
|
1172
1178
|
* Spec S4 — Safety: entity status transitions must be valid.
|
|
1173
1179
|
* spawning → running is valid (at spawn time)
|
|
1174
|
-
* running/idle →
|
|
1175
|
-
*
|
|
1180
|
+
* running/idle → killed is valid (at kill time)
|
|
1181
|
+
* killed → running is NOT valid
|
|
1176
1182
|
* Soundness: Sound | Completeness: Incomplete (only checks observed status reads)
|
|
1177
1183
|
*/
|
|
1178
1184
|
function checkStatusTransitionsValid(history) {
|
|
@@ -1181,7 +1187,7 @@ function checkStatusTransitionsValid(history) {
|
|
|
1181
1187
|
if (event.type === `entity_spawned`) lastStatus.set(event.entityUrl, event.status);
|
|
1182
1188
|
if (event.type === `entity_status_checked`) {
|
|
1183
1189
|
const prev = lastStatus.get(event.entityUrl);
|
|
1184
|
-
if (prev === `
|
|
1190
|
+
if (prev === `killed`) expect(event.status, `Safety: entity ${event.entityUrl} transitioned from killed to ${event.status}`).toBe(`killed`);
|
|
1185
1191
|
lastStatus.set(event.entityUrl, event.status);
|
|
1186
1192
|
}
|
|
1187
1193
|
}
|
|
@@ -1300,14 +1306,14 @@ function enabledElectricAgentsActions(model) {
|
|
|
1300
1306
|
* Next relation — pure state transition for the model.
|
|
1301
1307
|
* The real server execution happens separately in the property test.
|
|
1302
1308
|
*/
|
|
1303
|
-
function applyElectricAgentsAction(model, action, targetIdx) {
|
|
1309
|
+
function applyElectricAgentsAction(model, action, targetIdx, opts) {
|
|
1304
1310
|
switch (action) {
|
|
1305
1311
|
case `register_type`: {
|
|
1306
1312
|
const typeNum = model.entityTypes.length;
|
|
1307
1313
|
return {
|
|
1308
1314
|
...model,
|
|
1309
1315
|
entityTypes: [...model.entityTypes, {
|
|
1310
|
-
name: `prop-type-${typeNum}`,
|
|
1316
|
+
name: opts?.typeName ?? `prop-type-${typeNum}`,
|
|
1311
1317
|
hasCreationSchema: false,
|
|
1312
1318
|
hasInputSchemas: false,
|
|
1313
1319
|
hasOutputSchemas: false
|
|
@@ -1357,7 +1363,7 @@ function applyElectricAgentsAction(model, action, targetIdx) {
|
|
|
1357
1363
|
const e = entities[targetIdx];
|
|
1358
1364
|
entities[targetIdx] = {
|
|
1359
1365
|
...e,
|
|
1360
|
-
status: `
|
|
1366
|
+
status: `killed`
|
|
1361
1367
|
};
|
|
1362
1368
|
return {
|
|
1363
1369
|
...model,
|
|
@@ -1878,8 +1884,8 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1878
1884
|
name: `status-filter-agent`,
|
|
1879
1885
|
description: `Test entity type for status filter`,
|
|
1880
1886
|
creation_schema: { type: `object` }
|
|
1881
|
-
}).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `
|
|
1882
|
-
expect(ctx.lastListResult.every((e) => e.status === `
|
|
1887
|
+
}).spawn(`status-filter-agent`, `entity-1`).spawn(`status-filter-agent`, `entity-2`).kill().list({ status: `killed` }).custom(async (ctx) => {
|
|
1888
|
+
expect(ctx.lastListResult.every((e) => e.status === `killed`)).toBe(true);
|
|
1883
1889
|
}).list({ status: `running` }).custom(async (ctx) => {
|
|
1884
1890
|
expect(ctx.lastListResult.every((e) => [`running`, `idle`].includes(e.status))).toBe(true);
|
|
1885
1891
|
}).run();
|
|
@@ -1894,7 +1900,7 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1894
1900
|
name: `kill-test-agent`,
|
|
1895
1901
|
description: `Test entity type for kill`,
|
|
1896
1902
|
creation_schema: { type: `object` }
|
|
1897
|
-
}).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`
|
|
1903
|
+
}).spawn(`kill-test-agent`, `entity-1`).kill().expectStatus(`killed`).expectSendError(`NOT_RUNNING`, 409).run());
|
|
1898
1904
|
test(`stream data persists after kill`, () => electricAgents(config.baseUrl).subscription(`/kill-persist-agent/**`, `kill-persist-sub`).registerType({
|
|
1899
1905
|
name: `kill-persist-agent`,
|
|
1900
1906
|
description: `Test entity type for kill persistence`,
|
|
@@ -1903,8 +1909,9 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1903
1909
|
const msgs = ctx.lastStreamMessages;
|
|
1904
1910
|
const msgReceived = msgs.find((m) => m.type === `inbox`);
|
|
1905
1911
|
expect(msgReceived.value?.payload).toEqual({ before: `kill` });
|
|
1906
|
-
const
|
|
1907
|
-
expect(
|
|
1912
|
+
const signal = msgs.find((m) => m.type === `signal`);
|
|
1913
|
+
expect(signal).toBeDefined();
|
|
1914
|
+
expect(signal.value?.signal).toBe(`SIGKILL`);
|
|
1908
1915
|
}).run());
|
|
1909
1916
|
test.skip(`multiple entities under same subscription`, () => {
|
|
1910
1917
|
let firstEntityUrl = null;
|
|
@@ -1918,7 +1925,10 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1918
1925
|
}).spawn(`multi-test-worker`, `entity-2`).custom(async (ctx) => {
|
|
1919
1926
|
secondEntityUrl = ctx.currentEntityUrl;
|
|
1920
1927
|
}).custom(async (ctx) => {
|
|
1921
|
-
const res = await electricAgentsFetch(ctx.baseUrl, firstEntityUrl
|
|
1928
|
+
const res = await electricAgentsFetch(ctx.baseUrl, `${firstEntityUrl}/signal`, {
|
|
1929
|
+
method: `POST`,
|
|
1930
|
+
body: JSON.stringify({ signal: `SIGKILL` })
|
|
1931
|
+
});
|
|
1922
1932
|
expect(res.status).toBe(200);
|
|
1923
1933
|
ctx.history.push({
|
|
1924
1934
|
type: `entity_killed`,
|
|
@@ -1940,8 +1950,8 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
1940
1950
|
description: `Test entity type for E2E lifecycle`,
|
|
1941
1951
|
creation_schema: { type: `object` }
|
|
1942
1952
|
}).spawn(`e2e-test-agent`, `entity-1`).expectStatus(`running`).send({ task: `do-something` }).expectWebhook().expectEntityContext({ type: `e2e-test-agent` }).respondDone().readStream().expectStreamContains(`inbox`).kill().custom(async (ctx) => {
|
|
1943
|
-
const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`
|
|
1944
|
-
expect(entity.status).toBe(`
|
|
1953
|
+
const entity = await pollEntityStatus(ctx.baseUrl, ctx.currentEntityUrl, [`killed`]);
|
|
1954
|
+
expect(entity.status).toBe(`killed`);
|
|
1945
1955
|
}).expectSendError(`NOT_RUNNING`, 409).run());
|
|
1946
1956
|
});
|
|
1947
1957
|
describe(`Electric Agents Entity Type Registration`, () => {
|
|
@@ -2539,7 +2549,6 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2539
2549
|
const runId = `prop-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2540
2550
|
const baseUrl = config.baseUrl;
|
|
2541
2551
|
const entityUrls = [];
|
|
2542
|
-
const registeredTypeNames = [];
|
|
2543
2552
|
const scenario = electricAgents(baseUrl);
|
|
2544
2553
|
let model = {
|
|
2545
2554
|
entityTypes: [],
|
|
@@ -2547,21 +2556,21 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2547
2556
|
nextEntityNum: 0
|
|
2548
2557
|
};
|
|
2549
2558
|
let entityCounter = 0;
|
|
2559
|
+
let typeCounter = 0;
|
|
2550
2560
|
for (const action of actions) {
|
|
2551
2561
|
const valid = enabledElectricAgentsActions(model);
|
|
2552
2562
|
if (!valid.includes(action)) continue;
|
|
2553
2563
|
switch (action) {
|
|
2554
2564
|
case `register_type`: {
|
|
2555
|
-
const typeNum =
|
|
2565
|
+
const typeNum = typeCounter++;
|
|
2556
2566
|
const typeName = `prop-type-${runId}-${typeNum}`;
|
|
2557
|
-
registeredTypeNames.push(typeName);
|
|
2558
2567
|
scenario.subscription(`/${typeName}/**`, `prop-sub-${typeName}`);
|
|
2559
2568
|
scenario.registerType({
|
|
2560
2569
|
name: typeName,
|
|
2561
2570
|
description: `Property-based test type ${typeNum}`,
|
|
2562
2571
|
creation_schema: { type: `object` }
|
|
2563
2572
|
});
|
|
2564
|
-
model = applyElectricAgentsAction(model, `register_type
|
|
2573
|
+
model = applyElectricAgentsAction(model, `register_type`, void 0, { typeName });
|
|
2565
2574
|
break;
|
|
2566
2575
|
}
|
|
2567
2576
|
case `delete_type`: {
|
|
@@ -2569,14 +2578,13 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2569
2578
|
const deletableIndices = model.entityTypes.map((t, i) => !runningModelTypeNames.has(t.name) ? i : -1).filter((i) => i >= 0);
|
|
2570
2579
|
if (deletableIndices.length === 0) break;
|
|
2571
2580
|
const deleteIdx = deletableIndices[0];
|
|
2572
|
-
scenario.deleteType(
|
|
2573
|
-
registeredTypeNames.splice(deleteIdx, 1);
|
|
2581
|
+
scenario.deleteType(model.entityTypes[deleteIdx].name);
|
|
2574
2582
|
model = applyElectricAgentsAction(model, `delete_type`);
|
|
2575
2583
|
break;
|
|
2576
2584
|
}
|
|
2577
2585
|
case `spawn`: {
|
|
2578
|
-
if (
|
|
2579
|
-
const typeName =
|
|
2586
|
+
if (model.entityTypes.length === 0) break;
|
|
2587
|
+
const typeName = model.entityTypes[0].name;
|
|
2580
2588
|
const instanceId = `entity-${entityCounter++}`;
|
|
2581
2589
|
scenario.spawn(typeName, instanceId);
|
|
2582
2590
|
scenario.custom(async (ctx) => {
|
|
@@ -2621,7 +2629,10 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2621
2629
|
scenario.custom(async (ctx) => {
|
|
2622
2630
|
const url = entityUrls[targetIdx];
|
|
2623
2631
|
if (!url) return;
|
|
2624
|
-
const res = await electricAgentsFetch(ctx.baseUrl, url
|
|
2632
|
+
const res = await electricAgentsFetch(ctx.baseUrl, `${url}/signal`, {
|
|
2633
|
+
method: `POST`,
|
|
2634
|
+
body: JSON.stringify({ signal: `SIGKILL` })
|
|
2635
|
+
});
|
|
2625
2636
|
expect(res.status).toBe(200);
|
|
2626
2637
|
ctx.history.push({
|
|
2627
2638
|
type: `entity_killed`,
|
|
@@ -2995,7 +3006,10 @@ function runElectricAgentsConformanceTests(config) {
|
|
|
2995
3006
|
description: `test`
|
|
2996
3007
|
})
|
|
2997
3008
|
});
|
|
2998
|
-
const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345`, {
|
|
3009
|
+
const res = await electricAgentsFetch(config.baseUrl, `/${typeName}/nonexistent-id-12345/signal`, {
|
|
3010
|
+
method: `POST`,
|
|
3011
|
+
body: JSON.stringify({ signal: `SIGKILL` })
|
|
3012
|
+
});
|
|
2999
3013
|
expect(res.status).toBe(404);
|
|
3000
3014
|
const body = await res.json();
|
|
3001
3015
|
expect(body.error.code).toBe(`NOT_FOUND`);
|
|
@@ -3428,8 +3442,8 @@ function runCliConformanceTests(config) {
|
|
|
3428
3442
|
name: `cli-kill-type`,
|
|
3429
3443
|
description: `Type for CLI kill test`
|
|
3430
3444
|
}).setupSubscription(`/cli-kill-type/**`, `cli-kill-sub`).exec(`spawn`, `/cli-kill-type/${id}`).expectExitCode(0).exec(`kill`, `/cli-kill-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
|
|
3431
|
-
const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`
|
|
3432
|
-
expect(entity.status).toBe(`
|
|
3445
|
+
const entity = await pollEntityStatus(baseUrl, `/cli-kill-type/${id}`, [`killed`]);
|
|
3446
|
+
expect(entity.status).toBe(`killed`);
|
|
3433
3447
|
}).run();
|
|
3434
3448
|
}, 15e3);
|
|
3435
3449
|
test(`kill nonexistent entity fails`, async () => {
|
|
@@ -3448,8 +3462,8 @@ function runCliConformanceTests(config) {
|
|
|
3448
3462
|
const entity = await res.json();
|
|
3449
3463
|
expect([`running`, `idle`]).toContain(entity.status);
|
|
3450
3464
|
}).exec(`send`, `/cli-lifecycle-type/${id}`, `test message`).expectExitCode(0).expectStdout(/Message sent/).exec(`inspect`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/running|idle/).exec(`kill`, `/cli-lifecycle-type/${id}`).expectExitCode(0).expectStdout(/Killed/).verifyApi(async (baseUrl) => {
|
|
3451
|
-
const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`
|
|
3452
|
-
expect(entity.status).toBe(`
|
|
3465
|
+
const entity = await pollEntityStatus(baseUrl, `/cli-lifecycle-type/${id}`, [`killed`]);
|
|
3466
|
+
expect(entity.status).toBe(`killed`);
|
|
3453
3467
|
}).exec(`ps`, `--status`, `running`).expectExitCode(0).expectStdoutNot(new RegExp(id)).run();
|
|
3454
3468
|
}, 15e3);
|
|
3455
3469
|
test(`send to stopped entity fails`, async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-ax/agents-server-conformance-tests",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Conformance test suite for Electric Agents server implementations",
|
|
5
5
|
"author": "Durable Stream contributors",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dist"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@durable-streams/client": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/client@
|
|
37
|
+
"@durable-streams/client": "https://pkg.pr.new/durable-streams/durable-streams/@durable-streams/client@5d5c217",
|
|
38
38
|
"@electric-sql/client": "^1.5.18",
|
|
39
39
|
"fast-check": "^4.6.0",
|
|
40
40
|
"vitest": "^4.1.0"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@mariozechner/pi-agent-core": "^0.70.2",
|
|
44
44
|
"tsdown": "^0.9.0",
|
|
45
45
|
"typescript": "^5.0.0",
|
|
46
|
-
"@electric-ax/agents-server": "0.4.
|
|
46
|
+
"@electric-ax/agents-server": "0.4.7"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18.0.0"
|