@checkstack/backend 0.0.2
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/CHANGELOG.md +225 -0
- package/drizzle/0000_loose_yellow_claw.sql +28 -0
- package/drizzle/meta/0000_snapshot.json +187 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +10 -0
- package/package.json +42 -0
- package/src/db.ts +20 -0
- package/src/health-check-plugin-integration.test.ts +93 -0
- package/src/index.ts +419 -0
- package/src/integration/event-bus.integration.test.ts +313 -0
- package/src/logger.ts +65 -0
- package/src/openapi-router.ts +177 -0
- package/src/plugin-lifecycle.test.ts +276 -0
- package/src/plugin-manager/api-router.ts +163 -0
- package/src/plugin-manager/core-services.ts +312 -0
- package/src/plugin-manager/dependency-sorter.ts +103 -0
- package/src/plugin-manager/deregistration-guard.ts +41 -0
- package/src/plugin-manager/extension-points.ts +85 -0
- package/src/plugin-manager/index.ts +13 -0
- package/src/plugin-manager/plugin-admin-router.ts +89 -0
- package/src/plugin-manager/plugin-loader.ts +464 -0
- package/src/plugin-manager/types.ts +14 -0
- package/src/plugin-manager.test.ts +464 -0
- package/src/plugin-manager.ts +431 -0
- package/src/rpc-rest-compat.test.ts +80 -0
- package/src/schema.ts +46 -0
- package/src/services/config-service.test.ts +66 -0
- package/src/services/config-service.ts +322 -0
- package/src/services/event-bus.test.ts +469 -0
- package/src/services/event-bus.ts +317 -0
- package/src/services/health-check-registry.test.ts +101 -0
- package/src/services/health-check-registry.ts +27 -0
- package/src/services/jwt.ts +45 -0
- package/src/services/keystore.test.ts +198 -0
- package/src/services/keystore.ts +136 -0
- package/src/services/plugin-installer.test.ts +90 -0
- package/src/services/plugin-installer.ts +70 -0
- package/src/services/queue-manager.ts +382 -0
- package/src/services/queue-plugin-registry.ts +17 -0
- package/src/services/queue-proxy.ts +182 -0
- package/src/services/service-registry.ts +35 -0
- package/src/test-preload.ts +114 -0
- package/src/utils/plugin-discovery.test.ts +383 -0
- package/src/utils/plugin-discovery.ts +157 -0
- package/src/utils/strip-public-schema.ts +40 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { describe, it, expect, mock, beforeEach } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
createServiceRef,
|
|
4
|
+
createExtensionPoint,
|
|
5
|
+
createBackendPlugin,
|
|
6
|
+
ServiceRef,
|
|
7
|
+
coreServices,
|
|
8
|
+
} from "@checkstack/backend-api";
|
|
9
|
+
import {
|
|
10
|
+
createMockLogger,
|
|
11
|
+
createMockQueueManager,
|
|
12
|
+
} from "@checkstack/test-utils-backend";
|
|
13
|
+
import { sortPlugins } from "./plugin-manager/dependency-sorter";
|
|
14
|
+
|
|
15
|
+
// Note: ./db and ./logger are mocked via test-preload.ts (bunfig.toml preload)
|
|
16
|
+
// This ensures mocks are in place BEFORE any module imports them
|
|
17
|
+
|
|
18
|
+
import { PluginManager } from "./plugin-manager";
|
|
19
|
+
|
|
20
|
+
describe("PluginManager", () => {
|
|
21
|
+
let pluginManager: PluginManager;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
pluginManager = new PluginManager();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("Service Registration", () => {
|
|
28
|
+
it("should register and retrieve a global service", async () => {
|
|
29
|
+
const testServiceRef = createServiceRef<{ foo: string }>("test.service");
|
|
30
|
+
const testImpl = { foo: "bar" };
|
|
31
|
+
|
|
32
|
+
pluginManager.registerService(testServiceRef, testImpl);
|
|
33
|
+
const retrieved = await pluginManager.getService(testServiceRef);
|
|
34
|
+
|
|
35
|
+
expect(retrieved).toBe(testImpl);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should return undefined for unregistered service", async () => {
|
|
39
|
+
const testServiceRef = createServiceRef<{ foo: string }>("test.service");
|
|
40
|
+
const retrieved = await pluginManager.getService(testServiceRef);
|
|
41
|
+
|
|
42
|
+
expect(retrieved).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("Extension Points", () => {
|
|
47
|
+
interface TestExtensionPoint {
|
|
48
|
+
addThing(name: string): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
it("should allow registering and getting an extension point", () => {
|
|
52
|
+
const testEPRef = createExtensionPoint<TestExtensionPoint>("test.ep");
|
|
53
|
+
const mockImpl: TestExtensionPoint = {
|
|
54
|
+
addThing: mock(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// In the real flow, a plugin calls registerExtensionPoint
|
|
58
|
+
// which sets the implementation on the proxy
|
|
59
|
+
// We can simulate this by mocking the environment passed to register
|
|
60
|
+
pluginManager["registerExtensionPoint"](testEPRef, mockImpl);
|
|
61
|
+
|
|
62
|
+
const ep = pluginManager.getExtensionPoint(testEPRef);
|
|
63
|
+
ep.addThing("hello");
|
|
64
|
+
|
|
65
|
+
expect(mockImpl.addThing).toHaveBeenCalledWith("hello");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should buffer calls to extension points before they are registered", () => {
|
|
69
|
+
const testEPRef = createExtensionPoint<TestExtensionPoint>("test.ep");
|
|
70
|
+
|
|
71
|
+
// 1. Get the proxy before implementation is registered
|
|
72
|
+
const ep = pluginManager.getExtensionPoint(testEPRef);
|
|
73
|
+
|
|
74
|
+
// 2. Call a method on the proxy
|
|
75
|
+
ep.addThing("buffered-call");
|
|
76
|
+
|
|
77
|
+
const mockImpl: TestExtensionPoint = {
|
|
78
|
+
addThing: mock(),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// 3. Register the implementation
|
|
82
|
+
pluginManager["registerExtensionPoint"](testEPRef, mockImpl);
|
|
83
|
+
|
|
84
|
+
// 4. Verify the buffered call was replayed
|
|
85
|
+
expect(mockImpl.addThing).toHaveBeenCalledWith("buffered-call");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("sortPlugins (Topological Sort)", () => {
|
|
90
|
+
it("should sort plugins based on their dependencies", () => {
|
|
91
|
+
const s1 = createServiceRef<unknown>("service-1");
|
|
92
|
+
const s2 = createServiceRef<unknown>("service-2");
|
|
93
|
+
|
|
94
|
+
const pendingInits = [
|
|
95
|
+
{
|
|
96
|
+
metadata: { pluginId: "consumer" },
|
|
97
|
+
deps: { d1: s1, d2: s2 } as Record<string, ServiceRef<unknown>>,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
metadata: { pluginId: "provider-1" },
|
|
101
|
+
deps: {} as Record<string, ServiceRef<unknown>>,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
metadata: { pluginId: "provider-2" },
|
|
105
|
+
deps: { d1: s1 } as Record<string, ServiceRef<unknown>>,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const providedBy = new Map([
|
|
110
|
+
[s1.id, "provider-1"],
|
|
111
|
+
[s2.id, "provider-2"],
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
const sorted = sortPlugins({
|
|
115
|
+
pendingInits,
|
|
116
|
+
providedBy,
|
|
117
|
+
logger: createMockLogger(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// provider-1 must come before consumer and provider-2
|
|
121
|
+
// provider-2 must come before consumer
|
|
122
|
+
expect(sorted.indexOf("provider-1")).toBeLessThan(
|
|
123
|
+
sorted.indexOf("consumer")
|
|
124
|
+
);
|
|
125
|
+
expect(sorted.indexOf("provider-1")).toBeLessThan(
|
|
126
|
+
sorted.indexOf("provider-2")
|
|
127
|
+
);
|
|
128
|
+
expect(sorted.indexOf("provider-2")).toBeLessThan(
|
|
129
|
+
sorted.indexOf("consumer")
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should throw error on circular dependency", () => {
|
|
134
|
+
const s1 = createServiceRef<unknown>("service-1");
|
|
135
|
+
const s2 = createServiceRef<unknown>("service-2");
|
|
136
|
+
|
|
137
|
+
const pendingInits = [
|
|
138
|
+
{ metadata: { pluginId: "p1" }, deps: { d: s2 } },
|
|
139
|
+
{ metadata: { pluginId: "p2" }, deps: { d: s1 } },
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const providedBy = new Map([
|
|
143
|
+
[s1.id, "p1"],
|
|
144
|
+
[s2.id, "p2"],
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
expect(() =>
|
|
148
|
+
sortPlugins({ pendingInits, providedBy, logger: createMockLogger() })
|
|
149
|
+
).toThrow("Circular dependency detected");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("Queue Plugin Ordering", () => {
|
|
153
|
+
it("should initialize queue plugin providers before queue consumers", () => {
|
|
154
|
+
const queueManagerRef = createServiceRef<unknown>(
|
|
155
|
+
coreServices.queueManager.id
|
|
156
|
+
);
|
|
157
|
+
const queueRegistryRef = createServiceRef<unknown>(
|
|
158
|
+
coreServices.queuePluginRegistry.id
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const pendingInits = [
|
|
162
|
+
{
|
|
163
|
+
metadata: { pluginId: "queue-consumer" },
|
|
164
|
+
deps: { queueManager: queueManagerRef } as Record<
|
|
165
|
+
string,
|
|
166
|
+
ServiceRef<unknown>
|
|
167
|
+
>,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
metadata: { pluginId: "queue-provider" },
|
|
171
|
+
deps: { queuePluginRegistry: queueRegistryRef } as Record<
|
|
172
|
+
string,
|
|
173
|
+
ServiceRef<unknown>
|
|
174
|
+
>,
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
const providedBy = new Map<string, string>();
|
|
179
|
+
const sorted = sortPlugins({
|
|
180
|
+
pendingInits,
|
|
181
|
+
providedBy,
|
|
182
|
+
logger: createMockLogger(),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Queue provider should come before queue consumer
|
|
186
|
+
expect(sorted.indexOf("queue-provider")).toBeLessThan(
|
|
187
|
+
sorted.indexOf("queue-consumer")
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should handle multiple queue providers and consumers", () => {
|
|
192
|
+
const queueManagerRef = createServiceRef<unknown>(
|
|
193
|
+
coreServices.queueManager.id
|
|
194
|
+
);
|
|
195
|
+
const queueRegistryRef = createServiceRef<unknown>(
|
|
196
|
+
coreServices.queuePluginRegistry.id
|
|
197
|
+
);
|
|
198
|
+
const loggerRef = createServiceRef<unknown>("core.logger");
|
|
199
|
+
|
|
200
|
+
const pendingInits = [
|
|
201
|
+
{
|
|
202
|
+
metadata: { pluginId: "consumer-1" },
|
|
203
|
+
deps: { queueManager: queueManagerRef } as Record<
|
|
204
|
+
string,
|
|
205
|
+
ServiceRef<unknown>
|
|
206
|
+
>,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
metadata: { pluginId: "provider-1" },
|
|
210
|
+
deps: { queuePluginRegistry: queueRegistryRef } as Record<
|
|
211
|
+
string,
|
|
212
|
+
ServiceRef<unknown>
|
|
213
|
+
>,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
metadata: { pluginId: "consumer-2" },
|
|
217
|
+
deps: { queueManager: queueManagerRef } as Record<
|
|
218
|
+
string,
|
|
219
|
+
ServiceRef<unknown>
|
|
220
|
+
>,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
metadata: { pluginId: "provider-2" },
|
|
224
|
+
deps: { queuePluginRegistry: queueRegistryRef } as Record<
|
|
225
|
+
string,
|
|
226
|
+
ServiceRef<unknown>
|
|
227
|
+
>,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
metadata: { pluginId: "unrelated" },
|
|
231
|
+
deps: { logger: loggerRef } as Record<string, ServiceRef<unknown>>,
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
const providedBy = new Map<string, string>();
|
|
236
|
+
const sorted = sortPlugins({
|
|
237
|
+
pendingInits,
|
|
238
|
+
providedBy,
|
|
239
|
+
logger: createMockLogger(),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// All providers should come before all consumers
|
|
243
|
+
const provider1Index = sorted.indexOf("provider-1");
|
|
244
|
+
const provider2Index = sorted.indexOf("provider-2");
|
|
245
|
+
const consumer1Index = sorted.indexOf("consumer-1");
|
|
246
|
+
const consumer2Index = sorted.indexOf("consumer-2");
|
|
247
|
+
|
|
248
|
+
expect(provider1Index).toBeLessThan(consumer1Index);
|
|
249
|
+
expect(provider1Index).toBeLessThan(consumer2Index);
|
|
250
|
+
expect(provider2Index).toBeLessThan(consumer1Index);
|
|
251
|
+
expect(provider2Index).toBeLessThan(consumer2Index);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should respect existing service dependencies while prioritizing queue plugins", () => {
|
|
255
|
+
const queueManagerRef = createServiceRef<unknown>(
|
|
256
|
+
coreServices.queueManager.id
|
|
257
|
+
);
|
|
258
|
+
const queueRegistryRef = createServiceRef<unknown>(
|
|
259
|
+
coreServices.queuePluginRegistry.id
|
|
260
|
+
);
|
|
261
|
+
const customServiceRef = createServiceRef<unknown>("custom.service");
|
|
262
|
+
const loggerRef = createServiceRef<unknown>("core.logger");
|
|
263
|
+
|
|
264
|
+
const pendingInits = [
|
|
265
|
+
{
|
|
266
|
+
metadata: { pluginId: "queue-consumer" },
|
|
267
|
+
deps: {
|
|
268
|
+
queueManager: queueManagerRef,
|
|
269
|
+
customService: customServiceRef,
|
|
270
|
+
} as Record<string, ServiceRef<unknown>>,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
metadata: { pluginId: "queue-provider" },
|
|
274
|
+
deps: { queuePluginRegistry: queueRegistryRef } as Record<
|
|
275
|
+
string,
|
|
276
|
+
ServiceRef<unknown>
|
|
277
|
+
>,
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
metadata: { pluginId: "provider-plugin" },
|
|
281
|
+
deps: { logger: loggerRef } as Record<string, ServiceRef<unknown>>,
|
|
282
|
+
},
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const providedBy = new Map<string, string>([
|
|
286
|
+
[customServiceRef.id, "provider-plugin"],
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
const sorted = sortPlugins({
|
|
290
|
+
pendingInits,
|
|
291
|
+
providedBy,
|
|
292
|
+
logger: createMockLogger(),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Queue provider should come before queue consumer
|
|
296
|
+
const queueProviderIndex = sorted.indexOf("queue-provider");
|
|
297
|
+
const queueConsumerIndex = sorted.indexOf("queue-consumer");
|
|
298
|
+
expect(queueProviderIndex).toBeLessThan(queueConsumerIndex);
|
|
299
|
+
|
|
300
|
+
// Provider plugin should come before queue consumer (due to service dependency)
|
|
301
|
+
const providerPluginIndex = sorted.indexOf("provider-plugin");
|
|
302
|
+
expect(providerPluginIndex).toBeLessThan(queueConsumerIndex);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should handle plugins that both provide and consume queues", () => {
|
|
306
|
+
const queueManagerRef = createServiceRef<unknown>(
|
|
307
|
+
coreServices.queueManager.id
|
|
308
|
+
);
|
|
309
|
+
const queueRegistryRef = createServiceRef<unknown>(
|
|
310
|
+
coreServices.queuePluginRegistry.id
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const pendingInits = [
|
|
314
|
+
{
|
|
315
|
+
metadata: { pluginId: "dual-plugin" },
|
|
316
|
+
deps: {
|
|
317
|
+
queuePluginRegistry: queueRegistryRef,
|
|
318
|
+
queueManager: queueManagerRef,
|
|
319
|
+
} as Record<string, ServiceRef<unknown>>,
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
metadata: { pluginId: "consumer-only" },
|
|
323
|
+
deps: { queueManager: queueManagerRef } as Record<
|
|
324
|
+
string,
|
|
325
|
+
ServiceRef<unknown>
|
|
326
|
+
>,
|
|
327
|
+
},
|
|
328
|
+
];
|
|
329
|
+
|
|
330
|
+
const providedBy = new Map<string, string>();
|
|
331
|
+
const sorted = sortPlugins({
|
|
332
|
+
pendingInits,
|
|
333
|
+
providedBy,
|
|
334
|
+
logger: createMockLogger(),
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Dual plugin should come before consumer-only
|
|
338
|
+
const dualIndex = sorted.indexOf("dual-plugin");
|
|
339
|
+
const consumerIndex = sorted.indexOf("consumer-only");
|
|
340
|
+
expect(dualIndex).toBeLessThan(consumerIndex);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should not create circular dependencies with queue ordering", () => {
|
|
344
|
+
const queueManagerRef = createServiceRef<unknown>(
|
|
345
|
+
coreServices.queueManager.id
|
|
346
|
+
);
|
|
347
|
+
const queueRegistryRef = createServiceRef<unknown>(
|
|
348
|
+
coreServices.queuePluginRegistry.id
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const pendingInits = [
|
|
352
|
+
{
|
|
353
|
+
metadata: { pluginId: "queue-provider" },
|
|
354
|
+
deps: { queuePluginRegistry: queueRegistryRef } as Record<
|
|
355
|
+
string,
|
|
356
|
+
ServiceRef<unknown>
|
|
357
|
+
>,
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
metadata: { pluginId: "queue-consumer" },
|
|
361
|
+
deps: { queueManager: queueManagerRef } as Record<
|
|
362
|
+
string,
|
|
363
|
+
ServiceRef<unknown>
|
|
364
|
+
>,
|
|
365
|
+
},
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
const providedBy = new Map<string, string>();
|
|
369
|
+
|
|
370
|
+
// Should not throw
|
|
371
|
+
expect(() => {
|
|
372
|
+
sortPlugins({ pendingInits, providedBy, logger: createMockLogger() });
|
|
373
|
+
}).not.toThrow();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe("Permission Registration", () => {
|
|
379
|
+
it("should store permissions in the registry", () => {
|
|
380
|
+
// Permissions are now stored directly via the registeredPermissions array
|
|
381
|
+
// and hooks are emitted in Phase 3 (afterPluginsReady)
|
|
382
|
+
const perms = (
|
|
383
|
+
pluginManager as unknown as {
|
|
384
|
+
registeredPermissions: {
|
|
385
|
+
pluginId: string;
|
|
386
|
+
id: string;
|
|
387
|
+
description?: string;
|
|
388
|
+
}[];
|
|
389
|
+
}
|
|
390
|
+
).registeredPermissions;
|
|
391
|
+
|
|
392
|
+
// Add permissions directly (simulating what plugin-loader does)
|
|
393
|
+
perms.push({
|
|
394
|
+
pluginId: "test-plugin",
|
|
395
|
+
id: "test-plugin.test.permission",
|
|
396
|
+
description: "Test permission",
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// getAllPermissions should return them (without pluginId in the output)
|
|
400
|
+
const all = pluginManager.getAllPermissions();
|
|
401
|
+
expect(all.length).toBe(1);
|
|
402
|
+
expect(all[0]).toEqual({
|
|
403
|
+
id: "test-plugin.test.permission",
|
|
404
|
+
description: "Test permission",
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should aggregate permissions from multiple plugins", () => {
|
|
409
|
+
const perms = (
|
|
410
|
+
pluginManager as unknown as {
|
|
411
|
+
registeredPermissions: {
|
|
412
|
+
pluginId: string;
|
|
413
|
+
id: string;
|
|
414
|
+
description?: string;
|
|
415
|
+
}[];
|
|
416
|
+
}
|
|
417
|
+
).registeredPermissions;
|
|
418
|
+
|
|
419
|
+
perms.push(
|
|
420
|
+
{ pluginId: "plugin-1", id: "plugin-1.perm.1", description: undefined },
|
|
421
|
+
{ pluginId: "plugin-1", id: "plugin-1.perm.2", description: undefined },
|
|
422
|
+
{ pluginId: "plugin-2", id: "plugin-2.perm.3", description: undefined }
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
const all = pluginManager.getAllPermissions();
|
|
426
|
+
expect(all.length).toBe(3);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
describe("loadPlugins", () => {
|
|
431
|
+
it("should discover and initialize plugins", async () => {
|
|
432
|
+
const mockRouter = {
|
|
433
|
+
route: mock(),
|
|
434
|
+
all: mock(),
|
|
435
|
+
newResponse: mock(),
|
|
436
|
+
} as never;
|
|
437
|
+
|
|
438
|
+
// Mock dynamic imports
|
|
439
|
+
const testBackendInit = mock(async () => {});
|
|
440
|
+
|
|
441
|
+
const testPlugin = createBackendPlugin({
|
|
442
|
+
metadata: { pluginId: "test-backend" },
|
|
443
|
+
register(env) {
|
|
444
|
+
env.registerInit({ deps: {}, init: testBackendInit });
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Register mock services since core-services is mocked as no-op
|
|
449
|
+
pluginManager.registerService(
|
|
450
|
+
coreServices.queueManager,
|
|
451
|
+
createMockQueueManager()
|
|
452
|
+
);
|
|
453
|
+
pluginManager.registerService(coreServices.logger, createMockLogger());
|
|
454
|
+
pluginManager.registerService(coreServices.database, {} as never); // Mock database
|
|
455
|
+
|
|
456
|
+
// Use manual plugin injection with skipDiscovery to avoid loading real plugins
|
|
457
|
+
await pluginManager.loadPlugins(mockRouter, [testPlugin], {
|
|
458
|
+
skipDiscovery: true,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(testBackendInit).toHaveBeenCalled();
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
});
|