create-windy 0.3.18 → 0.4.0
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/README.md +28 -13
- package/dist/cli.js +788 -280
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +18 -8
- package/template/README.md +32 -22
- package/template/apps/agent-server/src/southwind_agent_server/adk_debug.py +38 -5
- package/template/apps/agent-server/tests/test_adk_debug_result_polling.py +139 -0
- package/template/apps/server/Dockerfile +1 -0
- package/template/apps/server/package.json +1 -0
- package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
- package/template/apps/server/scripts/validate-module-composition.ts +28 -0
- package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
- package/template/apps/server/src/agent/routes.test.ts +20 -7
- package/template/apps/server/src/agent/routes.ts +14 -0
- package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
- package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
- package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
- package/template/apps/server/src/app/server-composition.ts +56 -0
- package/template/apps/server/src/application-services.ts +2 -0
- package/template/apps/server/src/bulk-data/service.ts +14 -0
- package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
- package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
- package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
- package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
- package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
- package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
- package/template/apps/server/src/feature/routes.ts +33 -15
- package/template/apps/server/src/foundation-activation.test.ts +60 -0
- package/template/apps/server/src/foundation.ts +64 -5
- package/template/apps/server/src/guards.test.ts +23 -0
- package/template/apps/server/src/guards.ts +12 -0
- package/template/apps/server/src/index.ts +28 -34
- package/template/apps/server/src/installed-business-modules.ts +13 -24
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
- package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
- package/template/apps/server/src/license/activation.test.ts +30 -0
- package/template/apps/server/src/license/activation.ts +26 -0
- package/template/apps/server/src/license/restriction.test.ts +16 -0
- package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
- package/template/apps/server/src/license/routes.ts +20 -4
- package/template/apps/server/src/license/runtime-service.test.ts +10 -2
- package/template/apps/server/src/module-bootstrap.ts +14 -23
- package/template/apps/server/src/module-composition.test.ts +43 -2
- package/template/apps/server/src/module-composition.ts +29 -0
- package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
- package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
- package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
- package/template/apps/server/src/module-host/durable-context.ts +42 -2
- package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
- package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
- package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
- package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
- package/template/apps/server/src/module-host/initialize.test.ts +78 -2
- package/template/apps/server/src/module-host/initialize.ts +15 -4
- package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
- package/template/apps/server/src/module-host/project-attachments.ts +58 -0
- package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
- package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
- package/template/apps/server/src/module-host/project-composition.ts +311 -0
- package/template/apps/server/src/module-host/request-context.ts +3 -0
- package/template/apps/server/src/module-host/route-declaration.ts +15 -6
- package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
- package/template/apps/server/src/module-host/route-installer.ts +12 -2
- package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
- package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
- package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
- package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
- package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
- package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
- package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
- package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
- package/template/apps/server/src/module-host/task-definitions.ts +1 -0
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
- package/template/apps/server/src/module-host.test.ts +2 -0
- package/template/apps/server/src/module-preflight.test.ts +289 -0
- package/template/apps/server/src/module-preflight.ts +96 -0
- package/template/apps/server/src/module-runtime-validation.ts +1 -0
- package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
- package/template/apps/server/src/platform-http-runtime.ts +32 -7
- package/template/apps/server/src/route-guards.test.ts +22 -0
- package/template/apps/server/src/route-guards.ts +10 -3
- package/template/apps/server/src/runtime-audit-query.ts +50 -0
- package/template/apps/server/src/runtime-feature.test.ts +46 -0
- package/template/apps/server/src/runtime-feature.ts +5 -0
- package/template/apps/server/src/runtime.ts +10 -49
- package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
- package/template/apps/server/src/scheduler/audit.ts +1 -1
- package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
- package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
- package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
- package/template/apps/server/src/scheduler/service.test.ts +1 -0
- package/template/apps/server/src/scheduler/service.ts +18 -21
- package/template/apps/server/src/scheduler/types.ts +5 -1
- package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
- package/template/apps/server/src/startup-composition-validation.ts +54 -0
- package/template/apps/server/src/startup-preflight.test.ts +36 -0
- package/template/apps/server/src/startup-preflight.ts +37 -0
- package/template/apps/server/src/system/audit-routes.test.ts +9 -3
- package/template/apps/server/src/system/department-routes.test.ts +5 -3
- package/template/apps/server/src/system/feature-routes.test.ts +4 -3
- package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
- package/template/apps/server/src/system/interface-catalog.ts +3 -0
- package/template/apps/server/src/system/menu-routes.test.ts +5 -3
- package/template/apps/server/src/system/route-test-runtime.ts +24 -0
- package/template/apps/server/src/system/routes-validation.test.ts +7 -3
- package/template/apps/server/src/system/routes.test.ts +21 -11
- package/template/apps/server/src/system/routes.ts +11 -2
- package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
- package/template/apps/server/src/work-order/routes.test.ts +8 -0
- package/template/apps/server/src/work-order/task.test.ts +6 -1
- package/template/apps/web/Dockerfile +5 -0
- package/template/apps/web/package.json +1 -0
- package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
- package/template/apps/web/src/app/web-composition.ts +24 -0
- package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
- package/template/apps/web/src/layout/AppSidebar.vue +8 -1
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
- package/template/apps/web/src/layout/navigation.ts +29 -4
- package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
- package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
- package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
- package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
- package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
- package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
- package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
- package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
- package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
- package/template/apps/web/src/router/business-routes.ts +32 -45
- package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
- package/template/apps/web/src/router/index.ts +64 -54
- package/template/apps/web/src/router/manifest-routes.ts +27 -7
- package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
- package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
- package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
- package/template/apps/web/src/router/web-composition-host.ts +20 -0
- package/template/apps/web/src/services/work-order-api.ts +16 -0
- package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
- package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
- package/template/apps/web/vite.config.ts +22 -1
- package/template/docs/architecture/migration-bundle.md +10 -7
- package/template/docs/architecture/module-manifest-composition.md +85 -3
- package/template/docs/architecture/search-provider-protocol.md +6 -1
- package/template/docs/architecture/server-module-interface.md +70 -7
- package/template/docs/development/business-web-routes.md +53 -50
- package/template/docs/development/module-lifecycle.md +48 -54
- package/template/docs/platform/agent-runtime.md +2 -0
- package/template/docs/platform/navigation-registry.md +5 -4
- package/template/package.json +1 -1
- package/template/packages/config/package.json +1 -1
- package/template/packages/database/package.json +1 -1
- package/template/packages/example-work-order/package.json +6 -0
- package/template/packages/example-work-order/src/admin-page.ts +78 -0
- package/template/packages/example-work-order/src/admin.ts +35 -0
- package/template/packages/example-work-order/src/manifest.test.ts +62 -5
- package/template/packages/example-work-order/src/manifest.ts +54 -7
- package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
- package/template/packages/example-work-order/src/server-module.ts +2 -2
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/modules/index.ts +4 -0
- package/template/packages/modules/package.json +3 -2
- package/template/packages/modules/src/admin-ui-entry.ts +8 -0
- package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
- package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
- package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
- package/template/packages/modules/src/capability-audit-validation.ts +38 -0
- package/template/packages/modules/src/capability-guard-validation.ts +148 -0
- package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
- package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
- package/template/packages/modules/src/composition.test.ts +74 -9
- package/template/packages/modules/src/composition.ts +8 -0
- package/template/packages/modules/src/manifest.ts +1 -0
- package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
- package/template/packages/modules/src/module-host-range.test.ts +57 -0
- package/template/packages/modules/src/module-host-range.ts +125 -0
- package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
- package/template/packages/modules/src/project-module-assembly.ts +126 -0
- package/template/packages/modules/src/system-admin-routes.ts +6 -5
- package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
- package/template/packages/modules/src/system-agent-tools.ts +2 -2
- package/template/packages/modules/src/system-api-binding.ts +43 -9
- package/template/packages/modules/src/system-api-control-plane.ts +202 -0
- package/template/packages/modules/src/system-api-data-governance.ts +4 -2
- package/template/packages/modules/src/system-api-operational.ts +57 -8
- package/template/packages/modules/src/system-api-permissions.ts +115 -21
- package/template/packages/modules/src/system-api-watermark.ts +3 -0
- package/template/packages/modules/src/system-audit-actions.ts +7 -0
- package/template/packages/modules/src/system-data-governance.ts +5 -2
- package/template/packages/modules/src/system-features.ts +31 -0
- package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
- package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
- package/template/packages/modules/src/system-module-catalog.ts +68 -7
- package/template/packages/modules/src/system-module-tasks.ts +8 -1
- package/template/packages/modules/src/system-modules-access.test.ts +4 -0
- package/template/packages/modules/src/system-modules.test.ts +10 -2
- package/template/packages/modules/src/system-modules.ts +6 -5
- package/template/packages/modules/src/system-permissions.ts +4 -0
- package/template/packages/modules/user/module.ts +19 -2
- package/template/packages/server-sdk/index.ts +8 -0
- package/template/packages/server-sdk/package.json +2 -1
- package/template/packages/server-sdk/src/durable-handler.ts +16 -1
- package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
- package/template/packages/server-sdk/src/module-adapter.ts +59 -0
- package/template/packages/server-sdk/src/route-definition.ts +3 -2
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.test.ts +7 -0
- package/template/packages/shared/src/audit.ts +16 -0
- package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
- package/template/packages/shared/src/rbac.ts +2 -0
- package/template/apps/web/src/app/business-routes.ts +0 -22
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUIDv7 } from "bun";
|
|
2
2
|
import type { SchedulerOperationsSnapshot } from "@southwind-ai/shared";
|
|
3
3
|
import { runGuardedBackgroundTask } from "../background/task-guard.js";
|
|
4
|
-
import type { RequestGuardContext } from "../guards.js";
|
|
4
|
+
import type { RequestActor, RequestGuardContext } from "../guards.js";
|
|
5
5
|
import type { LegacyDurableAdapter } from "../jobs/legacy-adapter.js";
|
|
6
6
|
import type { createServerRuntime } from "../runtime.js";
|
|
7
7
|
import type { SchedulerRepository } from "./repository.js";
|
|
@@ -41,6 +41,9 @@ export class SchedulerService {
|
|
|
41
41
|
private readonly runtime: ServerRuntime,
|
|
42
42
|
private readonly clock: SchedulerClock = systemClock,
|
|
43
43
|
private readonly durable?: LegacyDurableAdapter,
|
|
44
|
+
private readonly resolveActor?: (
|
|
45
|
+
actorId: string,
|
|
46
|
+
) => Promise<RequestActor | undefined>,
|
|
44
47
|
) {
|
|
45
48
|
this.recorder = new SchedulerExecutionRecorder(repository, runtime, clock);
|
|
46
49
|
validateTaskDefinitions(definitions);
|
|
@@ -196,13 +199,14 @@ export class SchedulerService {
|
|
|
196
199
|
await this.executeScheduled(run, false);
|
|
197
200
|
return;
|
|
198
201
|
}
|
|
199
|
-
await this.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
const actor = await this.resolveActor?.(run.requestedBy);
|
|
203
|
+
const context = await this.runtime.createGuardContextForActor(
|
|
204
|
+
new Request(
|
|
205
|
+
`http://scheduler.local/tasks/${encodeURIComponent(run.taskKey)}/resume`,
|
|
206
|
+
),
|
|
207
|
+
actor ?? deniedResumeActor(run.requestedBy),
|
|
205
208
|
);
|
|
209
|
+
await this.execute(run, definition, context, true, false);
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
private async executeScheduled(run: ScheduledTaskRun, useDurable = true) {
|
|
@@ -262,7 +266,7 @@ export class SchedulerService {
|
|
|
262
266
|
finishedAt: this.clock.now(),
|
|
263
267
|
error: guarded.reason,
|
|
264
268
|
});
|
|
265
|
-
this.recorder.denied(context, blocked, guarded.reason);
|
|
269
|
+
this.recorder.denied(context, blocked, definition, guarded.reason);
|
|
266
270
|
return blocked;
|
|
267
271
|
}
|
|
268
272
|
return this.recorder.success(run, definition, context, guarded.value);
|
|
@@ -321,19 +325,12 @@ export class SchedulerService {
|
|
|
321
325
|
}
|
|
322
326
|
}
|
|
323
327
|
|
|
324
|
-
function
|
|
325
|
-
run: ScheduledTaskRun,
|
|
326
|
-
definition: ScheduledTaskDefinition,
|
|
327
|
-
): RequestGuardContext {
|
|
328
|
+
function deniedResumeActor(actorId: string): RequestActor {
|
|
328
329
|
return {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
roleCodes: [],
|
|
335
|
-
permissionKeys: [definition.permissionKey],
|
|
336
|
-
},
|
|
337
|
-
startedAt: new Date().toISOString(),
|
|
330
|
+
id: actorId,
|
|
331
|
+
type: "user",
|
|
332
|
+
name: actorId,
|
|
333
|
+
roleCodes: [],
|
|
334
|
+
permissionKeys: [],
|
|
338
335
|
};
|
|
339
336
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AuditActionType,
|
|
3
|
+
FeatureFlagDefinition,
|
|
4
|
+
} from "@southwind-ai/shared";
|
|
2
5
|
|
|
3
6
|
export interface SchedulerClock {
|
|
4
7
|
now(): Date;
|
|
@@ -20,6 +23,7 @@ export interface ScheduledTaskDefinition {
|
|
|
20
23
|
maxAttempts: number;
|
|
21
24
|
retryDelaySeconds: number;
|
|
22
25
|
permissionKey: string;
|
|
26
|
+
auditAction: AuditActionType;
|
|
23
27
|
feature: FeatureFlagDefinition;
|
|
24
28
|
handler: ScheduledTaskHandler;
|
|
25
29
|
isIdleResult?: (value: Record<string, unknown> | void) => boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateCompositionBeforeStartup } from "./startup-composition-validation.js";
|
|
3
|
+
|
|
4
|
+
describe("完整 Server Composition 启动前门禁", () => {
|
|
5
|
+
test("正常启动先执行无数据库验证子进程", () => {
|
|
6
|
+
let databaseUrlSeen = "not-called";
|
|
7
|
+
validateCompositionBeforeStartup(
|
|
8
|
+
["bun", "src/index.ts"],
|
|
9
|
+
(_command, _args, options) => {
|
|
10
|
+
databaseUrlSeen = options.env.DATABASE_URL ?? "";
|
|
11
|
+
return { status: 0 };
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(databaseUrlSeen).toBe("");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("验证子进程自身不递归,并把失败传回启动进程", () => {
|
|
19
|
+
let calls = 0;
|
|
20
|
+
validateCompositionBeforeStartup(
|
|
21
|
+
["bun", "src/index.ts", "--validate-module-composition"],
|
|
22
|
+
() => {
|
|
23
|
+
calls += 1;
|
|
24
|
+
return { status: 0 };
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
expect(calls).toBe(0);
|
|
28
|
+
|
|
29
|
+
expect(() =>
|
|
30
|
+
validateCompositionBeforeStartup(["bun", "src/index.ts"], () => ({
|
|
31
|
+
status: 1,
|
|
32
|
+
})),
|
|
33
|
+
).toThrow("启动前校验失败");
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
const VALIDATION_ARGUMENT = "--validate-module-composition";
|
|
5
|
+
const validatorPath = fileURLToPath(
|
|
6
|
+
new URL("../scripts/validate-module-composition.ts", import.meta.url),
|
|
7
|
+
);
|
|
8
|
+
const serverRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
9
|
+
|
|
10
|
+
interface ValidationProcessResult {
|
|
11
|
+
readonly status: number | null;
|
|
12
|
+
readonly error?: Error;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type SpawnValidationProcess = (
|
|
16
|
+
command: string,
|
|
17
|
+
args: readonly string[],
|
|
18
|
+
options: {
|
|
19
|
+
readonly cwd: string;
|
|
20
|
+
readonly env: NodeJS.ProcessEnv;
|
|
21
|
+
readonly stdio: "inherit";
|
|
22
|
+
},
|
|
23
|
+
) => ValidationProcessResult;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 正常启动先用无数据库子进程走一遍与 build 相同的完整路由/任务/Provider 门禁。
|
|
27
|
+
*/
|
|
28
|
+
export function validateCompositionBeforeStartup(
|
|
29
|
+
argv: readonly string[] = process.argv,
|
|
30
|
+
spawn: SpawnValidationProcess = spawnValidationProcess,
|
|
31
|
+
): void {
|
|
32
|
+
if (argv.includes(VALIDATION_ARGUMENT)) return;
|
|
33
|
+
const env = { ...process.env };
|
|
34
|
+
delete env.DATABASE_URL;
|
|
35
|
+
const result = spawn(process.execPath, [validatorPath], {
|
|
36
|
+
cwd: serverRoot,
|
|
37
|
+
env,
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
});
|
|
40
|
+
if (result.error) throw result.error;
|
|
41
|
+
if (result.status !== 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Server 完整 Module Composition 启动前校验失败(exit ${result.status ?? "unknown"})`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function spawnValidationProcess(
|
|
49
|
+
command: string,
|
|
50
|
+
args: readonly string[],
|
|
51
|
+
options: Parameters<SpawnValidationProcess>[2],
|
|
52
|
+
): ValidationProcessResult {
|
|
53
|
+
return spawnSync(command, [...args], options);
|
|
54
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { runPreflightBeforeRecovery } from "./startup-preflight.js";
|
|
3
|
+
|
|
4
|
+
describe("Server 启动前门禁顺序", () => {
|
|
5
|
+
test("先完成无持久化 preflight,再恢复审计队列", async () => {
|
|
6
|
+
const calls: string[] = [];
|
|
7
|
+
const result = await runPreflightBeforeRecovery(
|
|
8
|
+
() => {
|
|
9
|
+
calls.push("prepare");
|
|
10
|
+
return Promise.resolve("ready");
|
|
11
|
+
},
|
|
12
|
+
() => {
|
|
13
|
+
calls.push("recover");
|
|
14
|
+
return Promise.resolve();
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
expect(result).toBe("ready");
|
|
19
|
+
expect(calls).toEqual(["prepare", "recover"]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("preflight 失败时不触发恢复写入", async () => {
|
|
23
|
+
let recovered = false;
|
|
24
|
+
await expect(
|
|
25
|
+
runPreflightBeforeRecovery(
|
|
26
|
+
() => Promise.reject(new Error("invalid composition")),
|
|
27
|
+
() => {
|
|
28
|
+
recovered = true;
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
},
|
|
31
|
+
),
|
|
32
|
+
).rejects.toThrow("invalid composition");
|
|
33
|
+
|
|
34
|
+
expect(recovered).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ModuleUploadPolicy } from "@southwind-ai/server-sdk";
|
|
2
|
+
import { prepareInstalledServerModules } from "./module-preflight.js";
|
|
3
|
+
import { createPlatformBootstrap } from "./platform-bootstrap.js";
|
|
4
|
+
// @windy-module system.storage begin
|
|
5
|
+
import { createPlatformUploadPolicies } from "./storage/runtime.js";
|
|
6
|
+
// @windy-module system.storage end
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 在恢复审计队列、执行迁移或写入目录前,完成项目装配、Host 与 attachment 门禁。
|
|
10
|
+
*/
|
|
11
|
+
export async function createPreparedPlatformBootstrap() {
|
|
12
|
+
const bootstrap = createPlatformBootstrap();
|
|
13
|
+
const baseUploadPolicies: ModuleUploadPolicy[] = [
|
|
14
|
+
// @windy-module system.storage begin
|
|
15
|
+
...createPlatformUploadPolicies(bootstrap.platformConfig),
|
|
16
|
+
// @windy-module system.storage end
|
|
17
|
+
];
|
|
18
|
+
const recoverAuditQueue = () =>
|
|
19
|
+
// @windy-module system.audit begin
|
|
20
|
+
bootstrap.persistence?.auditWriter.recover() ??
|
|
21
|
+
// @windy-module system.audit end
|
|
22
|
+
Promise.resolve();
|
|
23
|
+
const preparedModules = await runPreflightBeforeRecovery(
|
|
24
|
+
() => prepareInstalledServerModules(baseUploadPolicies),
|
|
25
|
+
recoverAuditQueue,
|
|
26
|
+
);
|
|
27
|
+
return { ...bootstrap, preparedModules };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runPreflightBeforeRecovery<T>(
|
|
31
|
+
prepare: () => Promise<T>,
|
|
32
|
+
recover: () => Promise<void>,
|
|
33
|
+
): Promise<T> {
|
|
34
|
+
const prepared = await prepare();
|
|
35
|
+
await recover();
|
|
36
|
+
return prepared;
|
|
37
|
+
}
|
|
@@ -6,12 +6,14 @@ import { registerSystemRoutes } from "./routes.js";
|
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
8
8
|
async function auditFixture() {
|
|
9
|
+
const snapshot = createFoundationSnapshot();
|
|
9
10
|
const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token_1" });
|
|
10
11
|
const app = new FakeRouteApp();
|
|
11
12
|
registerSystemRoutes(
|
|
12
13
|
app,
|
|
13
|
-
createSystemRepositories(
|
|
14
|
+
createSystemRepositories(snapshot),
|
|
14
15
|
runtime,
|
|
16
|
+
snapshot.features,
|
|
15
17
|
);
|
|
16
18
|
const guardContext = await runtime.createGuardContext(
|
|
17
19
|
new Request("http://localhost/api/system/audit-logs", {
|
|
@@ -54,10 +56,12 @@ describe("audit routes", () => {
|
|
|
54
56
|
},
|
|
55
57
|
);
|
|
56
58
|
const app = new FakeRouteApp();
|
|
59
|
+
const snapshot = createFoundationSnapshot();
|
|
57
60
|
registerSystemRoutes(
|
|
58
61
|
app,
|
|
59
|
-
createSystemRepositories(
|
|
62
|
+
createSystemRepositories(snapshot),
|
|
60
63
|
runtime,
|
|
64
|
+
snapshot.features,
|
|
61
65
|
);
|
|
62
66
|
const guardContext = await runtime.createGuardContext(
|
|
63
67
|
new Request("http://localhost/api/system/audit-logs", {
|
|
@@ -108,10 +112,12 @@ describe("audit routes", () => {
|
|
|
108
112
|
},
|
|
109
113
|
);
|
|
110
114
|
const app = new FakeRouteApp();
|
|
115
|
+
const snapshot = createFoundationSnapshot();
|
|
111
116
|
registerSystemRoutes(
|
|
112
117
|
app,
|
|
113
|
-
createSystemRepositories(
|
|
118
|
+
createSystemRepositories(snapshot),
|
|
114
119
|
runtime,
|
|
120
|
+
snapshot.features,
|
|
115
121
|
);
|
|
116
122
|
const guardContext = await runtime.createGuardContext(
|
|
117
123
|
new Request("http://localhost/api/system/audit-logs", {
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import { createServerRuntime } from "../runtime.js";
|
|
4
3
|
import { FakeRouteApp } from "./route-test-app.js";
|
|
4
|
+
import { createRouteTestRuntime } from "./route-test-runtime.js";
|
|
5
5
|
import { registerSystemRoutes } from "./routes.js";
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
8
8
|
async function fixture() {
|
|
9
9
|
const snapshot = createFoundationSnapshot();
|
|
10
10
|
const repositories = createSystemRepositories(snapshot);
|
|
11
|
-
const runtime =
|
|
11
|
+
const runtime = createRouteTestRuntime(snapshot, {
|
|
12
|
+
WINDY_DEV_ADMIN_TOKEN: "token_1",
|
|
13
|
+
});
|
|
12
14
|
const app = new FakeRouteApp();
|
|
13
15
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
14
16
|
const guardContext = await runtime.createGuardContext(
|
|
@@ -27,7 +29,7 @@ async function fixture() {
|
|
|
27
29
|
describe("部门组织路由", () => {
|
|
28
30
|
test("部门处置缺少权限时经过统一 Guard 拒绝", async () => {
|
|
29
31
|
const snapshot = createFoundationSnapshot();
|
|
30
|
-
const runtime =
|
|
32
|
+
const runtime = createRouteTestRuntime(snapshot);
|
|
31
33
|
const app = new FakeRouteApp();
|
|
32
34
|
registerSystemRoutes(
|
|
33
35
|
app,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import { createServerRuntime } from "../runtime.js";
|
|
4
3
|
import { FakeRouteApp } from "./route-test-app.js";
|
|
4
|
+
import { createRouteTestRuntime } from "./route-test-runtime.js";
|
|
5
5
|
import { registerSystemRoutes } from "./routes.js";
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
@@ -10,7 +10,8 @@ describe("feature routes", () => {
|
|
|
10
10
|
const snapshot = createFoundationSnapshot();
|
|
11
11
|
const repositories = createSystemRepositories(snapshot);
|
|
12
12
|
await repositories.features.syncManifest(snapshot.features);
|
|
13
|
-
const runtime =
|
|
13
|
+
const runtime = createRouteTestRuntime(
|
|
14
|
+
snapshot,
|
|
14
15
|
{ WINDY_DEV_ADMIN_TOKEN: "token_1" },
|
|
15
16
|
{ featureResolver: repositories.features },
|
|
16
17
|
);
|
|
@@ -46,7 +47,7 @@ describe("feature routes", () => {
|
|
|
46
47
|
});
|
|
47
48
|
expect(denied.status).toBe(403);
|
|
48
49
|
await expect(denied.json()).resolves.toMatchObject({
|
|
49
|
-
reason: "
|
|
50
|
+
reason: "dependency-missing",
|
|
50
51
|
});
|
|
51
52
|
expect(app.handlers.has("POST /system/features")).toBe(false);
|
|
52
53
|
expect(app.handlers.has("POST /system/features/import")).toBe(false);
|
|
@@ -15,7 +15,8 @@ describe("系统接口权限目录", () => {
|
|
|
15
15
|
),
|
|
16
16
|
).toMatchObject({
|
|
17
17
|
permissionKey: "system.user.manage",
|
|
18
|
-
featureKey: "system.
|
|
18
|
+
featureKey: "system.user",
|
|
19
|
+
auditAction: "crud.create",
|
|
19
20
|
guardOrder: ["license", "feature", "permission"],
|
|
20
21
|
source: "module-manifest",
|
|
21
22
|
});
|
|
@@ -52,11 +53,12 @@ describe("系统接口权限目录", () => {
|
|
|
52
53
|
),
|
|
53
54
|
).toMatchObject({
|
|
54
55
|
permissionKey: "system.feature.manage",
|
|
55
|
-
|
|
56
|
+
featureKey: "system.feature",
|
|
57
|
+
guardOrder: ["license", "feature", "permission"],
|
|
56
58
|
});
|
|
57
59
|
expect(
|
|
58
60
|
interfaces.find((item) => item.path === "/api/foundation"),
|
|
59
|
-
).toMatchObject({ anonymous: true });
|
|
61
|
+
).toMatchObject({ anonymous: true, auditAction: undefined });
|
|
60
62
|
// @windy-module system.bulk-data begin
|
|
61
63
|
expect(
|
|
62
64
|
interfaces.find(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
2
2
|
import type {
|
|
3
|
+
AuditActionType,
|
|
3
4
|
EntitlementKey,
|
|
4
5
|
HttpMethod,
|
|
5
6
|
LicenseVersionKey,
|
|
@@ -19,6 +20,7 @@ export interface SystemInterfacePermissionDetail {
|
|
|
19
20
|
permissionKey: string;
|
|
20
21
|
anonymous: boolean;
|
|
21
22
|
featureKey?: string;
|
|
23
|
+
auditAction?: AuditActionType;
|
|
22
24
|
allowedLicenseVersions?: readonly LicenseVersionKey[];
|
|
23
25
|
entitlementKey?: EntitlementKey;
|
|
24
26
|
guardOrder: readonly GuardStep[];
|
|
@@ -56,6 +58,7 @@ export function collectSystemInterfacePermissions(
|
|
|
56
58
|
permissionKey: binding.permissionKey,
|
|
57
59
|
anonymous: binding.anonymous === true,
|
|
58
60
|
featureKey: feature?.key,
|
|
61
|
+
auditAction: binding.auditAction,
|
|
59
62
|
allowedLicenseVersions: feature?.allowedLicenseVersions,
|
|
60
63
|
entitlementKey: feature?.requiredEntitlement,
|
|
61
64
|
guardOrder: resolveGuardOrder(feature),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import { createServerRuntime } from "../runtime.js";
|
|
4
3
|
import { FakeRouteApp } from "./route-test-app.js";
|
|
4
|
+
import { createRouteTestRuntime } from "./route-test-runtime.js";
|
|
5
5
|
import { registerSystemRoutes } from "./routes.js";
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
@@ -9,7 +9,9 @@ describe("菜单结构批量路由", () => {
|
|
|
9
9
|
test("完整草稿通过 system.menu.manage Guard 后一次保存并写审计", async () => {
|
|
10
10
|
const snapshot = createFoundationSnapshot();
|
|
11
11
|
const repositories = createSystemRepositories(snapshot);
|
|
12
|
-
const runtime =
|
|
12
|
+
const runtime = createRouteTestRuntime(snapshot, {
|
|
13
|
+
WINDY_DEV_ADMIN_TOKEN: "token_1",
|
|
14
|
+
});
|
|
13
15
|
const app = new FakeRouteApp();
|
|
14
16
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
15
17
|
const guardContext = await runtime.createGuardContext(
|
|
@@ -49,7 +51,7 @@ describe("菜单结构批量路由", () => {
|
|
|
49
51
|
test("缺少管理权限时拒绝保存菜单草稿", async () => {
|
|
50
52
|
const snapshot = createFoundationSnapshot();
|
|
51
53
|
const repositories = createSystemRepositories(snapshot);
|
|
52
|
-
const runtime =
|
|
54
|
+
const runtime = createRouteTestRuntime(snapshot);
|
|
53
55
|
const app = new FakeRouteApp();
|
|
54
56
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
55
57
|
const guardContext = await runtime.createGuardContext(
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FeatureFlagDefinition } from "@southwind-ai/shared";
|
|
2
|
+
import { createServerRuntime, type ServerRuntimeOptions } from "../runtime.js";
|
|
3
|
+
|
|
4
|
+
interface TestFeatureCatalog {
|
|
5
|
+
readonly features: readonly FeatureFlagDefinition[];
|
|
6
|
+
readonly disabledModuleNames?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type TestRuntimeOptions = Omit<
|
|
10
|
+
ServerRuntimeOptions,
|
|
11
|
+
"featureCatalog" | "disabledModuleNames"
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
export function createRouteTestRuntime(
|
|
15
|
+
catalog: TestFeatureCatalog,
|
|
16
|
+
env: Record<string, string | undefined> = {},
|
|
17
|
+
options: TestRuntimeOptions = {},
|
|
18
|
+
) {
|
|
19
|
+
return createServerRuntime(env, {
|
|
20
|
+
...options,
|
|
21
|
+
featureCatalog: catalog.features,
|
|
22
|
+
disabledModuleNames: new Set(catalog.disabledModuleNames),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import { createFoundationSnapshot } from "../foundation.js";
|
|
3
|
-
import { createServerRuntime } from "../runtime.js";
|
|
4
3
|
import { FakeRouteApp } from "./route-test-app.js";
|
|
4
|
+
import { createRouteTestRuntime } from "./route-test-runtime.js";
|
|
5
5
|
import { registerSystemRoutes } from "./routes.js";
|
|
6
6
|
import { createSystemRepositories } from "./seed.js";
|
|
7
7
|
|
|
@@ -71,12 +71,16 @@ describe("system route query validation", () => {
|
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
async function createFixture(path: string) {
|
|
74
|
-
const
|
|
74
|
+
const snapshot = createFoundationSnapshot();
|
|
75
|
+
const runtime = createRouteTestRuntime(snapshot, {
|
|
76
|
+
WINDY_DEV_ADMIN_TOKEN: "token_1",
|
|
77
|
+
});
|
|
75
78
|
const app = new FakeRouteApp();
|
|
76
79
|
registerSystemRoutes(
|
|
77
80
|
app,
|
|
78
|
-
createSystemRepositories(
|
|
81
|
+
createSystemRepositories(snapshot),
|
|
79
82
|
runtime,
|
|
83
|
+
snapshot.features,
|
|
80
84
|
);
|
|
81
85
|
const guardContext = await runtime.createGuardContext(
|
|
82
86
|
new Request(`http://localhost${path}`, {
|
|
@@ -12,19 +12,21 @@ import { registerSystemRoutes } from "./routes.js";
|
|
|
12
12
|
import { createSystemRepositories } from "./seed.js";
|
|
13
13
|
|
|
14
14
|
async function routeFixture(
|
|
15
|
-
features
|
|
15
|
+
features?: FeatureFlagDefinition[],
|
|
16
16
|
auditWriter?: AuditEventWriter,
|
|
17
17
|
) {
|
|
18
|
+
const snapshot = createFoundationSnapshot();
|
|
19
|
+
const activeFeatures = features ?? snapshot.features;
|
|
18
20
|
const runtime = createServerRuntime(
|
|
19
21
|
{ WINDY_DEV_ADMIN_TOKEN: "token_1" },
|
|
20
|
-
{ auditWriter },
|
|
22
|
+
{ auditWriter, featureCatalog: activeFeatures },
|
|
21
23
|
);
|
|
22
24
|
const app = new FakeRouteApp();
|
|
23
25
|
registerSystemRoutes(
|
|
24
26
|
app,
|
|
25
|
-
createSystemRepositories(
|
|
27
|
+
createSystemRepositories(snapshot),
|
|
26
28
|
runtime,
|
|
27
|
-
|
|
29
|
+
activeFeatures,
|
|
28
30
|
);
|
|
29
31
|
const guardContext = await runtime.createGuardContext(
|
|
30
32
|
new Request("http://localhost/api/system/users", {
|
|
@@ -78,12 +80,17 @@ describe("system routes", () => {
|
|
|
78
80
|
});
|
|
79
81
|
|
|
80
82
|
test("缺少权限时返回 403 Response 并记录 security.denied", async () => {
|
|
81
|
-
const
|
|
83
|
+
const snapshot = createFoundationSnapshot();
|
|
84
|
+
const runtime = createServerRuntime(
|
|
85
|
+
{},
|
|
86
|
+
{ featureCatalog: snapshot.features },
|
|
87
|
+
);
|
|
82
88
|
const app = new FakeRouteApp();
|
|
83
89
|
registerSystemRoutes(
|
|
84
90
|
app,
|
|
85
|
-
createSystemRepositories(
|
|
91
|
+
createSystemRepositories(snapshot),
|
|
86
92
|
runtime,
|
|
93
|
+
snapshot.features,
|
|
87
94
|
);
|
|
88
95
|
const guardContext = await runtime.createGuardContext(
|
|
89
96
|
new Request("http://localhost/api/system/users"),
|
|
@@ -127,7 +134,7 @@ describe("system routes", () => {
|
|
|
127
134
|
|
|
128
135
|
expect(hiddenResponse.status).toBe(403);
|
|
129
136
|
await expect(hiddenResponse.json()).resolves.toMatchObject({
|
|
130
|
-
reason: "
|
|
137
|
+
reason: "dependency-missing",
|
|
131
138
|
});
|
|
132
139
|
|
|
133
140
|
// @windy-module system.license begin
|
|
@@ -209,7 +216,7 @@ describe("system routes", () => {
|
|
|
209
216
|
|
|
210
217
|
test("用户与角色变更使用关键策略,普通资源变更保持降级可用", async () => {
|
|
211
218
|
const policies: string[] = [];
|
|
212
|
-
const { app, guardContext } = await routeFixture(
|
|
219
|
+
const { app, guardContext } = await routeFixture(undefined, {
|
|
213
220
|
write: async (_event, policy) => void policies.push(policy || "ordinary"),
|
|
214
221
|
});
|
|
215
222
|
|
|
@@ -234,7 +241,7 @@ describe("system routes", () => {
|
|
|
234
241
|
});
|
|
235
242
|
|
|
236
243
|
test("关键资源审计失败会拒绝请求,普通资源仍完成并报告降级", async () => {
|
|
237
|
-
const { app, runtime, guardContext } = await routeFixture(
|
|
244
|
+
const { app, runtime, guardContext } = await routeFixture(undefined, {
|
|
238
245
|
write: async () => {
|
|
239
246
|
throw new Error("audit persistence unavailable");
|
|
240
247
|
},
|
|
@@ -281,7 +288,10 @@ describe("system routes", () => {
|
|
|
281
288
|
await repositories.features.syncManifest(snapshot.features);
|
|
282
289
|
const runtime = createServerRuntime(
|
|
283
290
|
{ WINDY_DEV_ADMIN_TOKEN: "token_1" },
|
|
284
|
-
{
|
|
291
|
+
{
|
|
292
|
+
featureResolver: repositories.features,
|
|
293
|
+
featureCatalog: snapshot.features,
|
|
294
|
+
},
|
|
285
295
|
);
|
|
286
296
|
const app = new FakeRouteApp();
|
|
287
297
|
registerSystemRoutes(app, repositories, runtime, snapshot.features);
|
|
@@ -315,7 +325,7 @@ describe("system routes", () => {
|
|
|
315
325
|
});
|
|
316
326
|
expect(denied.status).toBe(403);
|
|
317
327
|
await expect(denied.json()).resolves.toMatchObject({
|
|
318
|
-
reason: "
|
|
328
|
+
reason: "dependency-missing",
|
|
319
329
|
});
|
|
320
330
|
expect(app.handlers.has("POST /system/features")).toBe(false);
|
|
321
331
|
expect(app.handlers.has("POST /system/features/import")).toBe(false);
|
|
@@ -27,7 +27,7 @@ export function registerSystemRoutes(
|
|
|
27
27
|
app: RouteApp,
|
|
28
28
|
repositories: SystemRepositories,
|
|
29
29
|
runtime: ReturnType<typeof createServerRuntime>,
|
|
30
|
-
features: FeatureFlagDefinition[]
|
|
30
|
+
features: FeatureFlagDefinition[],
|
|
31
31
|
options: {
|
|
32
32
|
authRepository?: Pick<AuthRepository, "revokeUserSessions">;
|
|
33
33
|
} = {},
|
|
@@ -98,7 +98,16 @@ export function registerSystemRoutes(
|
|
|
98
98
|
),
|
|
99
99
|
runtime,
|
|
100
100
|
);
|
|
101
|
-
|
|
101
|
+
const featureManagement = findSystemFeature(features, "feature");
|
|
102
|
+
if (!featureManagement) {
|
|
103
|
+
throw new Error("功能开关管理缺少 system.feature Feature");
|
|
104
|
+
}
|
|
105
|
+
registerFeatureFlagRoutes(
|
|
106
|
+
app,
|
|
107
|
+
repositories.features,
|
|
108
|
+
runtime,
|
|
109
|
+
featureManagement,
|
|
110
|
+
);
|
|
102
111
|
// @windy-module system.license begin
|
|
103
112
|
registerResourceRoutes(
|
|
104
113
|
app,
|
|
@@ -43,7 +43,9 @@ describe("平台模块 composition root", () => {
|
|
|
43
43
|
"advanced",
|
|
44
44
|
]);
|
|
45
45
|
const workOrder = modules.find(({ name }) => name === "work-order");
|
|
46
|
-
expect(workOrder?.adminRoutes).toEqual([
|
|
46
|
+
expect(workOrder?.adminRoutes.map(({ key }) => key)).toEqual([
|
|
47
|
+
"work-order.ticket.list",
|
|
48
|
+
]);
|
|
47
49
|
expect(workOrder?.apiPermissions).toHaveLength(5);
|
|
48
50
|
expect(workOrder?.tasks.map(({ key }) => key)).toContain(
|
|
49
51
|
"work-order.ticket.snapshot",
|
|
@@ -88,9 +90,9 @@ describe("平台模块 composition root", () => {
|
|
|
88
90
|
"@southwind-ai/web",
|
|
89
91
|
);
|
|
90
92
|
expect(businessPackage.dependencies).not.toHaveProperty("vue");
|
|
91
|
-
expect(webPackage.dependencies).
|
|
92
|
-
"@southwind-ai/example-work-order",
|
|
93
|
-
);
|
|
93
|
+
expect(webPackage.dependencies).toMatchObject({
|
|
94
|
+
"@southwind-ai/example-work-order": "workspace:*",
|
|
95
|
+
});
|
|
94
96
|
});
|
|
95
97
|
|
|
96
98
|
test("示例模块经 Host 声明式装配提供搜索 Provider", async () => {
|
|
@@ -22,6 +22,9 @@ describe("示例工单 routes", () => {
|
|
|
22
22
|
items: WorkOrder[];
|
|
23
23
|
};
|
|
24
24
|
expect(listed.items.map(({ id }) => id)).toEqual(["ticket_self"]);
|
|
25
|
+
expect(fixture.runtime.auditSink.list()).toContainEqual(
|
|
26
|
+
expect.objectContaining({ action: "data.read" }),
|
|
27
|
+
);
|
|
25
28
|
|
|
26
29
|
const denied = (await fixture.invoke("GET", "/work-orders/:id", {
|
|
27
30
|
id: "ticket_other",
|
|
@@ -76,6 +79,11 @@ describe("示例工单 routes", () => {
|
|
|
76
79
|
target: expect.objectContaining({ id: created.id, type: "work-order" }),
|
|
77
80
|
}),
|
|
78
81
|
);
|
|
82
|
+
expect(
|
|
83
|
+
fixture.runtime.auditSink
|
|
84
|
+
.list()
|
|
85
|
+
.filter(({ action }) => action === "crud.create"),
|
|
86
|
+
).toHaveLength(1);
|
|
79
87
|
});
|
|
80
88
|
});
|
|
81
89
|
|