@powerhousedao/switchboard 6.2.3-dev.30 → 6.2.3-dev.32

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.
@@ -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#webhook, over a real socket. Only
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#webhook over HTTP: unverified", () => {
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#webhook over HTTP: method restriction", () => {
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#webhook over HTTP: token scheme", () => {
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#webhook over HTTP: response modes", () => {
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#webhook over HTTP: endpoints that are not armed", () => {
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
  });