@powerhousedao/switchboard 6.2.3-dev.44 → 6.2.3-dev.46
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/durable-node-fs-pg16.test.d.ts +2 -0
- package/.tsbuild/test/durable-node-fs-pg16.test.d.ts.map +1 -0
- package/.tsbuild/test/pglite-preflight.test.d.ts +2 -0
- package/.tsbuild/test/pglite-preflight.test.d.ts.map +1 -0
- package/.tsbuild/test/snapshot-writer.d.ts +18 -0
- package/.tsbuild/test/snapshot-writer.d.ts.map +1 -0
- package/.tsbuild/test/workflow-reactor-access.test.d.ts +2 -0
- package/.tsbuild/test/workflow-reactor-access.test.d.ts.map +1 -0
- package/.tsbuild/test/workflow-reactor-enforcement.test.d.ts +2 -0
- package/.tsbuild/test/workflow-reactor-enforcement.test.d.ts.map +1 -0
- package/.tsbuild/tsconfig.tsbuildinfo +1 -1
- package/CHANGELOG.md +46 -0
- package/dist/index.mjs +1 -1
- package/dist/{server-Bk_IZzCn.mjs → server-Dvw8_NeM.mjs} +256 -77
- package/dist/server-Dvw8_NeM.mjs.map +1 -0
- package/dist/server.d.mts +3 -1
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +3 -3
- package/package.json +15 -16
- package/test/boot-unsupported-stored-documents.test.ts +5 -5
- package/test/durable-node-fs-pg16.test.ts +80 -0
- package/test/pglite-dialect.test.ts +28 -0
- package/test/pglite-preflight.test.ts +313 -0
- package/test/pglite-version.test.ts +9 -1
- package/test/snapshot-writer.ts +94 -0
- package/test/worker-pool.test.ts +27 -1
- package/test/workflow/resolvers.test.ts +26 -0
- package/test/workflow-reactor-access.test.ts +384 -0
- package/test/workflow-reactor-enforcement.test.ts +805 -0
- package/test/workflow-read-gate.test.ts +125 -60
- package/test/workflow-runtime.test.ts +158 -169
- package/tsconfig.json +0 -3
- package/dist/server-Bk_IZzCn.mjs.map +0 -1
|
@@ -4,11 +4,10 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import {
|
|
6
6
|
ReactorBuilder,
|
|
7
|
+
ReactorClientBuilder,
|
|
7
8
|
type Database,
|
|
8
|
-
type ILiveReadModelCoordinator,
|
|
9
9
|
type InProcessReactorClientModule,
|
|
10
10
|
type IReadModelCoordinator,
|
|
11
|
-
type ReadModelRegistrationStage,
|
|
12
11
|
} from "@powerhousedao/reactor";
|
|
13
12
|
import {
|
|
14
13
|
BaseSubgraph,
|
|
@@ -16,7 +15,12 @@ import {
|
|
|
16
15
|
type PackagePieceEntry,
|
|
17
16
|
} from "@powerhousedao/reactor-api";
|
|
18
17
|
import { PieceRegistry } from "@powerhousedao/reactor-workflow";
|
|
19
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
createRelationalDb,
|
|
20
|
+
type IRelationalDb,
|
|
21
|
+
type IWebhookScope,
|
|
22
|
+
type WebhookSpec,
|
|
23
|
+
} from "@powerhousedao/shared/processors";
|
|
20
24
|
import type { ILogger } from "document-model";
|
|
21
25
|
import { Kysely } from "kysely";
|
|
22
26
|
import { PGliteDialect } from "kysely-pglite-dialect";
|
|
@@ -29,8 +33,11 @@ import {
|
|
|
29
33
|
canReadAttachmentRef,
|
|
30
34
|
composeWorkflowRuntime,
|
|
31
35
|
assertWorkflowPackageLoadable,
|
|
36
|
+
hostPrincipalOf,
|
|
37
|
+
reactorAccessOf,
|
|
32
38
|
resolveWorkflowsEnabled,
|
|
33
39
|
type BooleanFlagSource,
|
|
40
|
+
type ComposedWorkflowRuntime,
|
|
34
41
|
} from "../src/workflow-runtime.mjs";
|
|
35
42
|
|
|
36
43
|
function stubLogger(): ILogger & {
|
|
@@ -52,80 +59,6 @@ function stubLogger(): ILogger & {
|
|
|
52
59
|
};
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
// The engine, faked at the seam the host loads it through: everything below
|
|
56
|
-
// belongs to @powerhousedao/reactor-workflow's own tests.
|
|
57
|
-
function fakeEngine() {
|
|
58
|
-
const runtime = {
|
|
59
|
-
registerWebhookEndpoint: vi.fn(() => Promise.resolve()),
|
|
60
|
-
startTriggerSupervisor: vi.fn(),
|
|
61
|
-
shutdown: vi.fn(),
|
|
62
|
-
onOperations: vi.fn((_operations: OperationWithContext[]) =>
|
|
63
|
-
Promise.resolve(),
|
|
64
|
-
),
|
|
65
|
-
};
|
|
66
|
-
const constructed: {
|
|
67
|
-
db: unknown;
|
|
68
|
-
runtime: unknown;
|
|
69
|
-
init: number;
|
|
70
|
-
}[] = [];
|
|
71
|
-
|
|
72
|
-
class FakeWorkflowTriggersReadModel {
|
|
73
|
-
readonly name = "workflow-triggers";
|
|
74
|
-
readonly #record: (typeof constructed)[number];
|
|
75
|
-
|
|
76
|
-
constructor(
|
|
77
|
-
db: unknown,
|
|
78
|
-
_operationIndex: unknown,
|
|
79
|
-
_writeCache: unknown,
|
|
80
|
-
_consistencyTracker: unknown,
|
|
81
|
-
boundRuntime: unknown,
|
|
82
|
-
) {
|
|
83
|
-
this.#record = { db, runtime: boundRuntime, init: 0 };
|
|
84
|
-
constructed.push(this.#record);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
init(): Promise<void> {
|
|
88
|
-
this.#record.init += 1;
|
|
89
|
-
return Promise.resolve();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
indexOperations(operations: OperationWithContext[]): Promise<void> {
|
|
93
|
-
return (this.#record.runtime as typeof runtime).onOperations(operations);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
runtime,
|
|
99
|
-
constructed,
|
|
100
|
-
module: {
|
|
101
|
-
WORKFLOW_PACKAGE_NAME: "@powerhousedao/workflow",
|
|
102
|
-
WORKFLOW_TRIGGERS_READ_MODEL: "workflow-triggers",
|
|
103
|
-
WORKFLOW_TRIGGERS_READ_MODEL_STAGE:
|
|
104
|
-
"post_ready" as ReadModelRegistrationStage,
|
|
105
|
-
WorkflowTriggersReadModel: FakeWorkflowTriggersReadModel,
|
|
106
|
-
setPieceRegistryUrl: vi.fn((_url: string | undefined) => undefined),
|
|
107
|
-
createWorkflowRuntime: vi.fn((_deps: Record<string, unknown>) => runtime),
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function compose(
|
|
113
|
-
engine: ReturnType<typeof fakeEngine>,
|
|
114
|
-
logger: ILogger,
|
|
115
|
-
overrides: Record<string, unknown> = {},
|
|
116
|
-
) {
|
|
117
|
-
return composeWorkflowRuntime({
|
|
118
|
-
reactorClient: {} as never,
|
|
119
|
-
relationalDb: { id: "relational-db" } as never,
|
|
120
|
-
attachments: { id: "attachments" } as never,
|
|
121
|
-
webhooks: { id: "webhooks" } as never,
|
|
122
|
-
authorizationService: {} as never,
|
|
123
|
-
logger,
|
|
124
|
-
load: () => Promise.resolve(engine.module as never),
|
|
125
|
-
...overrides,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
62
|
describe("resolveWorkflowsEnabled", () => {
|
|
130
63
|
let featureFlags: BooleanFlagSource;
|
|
131
64
|
|
|
@@ -179,19 +112,6 @@ describe("resolveWorkflowsEnabled", () => {
|
|
|
179
112
|
resolveWorkflowsEnabled({ featureFlags, configEnabled: false }),
|
|
180
113
|
).resolves.toBe(false);
|
|
181
114
|
});
|
|
182
|
-
|
|
183
|
-
// The engine must not be imported at all when workflows are off, so the
|
|
184
|
-
// decision is the flag's alone and is taken before compose is ever called.
|
|
185
|
-
it("is what gates composition", async () => {
|
|
186
|
-
const composeIfEnabled = async () =>
|
|
187
|
-
(await resolveWorkflowsEnabled({ featureFlags }))
|
|
188
|
-
? "composed"
|
|
189
|
-
: "skipped";
|
|
190
|
-
|
|
191
|
-
expect(await composeIfEnabled()).toBe("skipped");
|
|
192
|
-
process.env[PH_WORKFLOWS_ENABLED] = "true";
|
|
193
|
-
expect(await composeIfEnabled()).toBe("composed");
|
|
194
|
-
});
|
|
195
115
|
});
|
|
196
116
|
|
|
197
117
|
describe("assertWorkflowPackageLoadable", () => {
|
|
@@ -203,51 +123,85 @@ describe("assertWorkflowPackageLoadable", () => {
|
|
|
203
123
|
|
|
204
124
|
it("names the package a host would have to install when the load fails", async () => {
|
|
205
125
|
const cause = new Error("Cannot find module");
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
).rejects.toMatchObject({ cause });
|
|
126
|
+
const failing = assertWorkflowPackageLoadable(() => Promise.reject(cause));
|
|
127
|
+
await expect(failing).rejects.toThrow("@powerhousedao/workflow");
|
|
128
|
+
await expect(failing).rejects.toMatchObject({ cause });
|
|
209
129
|
});
|
|
210
130
|
});
|
|
211
131
|
|
|
132
|
+
// The endpoint family as the host's webhook service holds it, in memory.
|
|
133
|
+
function memoryWebhooks() {
|
|
134
|
+
const families: WebhookSpec[] = [];
|
|
135
|
+
const scope: IWebhookScope = {
|
|
136
|
+
hasPublicOrigin: true,
|
|
137
|
+
register: (spec) => {
|
|
138
|
+
families.push(spec);
|
|
139
|
+
return Promise.resolve({
|
|
140
|
+
endpointFor: (key) =>
|
|
141
|
+
Promise.resolve({
|
|
142
|
+
token: key,
|
|
143
|
+
url: `https://hooks.test/${key}`,
|
|
144
|
+
createdAt: new Date().toISOString(),
|
|
145
|
+
}),
|
|
146
|
+
revoke: () => Promise.resolve(),
|
|
147
|
+
list: () => Promise.resolve([]),
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
return { families, scope };
|
|
152
|
+
}
|
|
153
|
+
|
|
212
154
|
describe("composeWorkflowRuntime", () => {
|
|
213
|
-
let
|
|
214
|
-
let
|
|
155
|
+
let databases: Kysely<unknown>[] = [];
|
|
156
|
+
let module: InProcessReactorClientModule | undefined;
|
|
157
|
+
let composed: ComposedWorkflowRuntime | undefined;
|
|
215
158
|
|
|
216
159
|
afterEach(async () => {
|
|
217
|
-
|
|
218
|
-
await
|
|
219
|
-
await database
|
|
220
|
-
|
|
221
|
-
|
|
160
|
+
await composed?.stop();
|
|
161
|
+
await module?.reactor.kill().completed;
|
|
162
|
+
for (const database of databases) await database.destroy();
|
|
163
|
+
composed = undefined;
|
|
164
|
+
module = undefined;
|
|
165
|
+
databases = [];
|
|
222
166
|
});
|
|
223
167
|
|
|
168
|
+
function pglite(): Kysely<unknown> {
|
|
169
|
+
const database = new Kysely<unknown>({
|
|
170
|
+
dialect: new PGliteDialect(new PGlite()),
|
|
171
|
+
});
|
|
172
|
+
databases.push(database);
|
|
173
|
+
return database;
|
|
174
|
+
}
|
|
175
|
+
|
|
224
176
|
async function buildReactorModule(
|
|
225
177
|
coordinator?: IReadModelCoordinator,
|
|
226
178
|
): Promise<InProcessReactorClientModule> {
|
|
227
|
-
database = new Kysely<unknown>({
|
|
228
|
-
dialect: new PGliteDialect(new PGlite()),
|
|
229
|
-
});
|
|
230
179
|
const builder = new ReactorBuilder().withKysely(
|
|
231
|
-
|
|
180
|
+
pglite() as unknown as Kysely<Database>,
|
|
232
181
|
);
|
|
233
182
|
if (coordinator) builder.withReadModelCoordinator(coordinator);
|
|
234
|
-
|
|
235
|
-
|
|
183
|
+
module = await new ReactorClientBuilder()
|
|
184
|
+
.withReactorBuilder(builder)
|
|
185
|
+
.buildModule();
|
|
186
|
+
return module;
|
|
236
187
|
}
|
|
237
188
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
189
|
+
// The real engine, loaded as production loads it.
|
|
190
|
+
async function compose(
|
|
191
|
+
clientModule: InProcessReactorClientModule,
|
|
192
|
+
overrides: Partial<Parameters<typeof composeWorkflowRuntime>[0]> = {},
|
|
193
|
+
): Promise<ComposedWorkflowRuntime> {
|
|
194
|
+
composed = await composeWorkflowRuntime({
|
|
195
|
+
reactorClient: clientModule.client,
|
|
196
|
+
clientModule,
|
|
197
|
+
relationalDb: createRelationalDb(pglite()) as IRelationalDb,
|
|
198
|
+
attachments: {} as never,
|
|
199
|
+
authorizationService: {} as never,
|
|
200
|
+
logger: stubLogger(),
|
|
201
|
+
...overrides,
|
|
202
|
+
});
|
|
203
|
+
return composed;
|
|
204
|
+
}
|
|
251
205
|
|
|
252
206
|
it("lets a step read any well-formed attachment ref", async () => {
|
|
253
207
|
const hash = "a".repeat(64);
|
|
@@ -262,42 +216,25 @@ describe("composeWorkflowRuntime", () => {
|
|
|
262
216
|
});
|
|
263
217
|
|
|
264
218
|
it("serves a subgraph the GraphQL manager can construct", async () => {
|
|
265
|
-
const
|
|
266
|
-
const { subgraph } = await compose(engine, stubLogger());
|
|
219
|
+
const { subgraph } = await compose(await buildReactorModule());
|
|
267
220
|
|
|
268
221
|
expect(subgraph.prototype).toBeInstanceOf(BaseSubgraph);
|
|
269
222
|
});
|
|
270
223
|
|
|
271
|
-
it("registers the trigger read model
|
|
272
|
-
const engine = fakeEngine();
|
|
224
|
+
it("registers the trigger read model on the reactor's coordinator", async () => {
|
|
273
225
|
const clientModule = await buildReactorModule();
|
|
274
|
-
const coordinator = clientModule.reactorModule!
|
|
275
|
-
.readModelCoordinator as ILiveReadModelCoordinator;
|
|
276
|
-
const addReadModel = vi.spyOn(coordinator, "addReadModel");
|
|
277
|
-
const logger = stubLogger();
|
|
278
226
|
|
|
279
|
-
const workflows = await compose(
|
|
227
|
+
const workflows = await compose(clientModule);
|
|
280
228
|
|
|
281
229
|
expect(workflows.triggers).toEqual({ status: "available" });
|
|
282
|
-
expect(addReadModel).toHaveBeenCalledTimes(1);
|
|
283
|
-
expect(addReadModel.mock.calls[0]![1]).toBe("post_ready");
|
|
284
|
-
// Constructed once, initialised before it was registered, bound to the
|
|
285
|
-
// runtime this composition built.
|
|
286
|
-
expect(engine.constructed).toHaveLength(1);
|
|
287
|
-
expect(engine.constructed[0]!.init).toBe(1);
|
|
288
|
-
expect(engine.constructed[0]!.runtime).toBe(engine.runtime);
|
|
289
|
-
|
|
290
|
-
const registered = addReadModel.mock.calls[0]![0];
|
|
291
|
-
const operations = [] as OperationWithContext[];
|
|
292
|
-
await registered.indexOperations(operations);
|
|
293
|
-
expect(engine.runtime.onOperations).toHaveBeenCalledWith(operations);
|
|
294
230
|
expect(
|
|
295
|
-
|
|
231
|
+
clientModule.reactorModule!.readModelCoordinator.readModels.filter(
|
|
232
|
+
({ name }) => name === "workflow-triggers",
|
|
233
|
+
),
|
|
296
234
|
).toHaveLength(1);
|
|
297
235
|
});
|
|
298
236
|
|
|
299
|
-
it("reports the intake unavailable
|
|
300
|
-
const engine = fakeEngine();
|
|
237
|
+
it("reports the intake unavailable on a coordinator without live registration", async () => {
|
|
301
238
|
const customCoordinator: IReadModelCoordinator = {
|
|
302
239
|
readModels: [],
|
|
303
240
|
start: vi.fn(),
|
|
@@ -305,26 +242,22 @@ describe("composeWorkflowRuntime", () => {
|
|
|
305
242
|
drain: vi.fn().mockResolvedValue(undefined),
|
|
306
243
|
getChainDepth: vi.fn().mockReturnValue(0),
|
|
307
244
|
};
|
|
308
|
-
const clientModule = await buildReactorModule(customCoordinator);
|
|
309
|
-
const logger = stubLogger();
|
|
310
245
|
|
|
311
|
-
const workflows = await compose(
|
|
246
|
+
const workflows = await compose(
|
|
247
|
+
await buildReactorModule(customCoordinator),
|
|
248
|
+
);
|
|
312
249
|
|
|
313
250
|
expect(workflows.triggers).toEqual({
|
|
314
251
|
status: "unavailable",
|
|
315
252
|
reason: "live-read-model-registration-unsupported",
|
|
316
253
|
});
|
|
317
|
-
expect(engine.constructed).toHaveLength(0);
|
|
318
254
|
expect(customCoordinator.readModels).toEqual([]);
|
|
319
|
-
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
320
|
-
expect(logger.error.mock.calls[0]![0]).toContain("NOT armed");
|
|
321
255
|
});
|
|
322
256
|
|
|
323
257
|
it("reports the intake unavailable without an in-process reactor", async () => {
|
|
324
|
-
const
|
|
325
|
-
const logger = stubLogger();
|
|
258
|
+
const clientModule = await buildReactorModule();
|
|
326
259
|
|
|
327
|
-
const workflows = await compose(
|
|
260
|
+
const workflows = await compose(clientModule, {
|
|
328
261
|
clientModule: {} as InProcessReactorClientModule,
|
|
329
262
|
});
|
|
330
263
|
|
|
@@ -332,33 +265,38 @@ describe("composeWorkflowRuntime", () => {
|
|
|
332
265
|
status: "unavailable",
|
|
333
266
|
reason: "in-process-reactor-module-unavailable",
|
|
334
267
|
});
|
|
335
|
-
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
336
268
|
});
|
|
337
269
|
|
|
338
|
-
it("arms the webhooks and the supervisor on start", async () => {
|
|
339
|
-
const
|
|
340
|
-
const workflows = await compose(
|
|
341
|
-
|
|
342
|
-
|
|
270
|
+
it("arms the webhooks and the supervisor on start, and stops them once", async () => {
|
|
271
|
+
const webhooks = memoryWebhooks();
|
|
272
|
+
const workflows = await compose(await buildReactorModule(), {
|
|
273
|
+
webhooks: webhooks.scope,
|
|
274
|
+
});
|
|
343
275
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
276
|
+
// Intervals only, from here on: the supervisor's tick is the runtime's one.
|
|
277
|
+
vi.useFakeTimers({ toFake: ["setInterval", "clearInterval"] });
|
|
278
|
+
try {
|
|
279
|
+
await workflows.start();
|
|
347
280
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
281
|
+
expect(webhooks.families.map(({ name }) => name)).toEqual(["trigger"]);
|
|
282
|
+
// The real runtime answers for it: an unknown workflow is not armed.
|
|
283
|
+
await expect(
|
|
284
|
+
webhooks.families[0]!.policyFor?.("wf-unknown"),
|
|
285
|
+
).resolves.toBeUndefined();
|
|
286
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
352
287
|
|
|
353
|
-
|
|
354
|
-
|
|
288
|
+
await workflows.stop();
|
|
289
|
+
await workflows.stop();
|
|
355
290
|
|
|
356
|
-
|
|
291
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
292
|
+
} finally {
|
|
293
|
+
vi.useRealTimers();
|
|
294
|
+
}
|
|
357
295
|
});
|
|
358
296
|
|
|
359
297
|
it("names the package a host would have to install when the load fails", async () => {
|
|
360
298
|
const cause = new Error("Cannot find module");
|
|
361
|
-
const failing = compose(
|
|
299
|
+
const failing = compose(await buildReactorModule(), {
|
|
362
300
|
load: () => Promise.reject(cause),
|
|
363
301
|
});
|
|
364
302
|
|
|
@@ -367,6 +305,48 @@ describe("composeWorkflowRuntime", () => {
|
|
|
367
305
|
});
|
|
368
306
|
});
|
|
369
307
|
|
|
308
|
+
describe("the host's reactor access", () => {
|
|
309
|
+
const signer = { app: { key: "did:key:zHost" }, user: { address: "0xhost" } };
|
|
310
|
+
const moduleWith = (flags?: Record<string, boolean>) =>
|
|
311
|
+
({
|
|
312
|
+
signer,
|
|
313
|
+
reactorModule: flags ? { featureFlags: flags } : undefined,
|
|
314
|
+
}) as unknown as InProcessReactorClientModule;
|
|
315
|
+
|
|
316
|
+
it("grants the host by key under auth conditions, else by address", () => {
|
|
317
|
+
const byKey = reactorAccessOf(
|
|
318
|
+
moduleWith({ authEnforcement: true, authConditions: true }),
|
|
319
|
+
);
|
|
320
|
+
expect(byKey).toEqual({
|
|
321
|
+
authEnforcement: true,
|
|
322
|
+
authConditions: true,
|
|
323
|
+
identity: { address: "0xhost", key: "did:key:zHost" },
|
|
324
|
+
});
|
|
325
|
+
expect(hostPrincipalOf(byKey)).toEqual({
|
|
326
|
+
match: { eq: [{ attr: "subject.key" }, { lit: "did:key:zHost" }] },
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const byAddress = reactorAccessOf(
|
|
330
|
+
moduleWith({ authEnforcement: false, authConditions: false }),
|
|
331
|
+
);
|
|
332
|
+
expect(byAddress.authEnforcement).toBe(false);
|
|
333
|
+
expect(hostPrincipalOf(byAddress)).toEqual({ address: "0xhost" });
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("reads unknown flags as enforced and a keyless signer as no identity", () => {
|
|
337
|
+
const access = reactorAccessOf({
|
|
338
|
+
signer: {},
|
|
339
|
+
reactorModule: undefined,
|
|
340
|
+
} as unknown as InProcessReactorClientModule);
|
|
341
|
+
expect(access).toEqual({
|
|
342
|
+
authEnforcement: true,
|
|
343
|
+
authConditions: false,
|
|
344
|
+
identity: null,
|
|
345
|
+
});
|
|
346
|
+
expect(hostPrincipalOf(access)).toBeUndefined();
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
370
350
|
// The GraphQL manager rides on the boot result without being on its public
|
|
371
351
|
// type, and the subgraph registers late: poll rather than race it.
|
|
372
352
|
async function pollWorkflowSubgraph(
|
|
@@ -408,6 +388,15 @@ describe("booting Switchboard with workflows on", () => {
|
|
|
408
388
|
expect(
|
|
409
389
|
results.map(({ documentModel }) => documentModel.global.id),
|
|
410
390
|
).toContain("powerhouse/workflow");
|
|
391
|
+
// With no worker pool or projection worker, workflows alone resolve them.
|
|
392
|
+
expect(
|
|
393
|
+
switchboard.modelManifest().map((entry) => entry.documentType),
|
|
394
|
+
).toEqual(
|
|
395
|
+
expect.arrayContaining([
|
|
396
|
+
"powerhouse/workflow",
|
|
397
|
+
"powerhouse/connection",
|
|
398
|
+
]),
|
|
399
|
+
);
|
|
411
400
|
// The subgraph is registered late, so the schema it joins arrives after
|
|
412
401
|
// the boot resolves; poll rather than race it.
|
|
413
402
|
await expect(pollWorkflowSubgraph(switchboard)).resolves.toMatchObject({
|