@a5c-ai/krate 5.0.1-staging.00fa5317c
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 +183 -0
- package/bin/krate-demo.mjs +23 -0
- package/bin/krate-server.mjs +14 -0
- package/dist/krate-controller-ui.json +3205 -0
- package/dist/krate-lifecycle.json +201 -0
- package/dist/krate-runtime-snapshot.json +3125 -0
- package/dist/krate-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-krate-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/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/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/krate-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 +1275 -0
- package/docs/product-requirements.md +62 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/system-requirements.md +90 -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/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 +63 -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 +93 -0
- package/scripts/validate-ui.mjs +278 -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 +209 -0
- package/src/agent-gateway-config-controller.js +147 -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 +280 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-provider-config-controller.js +150 -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 +347 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +381 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +541 -0
- package/src/argocd-gitops.js +43 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +307 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +72 -0
- package/src/controller-ui.js +617 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +61 -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 +161 -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/hooks-events.js +63 -0
- package/src/http-server.js +377 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +57 -0
- package/src/kubernetes-controller-async.js +511 -0
- package/src/kubernetes-controller.js +878 -0
- package/src/kubernetes-resource-gateway.js +48 -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 +221 -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/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 +315 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-memory-controller.test.js +308 -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 +204 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +228 -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 +221 -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 +211 -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/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/deployment.test.js +407 -0
- package/tests/e2e/lifecycle.test.js +117 -0
- package/tests/event-bus-integration.test.js +190 -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 +340 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +215 -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/integration/full-flow.test.js +266 -0
- package/tests/krate.test.js +756 -0
- package/tests/memory-search-wiring.test.js +270 -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 +247 -0
- package/tests/sse-events.test.js +107 -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,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B3 + D1: External persistence + GitHub provider registration tests
|
|
3
|
+
*
|
|
4
|
+
* B3: sync/write/conflict controllers wire to CRDs via persistFn
|
|
5
|
+
* D1: createDefaultProviderRegistry, createExternalBackendProvider
|
|
6
|
+
*/
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import test from 'node:test';
|
|
9
|
+
|
|
10
|
+
import { createSyncController } from '../src/external/sync-controller.js';
|
|
11
|
+
import { createWriteController } from '../src/external/write-controller.js';
|
|
12
|
+
import { createConflictController } from '../src/external/conflict-controller.js';
|
|
13
|
+
import {
|
|
14
|
+
createDefaultProviderRegistry,
|
|
15
|
+
createExternalBackendProvider
|
|
16
|
+
} from '../src/external/provider-resource-factory.js';
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Helpers
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
function makeApplyFn() {
|
|
23
|
+
const calls = [];
|
|
24
|
+
const fn = async (resource) => {
|
|
25
|
+
calls.push(JSON.parse(JSON.stringify(resource)));
|
|
26
|
+
return { ok: true, resource };
|
|
27
|
+
};
|
|
28
|
+
fn.calls = calls;
|
|
29
|
+
return fn;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// B3 — Sync controller persistence
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
// Test B3-1
|
|
37
|
+
test('syncController updateWatermark with persistFn saves watermark via applyResource', async () => {
|
|
38
|
+
const applyFn = makeApplyFn();
|
|
39
|
+
const controller = createSyncController({ persistFn: applyFn });
|
|
40
|
+
|
|
41
|
+
controller.updateWatermark('binding-1', '2024-01-01T00:00:00Z');
|
|
42
|
+
// Wait a tick for async persist
|
|
43
|
+
await new Promise((r) => setImmediate(r));
|
|
44
|
+
|
|
45
|
+
assert.ok(applyFn.calls.length >= 1, 'persistFn must be called at least once');
|
|
46
|
+
const applied = applyFn.calls[0];
|
|
47
|
+
assert.ok(applied.kind, 'applied resource must have a kind');
|
|
48
|
+
assert.ok(applied.metadata?.name, 'applied resource must have a name');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Test B3-2
|
|
52
|
+
test('syncController upsertResource with persistFn saves resource via applyResource', async () => {
|
|
53
|
+
const applyFn = makeApplyFn();
|
|
54
|
+
const controller = createSyncController({ persistFn: applyFn });
|
|
55
|
+
|
|
56
|
+
controller.upsertResource({
|
|
57
|
+
kind: 'ExternalResource',
|
|
58
|
+
localName: 'my-resource',
|
|
59
|
+
namespace: 'default',
|
|
60
|
+
spec: { title: 'Hello' },
|
|
61
|
+
externalEnvelope: { nativeId: 'ext-123', url: 'https://example.com', etag: 'abc', providerRef: 'github' }
|
|
62
|
+
});
|
|
63
|
+
await new Promise((r) => setImmediate(r));
|
|
64
|
+
|
|
65
|
+
assert.ok(applyFn.calls.length >= 1, 'persistFn must be called for upserted resource');
|
|
66
|
+
const applied = applyFn.calls[0];
|
|
67
|
+
assert.equal(applied.kind, 'ExternalResource', 'applied resource must have correct kind');
|
|
68
|
+
assert.equal(applied.metadata.name, 'my-resource', 'applied resource must have correct name');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Test B3-3
|
|
72
|
+
test('syncController persistFn receives correct resource kind and metadata for watermark', async () => {
|
|
73
|
+
const applyFn = makeApplyFn();
|
|
74
|
+
const controller = createSyncController({ persistFn: applyFn });
|
|
75
|
+
|
|
76
|
+
controller.updateWatermark('binding-ns/binding-name', '2024-06-15T12:00:00Z');
|
|
77
|
+
await new Promise((r) => setImmediate(r));
|
|
78
|
+
|
|
79
|
+
const applied = applyFn.calls[0];
|
|
80
|
+
assert.ok(applied.kind, 'resource must have a kind field');
|
|
81
|
+
assert.ok(applied.metadata?.namespace, 'resource must have a namespace');
|
|
82
|
+
// Watermark resource should encode the binding ref in name or spec
|
|
83
|
+
const str = JSON.stringify(applied);
|
|
84
|
+
assert.ok(str.includes('2024-06-15T12:00:00Z') || str.includes('binding'), 'watermark data must be present in applied resource');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Test B3-4
|
|
88
|
+
test('syncController without persistFn works normally (no crash)', () => {
|
|
89
|
+
// No persistFn — existing behavior is unchanged
|
|
90
|
+
const controller = createSyncController();
|
|
91
|
+
controller.updateWatermark('binding-1', '2024-01-01T00:00:00Z');
|
|
92
|
+
assert.equal(controller.getWatermark('binding-1'), '2024-01-01T00:00:00Z');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// B3 — Write controller persistence
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
// Test B3-5
|
|
100
|
+
test('writeController createWriteIntent with persistFn persists WriteIntent CRD', async () => {
|
|
101
|
+
const applyFn = makeApplyFn();
|
|
102
|
+
const controller = createWriteController({ persistFn: applyFn });
|
|
103
|
+
|
|
104
|
+
const result = controller.createWriteIntent({
|
|
105
|
+
interfaceKey: 'issueTracking',
|
|
106
|
+
operation: 'createIssue',
|
|
107
|
+
payload: { title: 'Test' },
|
|
108
|
+
resourceRef: 'org/repo#1',
|
|
109
|
+
requiresApproval: false
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
assert.ok(result.intent, 'must return intent');
|
|
113
|
+
await new Promise((r) => setImmediate(r));
|
|
114
|
+
|
|
115
|
+
assert.ok(applyFn.calls.length >= 1, 'persistFn must be called after createWriteIntent');
|
|
116
|
+
const applied = applyFn.calls[0];
|
|
117
|
+
assert.equal(applied.kind, 'ExternalWriteIntent', 'applied resource must be ExternalWriteIntent');
|
|
118
|
+
assert.equal(applied.spec?.interfaceKey, 'issueTracking', 'applied resource must have correct interfaceKey');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Test B3-6
|
|
122
|
+
test('writeController with persistFn persists intent state after phase transitions', async () => {
|
|
123
|
+
const applyFn = makeApplyFn();
|
|
124
|
+
const controller = createWriteController({ persistFn: applyFn });
|
|
125
|
+
|
|
126
|
+
const result = controller.createWriteIntent({
|
|
127
|
+
interfaceKey: 'gitForge',
|
|
128
|
+
operation: 'createPR',
|
|
129
|
+
payload: {},
|
|
130
|
+
resourceRef: 'org/repo#pr-1',
|
|
131
|
+
requiresApproval: true
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const intent = result.intent;
|
|
135
|
+
await new Promise((r) => setImmediate(r));
|
|
136
|
+
const callsAfterCreate = applyFn.calls.length;
|
|
137
|
+
|
|
138
|
+
// Approve the intent
|
|
139
|
+
controller.approveWriteIntent({
|
|
140
|
+
intentName: intent.metadata.name,
|
|
141
|
+
approvedBy: 'user-1',
|
|
142
|
+
resources: { ExternalWriteIntent: [intent] }
|
|
143
|
+
});
|
|
144
|
+
await new Promise((r) => setImmediate(r));
|
|
145
|
+
|
|
146
|
+
assert.ok(applyFn.calls.length > callsAfterCreate, 'persistFn must be called again after approval');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// B3 — Conflict controller persistence
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
// Test B3-7
|
|
154
|
+
test('conflictController detectConflict with persistFn persists ExternalSyncConflict CRD', async () => {
|
|
155
|
+
const applyFn = makeApplyFn();
|
|
156
|
+
const controller = createConflictController({ persistFn: applyFn });
|
|
157
|
+
|
|
158
|
+
const result = controller.detectConflict({
|
|
159
|
+
resourceRef: 'org/repo#1',
|
|
160
|
+
fieldPath: 'spec.title',
|
|
161
|
+
localValue: 'Local',
|
|
162
|
+
externalValue: 'External'
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
assert.ok(result.conflict, 'must detect conflict');
|
|
166
|
+
await new Promise((r) => setImmediate(r));
|
|
167
|
+
|
|
168
|
+
assert.ok(applyFn.calls.length >= 1, 'persistFn must be called for detected conflict');
|
|
169
|
+
const applied = applyFn.calls[0];
|
|
170
|
+
assert.equal(applied.kind, 'ExternalSyncConflict', 'applied resource must be ExternalSyncConflict');
|
|
171
|
+
assert.equal(applied.spec?.fieldPath, 'spec.title', 'applied resource must have correct fieldPath');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Test B3-8
|
|
175
|
+
test('conflictController resolveConflict with persistFn persists resolution to CRD', async () => {
|
|
176
|
+
const applyFn = makeApplyFn();
|
|
177
|
+
const controller = createConflictController({ persistFn: applyFn });
|
|
178
|
+
|
|
179
|
+
const { conflict } = controller.detectConflict({
|
|
180
|
+
resourceRef: 'org/repo#2',
|
|
181
|
+
fieldPath: 'spec.body',
|
|
182
|
+
localValue: 'Old',
|
|
183
|
+
externalValue: 'New'
|
|
184
|
+
});
|
|
185
|
+
await new Promise((r) => setImmediate(r));
|
|
186
|
+
const callsAfterDetect = applyFn.calls.length;
|
|
187
|
+
|
|
188
|
+
controller.resolveConflict({
|
|
189
|
+
conflictName: conflict.metadata.name,
|
|
190
|
+
strategy: 'prefer-external',
|
|
191
|
+
resources: { ExternalSyncConflict: [conflict] }
|
|
192
|
+
});
|
|
193
|
+
await new Promise((r) => setImmediate(r));
|
|
194
|
+
|
|
195
|
+
assert.ok(applyFn.calls.length > callsAfterDetect, 'persistFn must be called after resolution');
|
|
196
|
+
const applied = applyFn.calls[applyFn.calls.length - 1];
|
|
197
|
+
assert.equal(applied.kind, 'ExternalSyncConflict', 'applied resource must still be ExternalSyncConflict');
|
|
198
|
+
assert.equal(applied.status?.phase, 'Resolved', 'applied resource must have Resolved phase');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Test B3-9
|
|
202
|
+
test('conflictController without persistFn works normally (no crash)', () => {
|
|
203
|
+
const controller = createConflictController();
|
|
204
|
+
const result = controller.detectConflict({
|
|
205
|
+
resourceRef: 'org/repo#3',
|
|
206
|
+
fieldPath: 'spec.title',
|
|
207
|
+
localValue: 'A',
|
|
208
|
+
externalValue: 'B'
|
|
209
|
+
});
|
|
210
|
+
assert.ok(result.conflict, 'must detect conflict without persistFn');
|
|
211
|
+
assert.equal(result.conflict.status.phase, 'Open', 'conflict must be Open');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// D1 — GitHub provider registration
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
// Test D1-1
|
|
219
|
+
test('createDefaultProviderRegistry returns a registry that includes github provider', () => {
|
|
220
|
+
const registry = createDefaultProviderRegistry();
|
|
221
|
+
assert.ok(registry, 'registry must be returned');
|
|
222
|
+
assert.equal(typeof registry.get, 'function', 'registry must have get method');
|
|
223
|
+
assert.equal(typeof registry.list, 'function', 'registry must have list method');
|
|
224
|
+
|
|
225
|
+
const types = registry.list();
|
|
226
|
+
assert.ok(types.includes('github'), `registry must include github; got: ${types.join(', ')}`);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Test D1-2
|
|
230
|
+
test('github provider from createDefaultProviderRegistry has correct descriptor shape', () => {
|
|
231
|
+
const registry = createDefaultProviderRegistry();
|
|
232
|
+
const adapter = registry.get('github');
|
|
233
|
+
|
|
234
|
+
assert.ok(adapter, 'github adapter must exist in registry');
|
|
235
|
+
assert.equal(typeof adapter.descriptor, 'function', 'adapter must have descriptor function');
|
|
236
|
+
|
|
237
|
+
const desc = adapter.descriptor();
|
|
238
|
+
assert.equal(desc.providerType, 'github', 'providerType must be github');
|
|
239
|
+
assert.ok(desc.displayName, 'displayName must be non-empty');
|
|
240
|
+
assert.ok(Array.isArray(desc.interfaces), 'interfaces must be an array');
|
|
241
|
+
assert.ok(desc.interfaces.length > 0, 'interfaces must have at least one entry');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Test D1-3
|
|
245
|
+
test('github provider descriptor interfaces include gitForge and issueTracking', () => {
|
|
246
|
+
const registry = createDefaultProviderRegistry();
|
|
247
|
+
const adapter = registry.get('github');
|
|
248
|
+
const desc = adapter.descriptor();
|
|
249
|
+
|
|
250
|
+
assert.ok(desc.interfaces.includes('gitForge'), 'interfaces must include gitForge');
|
|
251
|
+
assert.ok(desc.interfaces.includes('issueTracking'), 'interfaces must include issueTracking');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Test D1-4
|
|
255
|
+
test('createExternalBackendProvider creates valid CRD resource for github', () => {
|
|
256
|
+
const resource = createExternalBackendProvider({
|
|
257
|
+
name: 'my-github',
|
|
258
|
+
namespace: 'default',
|
|
259
|
+
providerType: 'github',
|
|
260
|
+
displayName: 'My GitHub',
|
|
261
|
+
config: { appId: 'app-123', installationId: 'install-456' }
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
assert.ok(resource, 'must return a resource');
|
|
265
|
+
assert.ok(resource.apiVersion, 'resource must have apiVersion');
|
|
266
|
+
assert.equal(resource.kind, 'ExternalBackendProvider', 'kind must be ExternalBackendProvider');
|
|
267
|
+
assert.equal(resource.metadata?.name, 'my-github', 'resource must have correct name');
|
|
268
|
+
assert.equal(resource.metadata?.namespace, 'default', 'resource must have correct namespace');
|
|
269
|
+
assert.equal(resource.spec?.providerType, 'github', 'spec must have providerType');
|
|
270
|
+
assert.equal(resource.spec?.displayName, 'My GitHub', 'spec must have displayName');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Test D1-5
|
|
274
|
+
test('createExternalBackendProvider resource has valid spec with config', () => {
|
|
275
|
+
const resource = createExternalBackendProvider({
|
|
276
|
+
name: 'github-prod',
|
|
277
|
+
namespace: 'production',
|
|
278
|
+
providerType: 'github',
|
|
279
|
+
displayName: 'GitHub Production',
|
|
280
|
+
config: { appId: 'prod-app', installationId: 'prod-install' }
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
assert.ok(resource.spec?.config, 'spec must have config');
|
|
284
|
+
assert.equal(resource.spec.config.appId, 'prod-app', 'config must have appId');
|
|
285
|
+
assert.equal(resource.spec.config.installationId, 'prod-install', 'config must have installationId');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Test D1-6
|
|
289
|
+
test('createDefaultProviderRegistry getProviderAdapter returns adapter with health method', () => {
|
|
290
|
+
const registry = createDefaultProviderRegistry();
|
|
291
|
+
const adapter = registry.get('github');
|
|
292
|
+
|
|
293
|
+
assert.ok(adapter, 'github adapter must be retrievable');
|
|
294
|
+
assert.equal(typeof adapter.health, 'function', 'adapter must have health method');
|
|
295
|
+
const health = adapter.health();
|
|
296
|
+
assert.ok(health, 'health must return a value');
|
|
297
|
+
assert.ok('status' in health, 'health must have status');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Test D1-7
|
|
301
|
+
test('createExternalBackendProvider defaults namespace to default when not provided', () => {
|
|
302
|
+
const resource = createExternalBackendProvider({
|
|
303
|
+
name: 'github-default-ns',
|
|
304
|
+
providerType: 'github',
|
|
305
|
+
displayName: 'GitHub'
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
assert.equal(resource.metadata.namespace, 'default', 'namespace must default to default');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Test D1-8
|
|
312
|
+
test('createExternalBackendProvider includes status with phase Pending', () => {
|
|
313
|
+
const resource = createExternalBackendProvider({
|
|
314
|
+
name: 'github-status-test',
|
|
315
|
+
providerType: 'github',
|
|
316
|
+
displayName: 'GitHub'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
assert.ok(resource.status, 'resource must have status');
|
|
320
|
+
assert.ok(resource.status.phase, 'status must have phase');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Test D1-9 — integration: backend provider wizard flow
|
|
324
|
+
test('createExternalBackendProvider followed by registry lookup works end-to-end', () => {
|
|
325
|
+
const registry = createDefaultProviderRegistry();
|
|
326
|
+
|
|
327
|
+
// Create a provider resource as the wizard would
|
|
328
|
+
const resource = createExternalBackendProvider({
|
|
329
|
+
name: 'wizard-github',
|
|
330
|
+
namespace: 'krate-org-acme',
|
|
331
|
+
providerType: 'github',
|
|
332
|
+
displayName: 'Acme GitHub',
|
|
333
|
+
config: { appId: 'acme-app', installationId: 'acme-install' }
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Verify adapter exists in registry for this provider type
|
|
337
|
+
const adapter = registry.get(resource.spec.providerType);
|
|
338
|
+
assert.ok(adapter, 'registry must have an adapter for the provider type in the CRD');
|
|
339
|
+
assert.equal(typeof adapter.descriptor, 'function', 'adapter must be usable');
|
|
340
|
+
});
|