@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.
Files changed (46) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/drizzle/0000_loose_yellow_claw.sql +28 -0
  3. package/drizzle/meta/0000_snapshot.json +187 -0
  4. package/drizzle/meta/_journal.json +13 -0
  5. package/drizzle.config.ts +10 -0
  6. package/package.json +42 -0
  7. package/src/db.ts +20 -0
  8. package/src/health-check-plugin-integration.test.ts +93 -0
  9. package/src/index.ts +419 -0
  10. package/src/integration/event-bus.integration.test.ts +313 -0
  11. package/src/logger.ts +65 -0
  12. package/src/openapi-router.ts +177 -0
  13. package/src/plugin-lifecycle.test.ts +276 -0
  14. package/src/plugin-manager/api-router.ts +163 -0
  15. package/src/plugin-manager/core-services.ts +312 -0
  16. package/src/plugin-manager/dependency-sorter.ts +103 -0
  17. package/src/plugin-manager/deregistration-guard.ts +41 -0
  18. package/src/plugin-manager/extension-points.ts +85 -0
  19. package/src/plugin-manager/index.ts +13 -0
  20. package/src/plugin-manager/plugin-admin-router.ts +89 -0
  21. package/src/plugin-manager/plugin-loader.ts +464 -0
  22. package/src/plugin-manager/types.ts +14 -0
  23. package/src/plugin-manager.test.ts +464 -0
  24. package/src/plugin-manager.ts +431 -0
  25. package/src/rpc-rest-compat.test.ts +80 -0
  26. package/src/schema.ts +46 -0
  27. package/src/services/config-service.test.ts +66 -0
  28. package/src/services/config-service.ts +322 -0
  29. package/src/services/event-bus.test.ts +469 -0
  30. package/src/services/event-bus.ts +317 -0
  31. package/src/services/health-check-registry.test.ts +101 -0
  32. package/src/services/health-check-registry.ts +27 -0
  33. package/src/services/jwt.ts +45 -0
  34. package/src/services/keystore.test.ts +198 -0
  35. package/src/services/keystore.ts +136 -0
  36. package/src/services/plugin-installer.test.ts +90 -0
  37. package/src/services/plugin-installer.ts +70 -0
  38. package/src/services/queue-manager.ts +382 -0
  39. package/src/services/queue-plugin-registry.ts +17 -0
  40. package/src/services/queue-proxy.ts +182 -0
  41. package/src/services/service-registry.ts +35 -0
  42. package/src/test-preload.ts +114 -0
  43. package/src/utils/plugin-discovery.test.ts +383 -0
  44. package/src/utils/plugin-discovery.ts +157 -0
  45. package/src/utils/strip-public-schema.ts +40 -0
  46. package/tsconfig.json +6 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,225 @@
1
+ # @checkstack/backend
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
8
+ - Updated dependencies [d20d274]
9
+ - @checkstack/api-docs-common@0.0.2
10
+ - @checkstack/auth-common@0.0.2
11
+ - @checkstack/backend-api@0.0.2
12
+ - @checkstack/common@0.0.2
13
+ - @checkstack/drizzle-helper@0.0.2
14
+ - @checkstack/queue-api@0.0.2
15
+ - @checkstack/signal-backend@0.0.2
16
+ - @checkstack/signal-common@0.0.2
17
+
18
+ ## 0.1.4
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [b4eb432]
23
+ - Updated dependencies [a65e002]
24
+ - @checkstack/backend-api@1.1.0
25
+ - @checkstack/common@0.2.0
26
+ - @checkstack/auth-common@0.2.1
27
+ - @checkstack/queue-api@1.0.1
28
+ - @checkstack/signal-backend@0.1.1
29
+ - @checkstack/api-docs-common@0.0.3
30
+ - @checkstack/signal-common@0.1.1
31
+
32
+ ## 0.1.3
33
+
34
+ ### Patch Changes
35
+
36
+ - Updated dependencies [e26c08e]
37
+ - @checkstack/auth-common@0.2.0
38
+
39
+ ## 0.1.2
40
+
41
+ ### Patch Changes
42
+
43
+ - 0f8cc7d: Add runtime configuration API for Docker deployments
44
+
45
+ - Backend: Add `/api/config` endpoint serving `BASE_URL` at runtime
46
+ - Backend: Update CORS to use `BASE_URL` and auto-allow Vite dev server
47
+ - Backend: `INTERNAL_URL` now defaults to `localhost:3000` (no BASE_URL fallback)
48
+ - Frontend API: Add `RuntimeConfigProvider` context for runtime config
49
+ - Frontend: Use `RuntimeConfigProvider` from `frontend-api`
50
+ - Auth Frontend: Add `useAuthClient()` hook using runtime config
51
+
52
+ ## 0.1.1
53
+
54
+ ### Patch Changes
55
+
56
+ - f0bdec2: Fixed CI test failures by implementing proper module mocking infrastructure:
57
+ - Added test-preload.ts with comprehensive mocks for db, logger, and core-services
58
+ - Added skipDiscovery option to loadPlugins() for test isolation
59
+ - Configured bunfig.toml preload for workspace-wide test setup
60
+
61
+ ## 0.1.0
62
+
63
+ ### Minor Changes
64
+
65
+ - ffc28f6: ### Anonymous Role and Public Access
66
+
67
+ Introduces a configurable "anonymous" role for managing permissions available to unauthenticated users.
68
+
69
+ **Core Changes:**
70
+
71
+ - Added `userType: "public"` - endpoints accessible by both authenticated users (with their permissions) and anonymous users (with anonymous role permissions)
72
+ - Renamed `userType: "both"` to `"authenticated"` for clarity
73
+ - Renamed `isDefault` to `isAuthenticatedDefault` on Permission interface
74
+ - Added `isPublicDefault` flag for permissions that should be granted to the anonymous role by default
75
+
76
+ **Backend Infrastructure:**
77
+
78
+ - New `anonymous` system role created during auth-backend initialization
79
+ - New `disabled_public_default_permission` table tracks admin-disabled public defaults
80
+ - `autoAuthMiddleware` now checks anonymous role permissions for unauthenticated public endpoint access
81
+ - `AuthService.getAnonymousPermissions()` with 1-minute caching for performance
82
+ - Anonymous role filtered from `getRoles` endpoint (not assignable to users)
83
+ - Validation prevents assigning anonymous role to users
84
+
85
+ **Catalog Integration:**
86
+
87
+ - `catalog.read` permission now has both `isAuthenticatedDefault` and `isPublicDefault`
88
+ - Read endpoints (`getSystems`, `getGroups`, `getEntities`) now use `userType: "public"`
89
+
90
+ **UI:**
91
+
92
+ - New `PermissionGate` component for conditionally rendering content based on permissions
93
+
94
+ - 71275dd: fix: Anonymous and non-admin user authorization
95
+
96
+ - Fixed permission metadata preservation in `plugin-manager.ts` - changed from outdated `isDefault` field to `isAuthenticatedDefault` and `isPublicDefault`
97
+ - Added `pluginId` to `RpcContext` to enable proper permission ID matching
98
+ - Updated `autoAuthMiddleware` to prefix contract permission IDs with the pluginId from context, ensuring that contract permissions (e.g., `catalog.read`) correctly match database permissions (e.g., `catalog-backend.catalog.read`)
99
+ - Route now uses `/api/:pluginId/*` pattern with Hono path parameters for clean pluginId extraction
100
+
101
+ - b55fae6: Added realtime Signal Service for backend-to-frontend push notifications via WebSockets.
102
+
103
+ ## New Packages
104
+
105
+ - **@checkstack/signal-common**: Shared types including `Signal`, `SignalService`, `createSignal()`, and WebSocket protocol messages
106
+ - **@checkstack/signal-backend**: `SignalServiceImpl` with EventBus integration and Bun WebSocket handler using native pub/sub
107
+ - **@checkstack/signal-frontend**: React `SignalProvider` and `useSignal()` hook for consuming typed signals
108
+
109
+ ## Changes
110
+
111
+ - **@checkstack/backend-api**: Added `coreServices.signalService` reference for plugins to emit signals
112
+ - **@checkstack/backend**: Integrated WebSocket server at `/api/signals/ws` with session-based authentication
113
+
114
+ ## Usage
115
+
116
+ Backend plugins can emit signals:
117
+
118
+ ```typescript
119
+ import { coreServices } from "@checkstack/backend-api";
120
+ import { NOTIFICATION_RECEIVED } from "@checkstack/notification-common";
121
+
122
+ const signalService = context.signalService;
123
+ await signalService.sendToUser(NOTIFICATION_RECEIVED, userId, { ... });
124
+ ```
125
+
126
+ Frontend components subscribe to signals:
127
+
128
+ ```tsx
129
+ import { useSignal } from "@checkstack/signal-frontend";
130
+ import { NOTIFICATION_RECEIVED } from "@checkstack/notification-common";
131
+
132
+ useSignal(NOTIFICATION_RECEIVED, (payload) => {
133
+ // Handle realtime notification
134
+ });
135
+ ```
136
+
137
+ ### Patch Changes
138
+
139
+ - ae19ff6: Add configurable state thresholds for health check evaluation
140
+
141
+ **@checkstack/backend-api:**
142
+
143
+ - Added `VersionedData<T>` generic interface as base for all versioned data structures
144
+ - `VersionedConfig<T>` now extends `VersionedData<T>` and adds `pluginId`
145
+ - Added `migrateVersionedData()` utility function for running migrations on any `VersionedData` subtype
146
+
147
+ **@checkstack/backend:**
148
+
149
+ - Refactored `ConfigMigrationRunner` to use the new `migrateVersionedData` utility
150
+
151
+ **@checkstack/healthcheck-common:**
152
+
153
+ - Added state threshold schemas with two evaluation modes (consecutive, window)
154
+ - Added `stateThresholds` field to `AssociateHealthCheckSchema`
155
+ - Added `getSystemHealthStatus` RPC endpoint contract
156
+
157
+ **@checkstack/healthcheck-backend:**
158
+
159
+ - Added `stateThresholds` column to `system_health_checks` table
160
+ - Added `state-evaluator.ts` with health status evaluation logic
161
+ - Added `state-thresholds-migrations.ts` with migration infrastructure
162
+ - Added `getSystemHealthStatus` RPC handler
163
+
164
+ **@checkstack/healthcheck-frontend:**
165
+
166
+ - Updated `SystemHealthBadge` to use new backend endpoint
167
+
168
+ - 81f3f85: ## Breaking: Unified Versioned<T> Architecture
169
+
170
+ Refactored the versioning system to use a unified `Versioned<T>` class instead of separate `VersionedSchema`, `VersionedData`, and `VersionedConfig` types.
171
+
172
+ ### Breaking Changes
173
+
174
+ - **`VersionedSchema<T>`** is replaced by `Versioned<T>` class
175
+ - **`VersionedData<T>`** is replaced by `VersionedRecord<T>` interface
176
+ - **`VersionedConfig<T>`** is replaced by `VersionedPluginRecord<T>` interface
177
+ - **`ConfigMigration<F, T>`** is replaced by `Migration<F, T>` interface
178
+ - **`MigrationChain<T>`** is removed (use `Migration<unknown, unknown>[]`)
179
+ - **`migrateVersionedData()`** is removed (use `versioned.parse()`)
180
+ - **`ConfigMigrationRunner`** is removed (migrations are internal to Versioned)
181
+
182
+ ### Migration Guide
183
+
184
+ Before:
185
+
186
+ ```typescript
187
+ const strategy: HealthCheckStrategy = {
188
+ config: {
189
+ version: 1,
190
+ schema: mySchema,
191
+ migrations: [],
192
+ },
193
+ };
194
+ const data = await migrateVersionedData(stored, 1, migrations);
195
+ ```
196
+
197
+ After:
198
+
199
+ ```typescript
200
+ const strategy: HealthCheckStrategy = {
201
+ config: new Versioned({
202
+ version: 1,
203
+ schema: mySchema,
204
+ migrations: [],
205
+ }),
206
+ };
207
+ const data = await strategy.config.parse(stored);
208
+ ```
209
+
210
+ - Updated dependencies [ffc28f6]
211
+ - Updated dependencies [e4d83fc]
212
+ - Updated dependencies [71275dd]
213
+ - Updated dependencies [ae19ff6]
214
+ - Updated dependencies [32f2535]
215
+ - Updated dependencies [b55fae6]
216
+ - Updated dependencies [b354ab3]
217
+ - Updated dependencies [8e889b4]
218
+ - Updated dependencies [81f3f85]
219
+ - @checkstack/common@0.1.0
220
+ - @checkstack/backend-api@1.0.0
221
+ - @checkstack/auth-common@0.1.0
222
+ - @checkstack/queue-api@1.0.0
223
+ - @checkstack/signal-common@0.1.0
224
+ - @checkstack/signal-backend@0.1.0
225
+ - @checkstack/api-docs-common@0.0.2
@@ -0,0 +1,28 @@
1
+ CREATE TABLE "jwt_keys" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "public_key" text NOT NULL,
4
+ "private_key" text NOT NULL,
5
+ "algorithm" text NOT NULL,
6
+ "created_at" text NOT NULL,
7
+ "expires_at" text,
8
+ "revoked_at" text
9
+ );
10
+ --> statement-breakpoint
11
+ CREATE TABLE "plugin_configs" (
12
+ "plugin_id" text NOT NULL,
13
+ "config_id" text NOT NULL,
14
+ "data" jsonb NOT NULL,
15
+ "updated_at" timestamp DEFAULT now() NOT NULL,
16
+ CONSTRAINT "plugin_configs_plugin_id_config_id_pk" PRIMARY KEY("plugin_id","config_id")
17
+ );
18
+ --> statement-breakpoint
19
+ CREATE TABLE "plugins" (
20
+ "id" serial PRIMARY KEY NOT NULL,
21
+ "name" text NOT NULL,
22
+ "path" text NOT NULL,
23
+ "is_uninstallable" boolean DEFAULT false NOT NULL,
24
+ "config" json DEFAULT '{}'::json,
25
+ "enabled" boolean DEFAULT true NOT NULL,
26
+ "type" text DEFAULT 'backend' NOT NULL,
27
+ CONSTRAINT "plugins_name_unique" UNIQUE("name")
28
+ );
@@ -0,0 +1,187 @@
1
+ {
2
+ "id": "0f04d1ab-b47d-4057-b267-4b904deebd99",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.jwt_keys": {
8
+ "name": "jwt_keys",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "public_key": {
18
+ "name": "public_key",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "private_key": {
24
+ "name": "private_key",
25
+ "type": "text",
26
+ "primaryKey": false,
27
+ "notNull": true
28
+ },
29
+ "algorithm": {
30
+ "name": "algorithm",
31
+ "type": "text",
32
+ "primaryKey": false,
33
+ "notNull": true
34
+ },
35
+ "created_at": {
36
+ "name": "created_at",
37
+ "type": "text",
38
+ "primaryKey": false,
39
+ "notNull": true
40
+ },
41
+ "expires_at": {
42
+ "name": "expires_at",
43
+ "type": "text",
44
+ "primaryKey": false,
45
+ "notNull": false
46
+ },
47
+ "revoked_at": {
48
+ "name": "revoked_at",
49
+ "type": "text",
50
+ "primaryKey": false,
51
+ "notNull": false
52
+ }
53
+ },
54
+ "indexes": {},
55
+ "foreignKeys": {},
56
+ "compositePrimaryKeys": {},
57
+ "uniqueConstraints": {},
58
+ "policies": {},
59
+ "checkConstraints": {},
60
+ "isRLSEnabled": false
61
+ },
62
+ "public.plugin_configs": {
63
+ "name": "plugin_configs",
64
+ "schema": "",
65
+ "columns": {
66
+ "plugin_id": {
67
+ "name": "plugin_id",
68
+ "type": "text",
69
+ "primaryKey": false,
70
+ "notNull": true
71
+ },
72
+ "config_id": {
73
+ "name": "config_id",
74
+ "type": "text",
75
+ "primaryKey": false,
76
+ "notNull": true
77
+ },
78
+ "data": {
79
+ "name": "data",
80
+ "type": "jsonb",
81
+ "primaryKey": false,
82
+ "notNull": true
83
+ },
84
+ "updated_at": {
85
+ "name": "updated_at",
86
+ "type": "timestamp",
87
+ "primaryKey": false,
88
+ "notNull": true,
89
+ "default": "now()"
90
+ }
91
+ },
92
+ "indexes": {},
93
+ "foreignKeys": {},
94
+ "compositePrimaryKeys": {
95
+ "plugin_configs_plugin_id_config_id_pk": {
96
+ "name": "plugin_configs_plugin_id_config_id_pk",
97
+ "columns": [
98
+ "plugin_id",
99
+ "config_id"
100
+ ]
101
+ }
102
+ },
103
+ "uniqueConstraints": {},
104
+ "policies": {},
105
+ "checkConstraints": {},
106
+ "isRLSEnabled": false
107
+ },
108
+ "public.plugins": {
109
+ "name": "plugins",
110
+ "schema": "",
111
+ "columns": {
112
+ "id": {
113
+ "name": "id",
114
+ "type": "serial",
115
+ "primaryKey": true,
116
+ "notNull": true
117
+ },
118
+ "name": {
119
+ "name": "name",
120
+ "type": "text",
121
+ "primaryKey": false,
122
+ "notNull": true
123
+ },
124
+ "path": {
125
+ "name": "path",
126
+ "type": "text",
127
+ "primaryKey": false,
128
+ "notNull": true
129
+ },
130
+ "is_uninstallable": {
131
+ "name": "is_uninstallable",
132
+ "type": "boolean",
133
+ "primaryKey": false,
134
+ "notNull": true,
135
+ "default": false
136
+ },
137
+ "config": {
138
+ "name": "config",
139
+ "type": "json",
140
+ "primaryKey": false,
141
+ "notNull": false,
142
+ "default": "'{}'::json"
143
+ },
144
+ "enabled": {
145
+ "name": "enabled",
146
+ "type": "boolean",
147
+ "primaryKey": false,
148
+ "notNull": true,
149
+ "default": true
150
+ },
151
+ "type": {
152
+ "name": "type",
153
+ "type": "text",
154
+ "primaryKey": false,
155
+ "notNull": true,
156
+ "default": "'backend'"
157
+ }
158
+ },
159
+ "indexes": {},
160
+ "foreignKeys": {},
161
+ "compositePrimaryKeys": {},
162
+ "uniqueConstraints": {
163
+ "plugins_name_unique": {
164
+ "name": "plugins_name_unique",
165
+ "nullsNotDistinct": false,
166
+ "columns": [
167
+ "name"
168
+ ]
169
+ }
170
+ },
171
+ "policies": {},
172
+ "checkConstraints": {},
173
+ "isRLSEnabled": false
174
+ }
175
+ },
176
+ "enums": {},
177
+ "schemas": {},
178
+ "sequences": {},
179
+ "roles": {},
180
+ "policies": {},
181
+ "views": {},
182
+ "_meta": {
183
+ "columns": {},
184
+ "schemas": {},
185
+ "tables": {}
186
+ }
187
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1767314744846,
9
+ "tag": "0000_loose_yellow_claw",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ export default defineConfig({
4
+ schema: "./src/schema.ts",
5
+ out: "./drizzle",
6
+ dialect: "postgresql",
7
+ dbCredentials: {
8
+ url: process.env.DATABASE_URL!,
9
+ },
10
+ });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@checkstack/backend",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "bun --env-file=../../.env --watch src/index.ts",
7
+ "typecheck": "tsc --noEmit",
8
+ "generate": "bun --env-file=../../.env run drizzle-kit generate",
9
+ "lint": "bun run lint:code",
10
+ "lint:code": "eslint . --max-warnings 0"
11
+ },
12
+ "dependencies": {
13
+ "@checkstack/api-docs-common": "workspace:*",
14
+ "@checkstack/auth-common": "workspace:*",
15
+ "@checkstack/backend-api": "workspace:*",
16
+ "@checkstack/common": "workspace:*",
17
+ "@checkstack/drizzle-helper": "workspace:*",
18
+ "@checkstack/queue-api": "workspace:*",
19
+ "@checkstack/signal-backend": "workspace:*",
20
+ "@checkstack/signal-common": "workspace:*",
21
+ "@hono/zod-validator": "^0.7.6",
22
+ "@orpc/client": "^1.13.2",
23
+ "@orpc/openapi": "^1.13.2",
24
+ "@orpc/server": "^1.13.2",
25
+ "@orpc/zod": "^1.13.2",
26
+ "better-auth": "^1.4.7",
27
+ "drizzle-orm": "^0.45.1",
28
+ "hono": "^4.0.0",
29
+ "jose": "^6.1.3",
30
+ "pg": "^8.11.0",
31
+ "winston": "^3.19.0",
32
+ "zod": "^4.2.1"
33
+ },
34
+ "devDependencies": {
35
+ "drizzle-kit": "^0.31.8",
36
+ "@types/pg": "^8.11.0",
37
+ "@types/bun": "latest",
38
+ "@checkstack/tsconfig": "workspace:*",
39
+ "@checkstack/scripts": "workspace:*",
40
+ "@checkstack/test-utils-backend": "workspace:*"
41
+ }
42
+ }
package/src/db.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { drizzle } from "drizzle-orm/node-postgres";
2
+ import { Pool } from "pg";
3
+ import * as schema from "./schema";
4
+
5
+ // Basic connection string sometimes fails with Bun + pg + docker SASL
6
+ // parsing manually or relying on pg to pick up ENV variables if we don't pass anything
7
+ // But we passed connectionString.
8
+
9
+ // Explicitly parse config or fallback to individual env vars to avoid SASL string errors
10
+ const connectionString = process.env.DATABASE_URL;
11
+
12
+ if (!connectionString) {
13
+ throw new Error("DATABASE_URL is not defined");
14
+ }
15
+
16
+ export const adminPool = new Pool({
17
+ connectionString,
18
+ });
19
+
20
+ export const db = drizzle(adminPool, { schema });
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, mock, beforeEach } from "bun:test";
2
+ import {
3
+ coreServices,
4
+ createBackendPlugin,
5
+ HealthCheckStrategy,
6
+ Versioned,
7
+ } from "@checkstack/backend-api";
8
+ import {
9
+ createMockQueueManager,
10
+ createMockLogger,
11
+ } from "@checkstack/test-utils-backend";
12
+ import { z } from "zod";
13
+
14
+ // Note: ./db and ./logger are mocked via test-preload.ts (bunfig.toml preload)
15
+ // This ensures mocks are in place BEFORE any module imports them
16
+
17
+ import { PluginManager } from "./plugin-manager";
18
+
19
+ describe("HealthCheck Plugin Integration", () => {
20
+ let pluginManager: PluginManager;
21
+
22
+ beforeEach(() => {
23
+ pluginManager = new PluginManager();
24
+ });
25
+
26
+ it("should allow a plugin to register a health check strategy", async () => {
27
+ const mockRouter = {
28
+ route: mock(),
29
+ all: mock(),
30
+ newResponse: mock(),
31
+ } as never;
32
+
33
+ // Define a mock execute function for the strategy
34
+ const mockExecute = mock(async () => ({ status: "healthy" as const }));
35
+
36
+ // 1. Define a mock strategy
37
+ const mockStrategy: HealthCheckStrategy = {
38
+ id: "test-strategy",
39
+ displayName: "Test Strategy",
40
+ description: "A test strategy for integration testing",
41
+ config: new Versioned({
42
+ version: 1,
43
+ schema: z.object({}),
44
+ }),
45
+ aggregatedResult: new Versioned({
46
+ version: 1,
47
+ schema: z.record(z.string(), z.unknown()),
48
+ }),
49
+ execute: mockExecute,
50
+ aggregateResult: mock(() => ({})),
51
+ };
52
+
53
+ // 2. Define a mock plugin that registers this strategy
54
+ const testPlugin = createBackendPlugin({
55
+ metadata: { pluginId: "test-plugin" },
56
+ register(env) {
57
+ env.registerInit({
58
+ deps: {
59
+ healthCheckRegistry: coreServices.healthCheckRegistry,
60
+ },
61
+ init: async ({ healthCheckRegistry }) => {
62
+ healthCheckRegistry.register(mockStrategy);
63
+ },
64
+ });
65
+ },
66
+ });
67
+
68
+ // Register mock services since core-services is mocked as no-op
69
+ pluginManager.registerService(
70
+ coreServices.queueManager,
71
+ createMockQueueManager()
72
+ );
73
+ pluginManager.registerService(coreServices.logger, createMockLogger());
74
+ pluginManager.registerService(coreServices.database, {} as never); // Mock database
75
+
76
+ // 4. Load plugins using the PluginManager with manual injection
77
+ await pluginManager.loadPlugins(mockRouter, [testPlugin], {
78
+ skipDiscovery: true,
79
+ });
80
+
81
+ // 5. Verify the strategy is registered in the registry managed by PluginManager
82
+ const registry = await pluginManager.getService(
83
+ coreServices.healthCheckRegistry
84
+ );
85
+ expect(registry).toBeDefined();
86
+
87
+ const retrieved = registry?.getStrategy(mockStrategy.id);
88
+ expect(retrieved).toBe(mockStrategy);
89
+ expect(retrieved?.displayName).toBe("Test Strategy");
90
+ expect(retrieved?.id).toBe("test-strategy");
91
+ expect(retrieved?.execute).toBe(mockExecute);
92
+ });
93
+ });