@a5c-ai/kradle 5.0.1-staging.3abdf9534c25
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/Dockerfile +31 -0
- package/README.md +187 -0
- package/bin/kradle-demo.mjs +23 -0
- package/bin/kradle-server.mjs +14 -0
- package/dist/kradle-controller-ui.json +3482 -0
- package/dist/kradle-lifecycle.json +201 -0
- package/dist/kradle-runtime-snapshot.json +3125 -0
- package/dist/kradle-summary.json +724 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-kradle-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/architecture-v2.md +2759 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/crd-behaviors-and-relationships.md +3926 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/integration-and-design-decisions.md +1530 -0
- package/docs/kradle-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/openapi.yaml +1291 -0
- package/docs/product-requirements.md +62 -0
- package/docs/requirements-v2.md +235 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/sdk-api-reference.md +1108 -0
- package/docs/system-requirements.md +90 -0
- package/docs/system-spec-v2.md +1230 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/docs/web-console-spec.md +533 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +66 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +95 -0
- package/scripts/validate-ui.mjs +305 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +549 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-identity-migration.js +115 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +589 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-persona-controller.js +135 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-prompt-composition.js +55 -0
- package/src/agent-provider-config-controller.js +151 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +421 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +387 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +621 -0
- package/src/argocd-gitops.js +43 -0
- package/src/artifact-registry-controller.js +542 -0
- package/src/assistant-runtime.js +284 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +310 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +112 -0
- package/src/controller-ui.js +620 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +397 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +221 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +131 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/health-probes.js +134 -0
- package/src/hooks-events.js +63 -0
- package/src/hooks-lifecycle.js +117 -0
- package/src/http-server.js +409 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +71 -0
- package/src/jitsi-agent-bridge.js +141 -0
- package/src/jitsi-meeting-controller.js +291 -0
- package/src/jitsi-sync-controller.js +198 -0
- package/src/kradle-inference-service-controller.js +246 -0
- package/src/kubernetes-controller-async.js +531 -0
- package/src/kubernetes-controller.js +904 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/model-route-controller.js +364 -0
- package/src/notification-controller.js +178 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +282 -0
- package/src/runner-controller.js +272 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/virtual-model-controller.js +538 -0
- package/src/virtual-model-hook-bridge.js +200 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +679 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-identity-migration.test.js +87 -0
- package/tests/agent-memory-controller.test.js +461 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-mux-client.test.js +389 -0
- package/tests/agent-mux-integration.test.js +971 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-persona-controller.test.js +127 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-prompt-composition.test.js +76 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +303 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +283 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +271 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/artifact-registry.test.js +511 -0
- package/tests/assistant-runtime.test.js +506 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/controller-client.test.js +133 -0
- package/tests/deployment.test.js +527 -0
- package/tests/e2e/lifecycle.test.js +120 -0
- package/tests/event-bus-integration.test.js +355 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +415 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +223 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/health-probes.test.js +90 -0
- package/tests/hooks-lifecycle.test.js +364 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/jitsi-agent-bridge.test.js +119 -0
- package/tests/jitsi-helm-integration.test.js +77 -0
- package/tests/jitsi-meeting-controller.test.js +170 -0
- package/tests/jitsi-resource-model.test.js +73 -0
- package/tests/jitsi-sync-controller.test.js +112 -0
- package/tests/kradle-inference-service.test.js +689 -0
- package/tests/kradle.test.js +779 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/model-route-controller.test.js +733 -0
- package/tests/notification-controller.test.js +196 -0
- package/tests/notification-integration.test.js +179 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/runner-controller.test.js +327 -0
- package/tests/runner-integration.test.js +231 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +315 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/virtual-model-controller.test.js +877 -0
- package/tests/virtual-model-hook-bridge.test.js +384 -0
- package/tests/webhook-trigger.test.js +198 -0
- package/tests/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { createNotificationController } from '../src/notification-controller.js';
|
|
4
|
+
|
|
5
|
+
// Helper to create a fresh controller for each test
|
|
6
|
+
function makeController() {
|
|
7
|
+
return createNotificationController();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
test('createNotification with AgentDispatchRun completed → type run-complete, severity info', () => {
|
|
11
|
+
const ctrl = makeController();
|
|
12
|
+
const notif = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'my-run', org: 'test-org' });
|
|
13
|
+
assert.equal(notif.type, 'run-complete');
|
|
14
|
+
assert.equal(notif.severity, 'info');
|
|
15
|
+
assert.match(notif.title, /completed/i);
|
|
16
|
+
assert.equal(notif.org, 'test-org');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('createNotification with AgentDispatchRun failed → type run-complete, severity error', () => {
|
|
20
|
+
const ctrl = makeController();
|
|
21
|
+
const notif = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'failed', name: 'my-run', org: 'test-org' });
|
|
22
|
+
assert.equal(notif.type, 'run-complete');
|
|
23
|
+
assert.equal(notif.severity, 'error');
|
|
24
|
+
assert.match(notif.title, /failed/i);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('createNotification with AgentApproval pending → type approval-needed, severity warning', () => {
|
|
28
|
+
const ctrl = makeController();
|
|
29
|
+
const notif = ctrl.createNotification({ type: 'AgentApproval', status: 'pending', action: 'tool-use', org: 'test-org' });
|
|
30
|
+
assert.equal(notif.type, 'approval-needed');
|
|
31
|
+
assert.equal(notif.severity, 'warning');
|
|
32
|
+
assert.match(notif.title, /approval/i);
|
|
33
|
+
assert.match(notif.title, /tool-use/i);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('createNotification with ExternalSyncConflict → type conflict-detected, severity warning', () => {
|
|
37
|
+
const ctrl = makeController();
|
|
38
|
+
const notif = ctrl.createNotification({ type: 'ExternalSyncConflict', resourceRef: 'my-repo', org: 'test-org' });
|
|
39
|
+
assert.equal(notif.type, 'conflict-detected');
|
|
40
|
+
assert.equal(notif.severity, 'warning');
|
|
41
|
+
assert.match(notif.title, /conflict/i);
|
|
42
|
+
assert.match(notif.title, /my-repo/i);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('createNotification with KradleWorkspace claimed → type workspace-ready, severity info', () => {
|
|
46
|
+
const ctrl = makeController();
|
|
47
|
+
const notif = ctrl.createNotification({ type: 'KradleWorkspace', claimed: true, name: 'ws-1', claimedBy: 'run-abc', org: 'test-org' });
|
|
48
|
+
assert.equal(notif.type, 'workspace-ready');
|
|
49
|
+
assert.equal(notif.severity, 'info');
|
|
50
|
+
assert.match(notif.title, /workspace/i);
|
|
51
|
+
assert.match(notif.title, /ws-1/i);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('createNotification returns notification with id, createdAt, read: false', () => {
|
|
55
|
+
const ctrl = makeController();
|
|
56
|
+
const notif = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-x', org: 'test-org' });
|
|
57
|
+
assert.equal(typeof notif.id, 'string');
|
|
58
|
+
assert.ok(notif.id.length > 0);
|
|
59
|
+
assert.equal(typeof notif.createdAt, 'string');
|
|
60
|
+
assert.ok(notif.createdAt.length > 0);
|
|
61
|
+
assert.equal(notif.read, false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('createNotification default event → type system, severity info', () => {
|
|
65
|
+
const ctrl = makeController();
|
|
66
|
+
const notif = ctrl.createNotification({ type: 'UnknownEvent', org: 'test-org' });
|
|
67
|
+
assert.equal(notif.type, 'system');
|
|
68
|
+
assert.equal(notif.severity, 'info');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('listNotifications returns all notifications for org', () => {
|
|
72
|
+
const ctrl = makeController();
|
|
73
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
74
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-a' });
|
|
75
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-3', org: 'org-b' }); // different org
|
|
76
|
+
|
|
77
|
+
const results = ctrl.listNotifications('org-a');
|
|
78
|
+
assert.equal(results.length, 2);
|
|
79
|
+
assert.ok(results.every((n) => n.org === 'org-a'));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('listNotifications with { unreadOnly: true } filters out read notifications', () => {
|
|
83
|
+
const ctrl = makeController();
|
|
84
|
+
const n1 = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
85
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-a' });
|
|
86
|
+
ctrl.markAsRead(n1.id);
|
|
87
|
+
|
|
88
|
+
const unreadOnly = ctrl.listNotifications('org-a', { unreadOnly: true });
|
|
89
|
+
assert.equal(unreadOnly.length, 1);
|
|
90
|
+
assert.equal(unreadOnly[0].read, false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('listNotifications with { limit: 2 } caps results to 2', () => {
|
|
94
|
+
const ctrl = makeController();
|
|
95
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
96
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-a' });
|
|
97
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-3', org: 'org-a' });
|
|
98
|
+
|
|
99
|
+
const limited = ctrl.listNotifications('org-a', { limit: 2 });
|
|
100
|
+
assert.equal(limited.length, 2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('listNotifications returns notifications sorted by createdAt descending', () => {
|
|
104
|
+
const ctrl = makeController();
|
|
105
|
+
const n1 = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
106
|
+
const n2 = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-a' });
|
|
107
|
+
|
|
108
|
+
// Sort order: they come back by createdAt descending (no guaranteed diff if same ms, just verify all present)
|
|
109
|
+
const results = ctrl.listNotifications('org-a');
|
|
110
|
+
assert.equal(results.length, 2);
|
|
111
|
+
// Both IDs present
|
|
112
|
+
const ids = results.map((n) => n.id);
|
|
113
|
+
assert.ok(ids.includes(n1.id));
|
|
114
|
+
assert.ok(ids.includes(n2.id));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('markAsRead sets the target notification read to true', () => {
|
|
118
|
+
const ctrl = makeController();
|
|
119
|
+
const notif = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
120
|
+
assert.equal(notif.read, false);
|
|
121
|
+
|
|
122
|
+
const result = ctrl.markAsRead(notif.id);
|
|
123
|
+
assert.equal(result, true);
|
|
124
|
+
|
|
125
|
+
const results = ctrl.listNotifications('org-a');
|
|
126
|
+
const found = results.find((n) => n.id === notif.id);
|
|
127
|
+
assert.equal(found.read, true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('markAsRead returns false for unknown id', () => {
|
|
131
|
+
const ctrl = makeController();
|
|
132
|
+
const result = ctrl.markAsRead('nonexistent-id');
|
|
133
|
+
assert.equal(result, false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('markAllAsRead sets all org notifications as read and returns count', () => {
|
|
137
|
+
const ctrl = makeController();
|
|
138
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
139
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-a' });
|
|
140
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-3', org: 'org-a' });
|
|
141
|
+
|
|
142
|
+
const count = ctrl.markAllAsRead('org-a');
|
|
143
|
+
assert.equal(count, 3);
|
|
144
|
+
|
|
145
|
+
const unread = ctrl.listNotifications('org-a', { unreadOnly: true });
|
|
146
|
+
assert.equal(unread.length, 0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('getUnreadCount returns correct count after some are read', () => {
|
|
150
|
+
const ctrl = makeController();
|
|
151
|
+
const n1 = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
152
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-a' });
|
|
153
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-3', org: 'org-a' });
|
|
154
|
+
|
|
155
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 3);
|
|
156
|
+
ctrl.markAsRead(n1.id);
|
|
157
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 2);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('getPreferences returns default prefs for new userId', () => {
|
|
161
|
+
const ctrl = makeController();
|
|
162
|
+
const prefs = ctrl.getPreferences('user-xyz');
|
|
163
|
+
assert.equal(prefs.runs, true);
|
|
164
|
+
assert.equal(prefs.approvals, true);
|
|
165
|
+
assert.equal(prefs.conflicts, true);
|
|
166
|
+
assert.equal(prefs.workspaces, true);
|
|
167
|
+
assert.equal(prefs.sound, false);
|
|
168
|
+
assert.equal(prefs.desktop, false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('updatePreferences merges and persists new prefs', () => {
|
|
172
|
+
const ctrl = makeController();
|
|
173
|
+
const updated = ctrl.updatePreferences('user-abc', { sound: true, runs: false });
|
|
174
|
+
assert.equal(updated.sound, true);
|
|
175
|
+
assert.equal(updated.runs, false);
|
|
176
|
+
assert.equal(updated.approvals, true); // default preserved
|
|
177
|
+
|
|
178
|
+
// Verify persisted by reading again
|
|
179
|
+
const prefs = ctrl.getPreferences('user-abc');
|
|
180
|
+
assert.equal(prefs.sound, true);
|
|
181
|
+
assert.equal(prefs.runs, false);
|
|
182
|
+
assert.equal(prefs.approvals, true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('notifications from different orgs do not mix', () => {
|
|
186
|
+
const ctrl = makeController();
|
|
187
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
188
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-b' });
|
|
189
|
+
|
|
190
|
+
const orgA = ctrl.listNotifications('org-a');
|
|
191
|
+
const orgB = ctrl.listNotifications('org-b');
|
|
192
|
+
assert.equal(orgA.length, 1);
|
|
193
|
+
assert.equal(orgB.length, 1);
|
|
194
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 1);
|
|
195
|
+
assert.equal(ctrl.getUnreadCount('org-b'), 1);
|
|
196
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification controller integration tests
|
|
3
|
+
*
|
|
4
|
+
* Exercises end-to-end notification workflows: creating from dispatch/approval
|
|
5
|
+
* events, org filtering, read-state transitions, and unread counts.
|
|
6
|
+
*/
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import test from 'node:test';
|
|
9
|
+
import { createNotificationController } from '../src/notification-controller.js';
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// createNotification from dispatch run completed event
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
test('createNotification from dispatch run completed event produces run-complete notification', () => {
|
|
16
|
+
const ctrl = createNotificationController();
|
|
17
|
+
const notif = ctrl.createNotification({
|
|
18
|
+
type: 'AgentDispatchRun',
|
|
19
|
+
status: 'completed',
|
|
20
|
+
name: 'deploy-run-001',
|
|
21
|
+
org: 'acme',
|
|
22
|
+
});
|
|
23
|
+
assert.equal(notif.type, 'run-complete');
|
|
24
|
+
assert.equal(notif.severity, 'info');
|
|
25
|
+
assert.equal(notif.org, 'acme');
|
|
26
|
+
assert.match(notif.title, /completed/i);
|
|
27
|
+
assert.equal(notif.read, false);
|
|
28
|
+
assert.ok(notif.id, 'notification must have an id');
|
|
29
|
+
assert.ok(notif.createdAt, 'notification must have a createdAt timestamp');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('createNotification from dispatch run failed event produces run-complete with error severity', () => {
|
|
33
|
+
const ctrl = createNotificationController();
|
|
34
|
+
const notif = ctrl.createNotification({
|
|
35
|
+
type: 'AgentDispatchRun',
|
|
36
|
+
status: 'failed',
|
|
37
|
+
name: 'deploy-run-002',
|
|
38
|
+
org: 'acme',
|
|
39
|
+
});
|
|
40
|
+
assert.equal(notif.type, 'run-complete');
|
|
41
|
+
assert.equal(notif.severity, 'error');
|
|
42
|
+
assert.match(notif.title, /failed/i);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// createNotification from approval pending event
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
test('createNotification from approval pending event produces approval-needed notification', () => {
|
|
50
|
+
const ctrl = createNotificationController();
|
|
51
|
+
const notif = ctrl.createNotification({
|
|
52
|
+
type: 'AgentApproval',
|
|
53
|
+
status: 'pending',
|
|
54
|
+
action: 'shell-exec',
|
|
55
|
+
org: 'acme',
|
|
56
|
+
});
|
|
57
|
+
assert.equal(notif.type, 'approval-needed');
|
|
58
|
+
assert.equal(notif.severity, 'warning');
|
|
59
|
+
assert.match(notif.title, /approval/i);
|
|
60
|
+
assert.match(notif.title, /shell-exec/i);
|
|
61
|
+
assert.equal(notif.org, 'acme');
|
|
62
|
+
assert.equal(notif.read, false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('createNotification from approval event stores notification in the controller registry', () => {
|
|
66
|
+
const ctrl = createNotificationController();
|
|
67
|
+
const notif = ctrl.createNotification({
|
|
68
|
+
type: 'AgentApproval',
|
|
69
|
+
status: 'pending',
|
|
70
|
+
action: 'file-write',
|
|
71
|
+
org: 'beta-org',
|
|
72
|
+
});
|
|
73
|
+
const listed = ctrl.listNotifications('beta-org');
|
|
74
|
+
assert.equal(listed.length, 1);
|
|
75
|
+
assert.equal(listed[0].id, notif.id);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// listNotifications filters by org
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
test('listNotifications filters by org and excludes notifications from other orgs', () => {
|
|
83
|
+
const ctrl = createNotificationController();
|
|
84
|
+
|
|
85
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-a1', org: 'org-alpha' });
|
|
86
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-a2', org: 'org-alpha' });
|
|
87
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-b1', org: 'org-beta' });
|
|
88
|
+
ctrl.createNotification({ type: 'AgentApproval', status: 'pending', action: 'tool-use', org: 'org-beta' });
|
|
89
|
+
|
|
90
|
+
const alpha = ctrl.listNotifications('org-alpha');
|
|
91
|
+
const beta = ctrl.listNotifications('org-beta');
|
|
92
|
+
|
|
93
|
+
assert.equal(alpha.length, 2, 'org-alpha should have 2 notifications');
|
|
94
|
+
assert.equal(beta.length, 2, 'org-beta should have 2 notifications');
|
|
95
|
+
assert.ok(alpha.every((n) => n.org === 'org-alpha'), 'all alpha notifications have correct org');
|
|
96
|
+
assert.ok(beta.every((n) => n.org === 'org-beta'), 'all beta notifications have correct org');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('listNotifications returns empty array for org with no notifications', () => {
|
|
100
|
+
const ctrl = createNotificationController();
|
|
101
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
102
|
+
const result = ctrl.listNotifications('org-with-no-data');
|
|
103
|
+
assert.deepEqual(result, []);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// getUnreadCount after marking as read
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
test('getUnreadCount decrements after marking individual notification as read', () => {
|
|
111
|
+
const ctrl = createNotificationController();
|
|
112
|
+
const n1 = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
113
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-a' });
|
|
114
|
+
ctrl.createNotification({ type: 'AgentApproval', status: 'pending', action: 'tool', org: 'org-a' });
|
|
115
|
+
|
|
116
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 3);
|
|
117
|
+
|
|
118
|
+
ctrl.markAsRead(n1.id);
|
|
119
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 2);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('getUnreadCount reaches zero after markAllAsRead', () => {
|
|
123
|
+
const ctrl = createNotificationController();
|
|
124
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
125
|
+
ctrl.createNotification({ type: 'AgentApproval', status: 'pending', action: 'tool', org: 'org-a' });
|
|
126
|
+
ctrl.createNotification({ type: 'ExternalSyncConflict', resourceRef: 'my-repo', org: 'org-a' });
|
|
127
|
+
|
|
128
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 3);
|
|
129
|
+
ctrl.markAllAsRead('org-a');
|
|
130
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('getUnreadCount is isolated per org', () => {
|
|
134
|
+
const ctrl = createNotificationController();
|
|
135
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-1', org: 'org-a' });
|
|
136
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-2', org: 'org-b' });
|
|
137
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-3', org: 'org-b' });
|
|
138
|
+
|
|
139
|
+
ctrl.markAllAsRead('org-a');
|
|
140
|
+
|
|
141
|
+
assert.equal(ctrl.getUnreadCount('org-a'), 0);
|
|
142
|
+
assert.equal(ctrl.getUnreadCount('org-b'), 2, 'org-b unread count should be unaffected by org-a markAllAsRead');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('listNotifications with unreadOnly reflects mark-as-read state', () => {
|
|
146
|
+
const ctrl = createNotificationController();
|
|
147
|
+
const n1 = ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'r1', org: 'org-a' });
|
|
148
|
+
ctrl.createNotification({ type: 'AgentApproval', status: 'pending', action: 'tool', org: 'org-a' });
|
|
149
|
+
|
|
150
|
+
ctrl.markAsRead(n1.id);
|
|
151
|
+
|
|
152
|
+
const unread = ctrl.listNotifications('org-a', { unreadOnly: true });
|
|
153
|
+
assert.equal(unread.length, 1, 'only one unread notification should remain');
|
|
154
|
+
assert.equal(unread[0].read, false);
|
|
155
|
+
assert.notEqual(unread[0].id, n1.id, 'the read notification should not appear in unread list');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Multiple notification types across one org
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
test('org receives notifications of multiple types in one controller instance', () => {
|
|
163
|
+
const ctrl = createNotificationController();
|
|
164
|
+
const org = 'mixed-org';
|
|
165
|
+
|
|
166
|
+
ctrl.createNotification({ type: 'AgentDispatchRun', status: 'completed', name: 'run-x', org });
|
|
167
|
+
ctrl.createNotification({ type: 'AgentApproval', status: 'pending', action: 'file-delete', org });
|
|
168
|
+
ctrl.createNotification({ type: 'ExternalSyncConflict', resourceRef: 'repo-y', org });
|
|
169
|
+
ctrl.createNotification({ type: 'KradleWorkspace', claimed: true, name: 'ws-z', claimedBy: 'run-x', org });
|
|
170
|
+
|
|
171
|
+
const all = ctrl.listNotifications(org);
|
|
172
|
+
assert.equal(all.length, 4);
|
|
173
|
+
|
|
174
|
+
const types = all.map((n) => n.type);
|
|
175
|
+
assert.ok(types.includes('run-complete'));
|
|
176
|
+
assert.ok(types.includes('approval-needed'));
|
|
177
|
+
assert.ok(types.includes('conflict-detected'));
|
|
178
|
+
assert.ok(types.includes('workspace-ready'));
|
|
179
|
+
});
|