@powerhousedao/switchboard 6.2.3-dev.30 → 6.2.3-dev.31
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/.tsbuild/test/workflow/harness.d.ts +12 -1
- package/.tsbuild/test/workflow/harness.d.ts.map +1 -1
- package/.tsbuild/test/workflow/oauth.test.d.ts +2 -0
- package/.tsbuild/test/workflow/oauth.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow/webhook-trigger-test.test.d.ts +2 -0
- package/.tsbuild/test/workflow/webhook-trigger-test.test.d.ts.map +1 -0
- package/.tsbuild/tsconfig.tsbuildinfo +1 -1
- package/CHANGELOG.md +27 -0
- package/dist/index.mjs +1 -1
- package/dist/{server-Cf0DB8sW.mjs → server-CKKn0aXV.mjs} +429 -45
- package/dist/server-CKKn0aXV.mjs.map +1 -0
- package/dist/server.d.mts +2 -0
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +3 -3
- package/package.json +15 -15
- package/test/workflow/harness.ts +61 -9
- package/test/workflow/oauth.test.ts +214 -0
- package/test/workflow/resolvers.test.ts +194 -3
- package/test/workflow/webhook-dedupe.test.ts +3 -3
- package/test/workflow/webhook-signed.test.ts +2 -2
- package/test/workflow/webhook-trigger-test.test.ts +82 -0
- package/test/workflow/webhook-unsigned.test.ts +19 -6
- package/test/workflow-runtime.test.ts +14 -0
- package/dist/server-Cf0DB8sW.mjs.map +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Testing a the core webhook trigger over HTTP: the next real delivery to the
|
|
2
|
+
// workflow's endpoint becomes the trigger's test sample, and runs nothing.
|
|
3
|
+
import {
|
|
4
|
+
actions,
|
|
5
|
+
reducer,
|
|
6
|
+
utils,
|
|
7
|
+
type WorkflowDocument,
|
|
8
|
+
} from "@powerhousedao/workflow/document-models/workflow";
|
|
9
|
+
import type { Action } from "document-model";
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_WORKFLOW,
|
|
13
|
+
startWebhookHost,
|
|
14
|
+
WEBHOOK_TRIGGER,
|
|
15
|
+
type WebhookHost,
|
|
16
|
+
} from "./harness.js";
|
|
17
|
+
|
|
18
|
+
const CTX = { headers: {}, db: {}, user: { address: "0xabc" } } as never;
|
|
19
|
+
|
|
20
|
+
let document: WorkflowDocument;
|
|
21
|
+
let host: WebhookHost;
|
|
22
|
+
|
|
23
|
+
function apply(...list: Action[]): void {
|
|
24
|
+
for (const action of list) document = reducer(document, action as never);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
beforeAll(async () => {
|
|
28
|
+
document = utils.createDocument();
|
|
29
|
+
document.header.id = DEFAULT_WORKFLOW;
|
|
30
|
+
// A draft, never enabled: nothing is armed but the test's listener.
|
|
31
|
+
apply(
|
|
32
|
+
actions.setTrigger({
|
|
33
|
+
id: "t1",
|
|
34
|
+
...WEBHOOK_TRIGGER,
|
|
35
|
+
config: { scheme: "none", responseStatus: 202 },
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
host = await startWebhookHost({
|
|
39
|
+
reactorClient: {
|
|
40
|
+
get: () => Promise.resolve(structuredClone(document)),
|
|
41
|
+
find: () => Promise.resolve({ results: [] }),
|
|
42
|
+
execute: (_id: string, _branch: string, list: Action[]) => {
|
|
43
|
+
apply(...list);
|
|
44
|
+
return Promise.resolve(document);
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterAll(async () => {
|
|
51
|
+
host.service.shutdown();
|
|
52
|
+
await host.stop();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("the core webhook trigger test", () => {
|
|
56
|
+
it("saves the next delivery as the trigger's last test", async () => {
|
|
57
|
+
const endpoint = await host.service.webhookEndpoint(DEFAULT_WORKFLOW, CTX);
|
|
58
|
+
const token = endpoint!.url.slice(endpoint!.url.lastIndexOf("/") + 1);
|
|
59
|
+
// Unarmed: a delivery before the test starts is refused.
|
|
60
|
+
expect((await host.deliver(token, { body: "{}" })).status).toBe(404);
|
|
61
|
+
|
|
62
|
+
const testing = host.service.testTrigger(DEFAULT_WORKFLOW, CTX);
|
|
63
|
+
let reply: Response | undefined;
|
|
64
|
+
for (let attempt = 0; attempt < 100 && reply?.status !== 202; attempt++) {
|
|
65
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
66
|
+
reply = await host.deliver(token, {
|
|
67
|
+
headers: { "content-type": "application/json" },
|
|
68
|
+
body: JSON.stringify({ id: "evt_1" }),
|
|
69
|
+
query: { source: "provider" },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
expect(reply?.status).toBe(202);
|
|
74
|
+
expect(await testing).toMatchObject({
|
|
75
|
+
method: "POST",
|
|
76
|
+
queryParams: { source: "provider" },
|
|
77
|
+
body: { id: "evt_1" },
|
|
78
|
+
});
|
|
79
|
+
expect(document.state.global.trigger?.lastTest?.runId).toBeTruthy();
|
|
80
|
+
expect(host.fired).toEqual([]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The unverified and token halves of core
|
|
1
|
+
// The unverified and token halves of the core webhook trigger, over a real socket. Only
|
|
2
2
|
// the status code a provider actually receives proves these gates are wired.
|
|
3
3
|
import { afterEach, describe, expect, it } from "vitest";
|
|
4
4
|
import {
|
|
@@ -35,7 +35,7 @@ afterEach(async () => {
|
|
|
35
35
|
host = undefined;
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
describe("core
|
|
38
|
+
describe("the core webhook trigger over HTTP: unverified", () => {
|
|
39
39
|
it("starts one run carrying the request the provider sent", async () => {
|
|
40
40
|
const webhook = await start();
|
|
41
41
|
const { token } = await webhook.arm({ methods: "POST", scheme: "none" });
|
|
@@ -74,7 +74,7 @@ describe("core#webhook over HTTP: unverified", () => {
|
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
describe("core
|
|
77
|
+
describe("the core webhook trigger over HTTP: method restriction", () => {
|
|
78
78
|
it("refuses every method the author did not allow", async () => {
|
|
79
79
|
// A provider's verification round often probes with GET; the author asked
|
|
80
80
|
// for POST only, so the probe must be refused rather than run anything.
|
|
@@ -123,7 +123,7 @@ describe("core#webhook over HTTP: method restriction", () => {
|
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
describe("core
|
|
126
|
+
describe("the core webhook trigger over HTTP: token scheme", () => {
|
|
127
127
|
const tokenConfig = { scheme: "token", secretRef: SECRET_REF };
|
|
128
128
|
|
|
129
129
|
it("accepts a delivery presenting the shared secret", async () => {
|
|
@@ -191,7 +191,7 @@ describe("core#webhook over HTTP: token scheme", () => {
|
|
|
191
191
|
});
|
|
192
192
|
});
|
|
193
193
|
|
|
194
|
-
describe("core
|
|
194
|
+
describe("the core webhook trigger over HTTP: response modes", () => {
|
|
195
195
|
it("answers async before the run has finished", async () => {
|
|
196
196
|
// The point of async mode: the provider's socket is never held for as long
|
|
197
197
|
// as the workflow takes, even for a run that never settles.
|
|
@@ -291,7 +291,7 @@ describe("core#webhook over HTTP: response modes", () => {
|
|
|
291
291
|
});
|
|
292
292
|
});
|
|
293
293
|
|
|
294
|
-
describe("core
|
|
294
|
+
describe("the core webhook trigger over HTTP: endpoints that are not armed", () => {
|
|
295
295
|
it("answers a disarmed endpoint exactly as an unknown one", async () => {
|
|
296
296
|
// The endpoint row outlives disabling so re-enabling keeps the URL, which
|
|
297
297
|
// is why the answer must not betray that the token is a real one.
|
|
@@ -311,6 +311,19 @@ describe("core#webhook over HTTP: endpoints that are not armed", () => {
|
|
|
311
311
|
await expectNoRuns(webhook);
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
+
it("revokes a deleted workflow's token and answers it as unknown", async () => {
|
|
315
|
+
const webhook = await start();
|
|
316
|
+
const { token } = await webhook.arm({ scheme: "none" });
|
|
317
|
+
expect(await webhook.hasToken(token)).toBe(true);
|
|
318
|
+
await webhook.remove();
|
|
319
|
+
|
|
320
|
+
// Revoked off the ingestion path, once the supervisor has released it.
|
|
321
|
+
await expect.poll(() => webhook.hasToken(token)).toBe(false);
|
|
322
|
+
const res = await webhook.deliver(token, { ...JSON_BODY, body: "{}" });
|
|
323
|
+
expect(res.status).toBe(404);
|
|
324
|
+
await expectNoRuns(webhook);
|
|
325
|
+
});
|
|
326
|
+
|
|
314
327
|
it("refuses deliveries to an endpoint whose config does not parse", async () => {
|
|
315
328
|
// A token scheme with no secretRef cannot be honoured; firing anyway would
|
|
316
329
|
// run the workflow with no verification at all.
|
|
@@ -469,4 +469,18 @@ describe("bindPackagePieces", () => {
|
|
|
469
469
|
source.emit(new Map());
|
|
470
470
|
expect(registry.entries()).toEqual([]);
|
|
471
471
|
});
|
|
472
|
+
|
|
473
|
+
it("gives a piece two packages ship to the first by package name, whatever the load order", () => {
|
|
474
|
+
const registry = new PieceRegistry();
|
|
475
|
+
const source = stubSource(
|
|
476
|
+
new Map([
|
|
477
|
+
["@zeta/pack", [entry("@acme/piece-a", "9.0.0")]],
|
|
478
|
+
["@alpha/pack", [entry("@acme/piece-a", "1.0.0")]],
|
|
479
|
+
]),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
bindPackagePieces(registry, source);
|
|
483
|
+
|
|
484
|
+
expect(registry.versions()).toEqual({ "@acme/piece-a": "1.0.0" });
|
|
485
|
+
});
|
|
472
486
|
});
|