@sensigo/realm-testing 0.1.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 +132 -0
- package/dist/assertions/evidence.d.ts +26 -0
- package/dist/assertions/evidence.d.ts.map +1 -0
- package/dist/assertions/evidence.js +60 -0
- package/dist/assertions/evidence.js.map +1 -0
- package/dist/fixtures/fixture-loader.d.ts +74 -0
- package/dist/fixtures/fixture-loader.d.ts.map +1 -0
- package/dist/fixtures/fixture-loader.js +62 -0
- package/dist/fixtures/fixture-loader.js.map +1 -0
- package/dist/helpers/test-adapter.d.ts +7 -0
- package/dist/helpers/test-adapter.d.ts.map +1 -0
- package/dist/helpers/test-adapter.js +8 -0
- package/dist/helpers/test-adapter.js.map +1 -0
- package/dist/helpers/test-processor.d.ts +7 -0
- package/dist/helpers/test-processor.d.ts.map +1 -0
- package/dist/helpers/test-processor.js +8 -0
- package/dist/helpers/test-processor.js.map +1 -0
- package/dist/helpers/test-step-handler.d.ts +7 -0
- package/dist/helpers/test-step-handler.d.ts.map +1 -0
- package/dist/helpers/test-step-handler.js +14 -0
- package/dist/helpers/test-step-handler.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/mocks/mock-agent.d.ts +21 -0
- package/dist/mocks/mock-agent.d.ts.map +1 -0
- package/dist/mocks/mock-agent.js +85 -0
- package/dist/mocks/mock-agent.js.map +1 -0
- package/dist/mocks/mock-gate.d.ts +9 -0
- package/dist/mocks/mock-gate.d.ts.map +1 -0
- package/dist/mocks/mock-gate.js +21 -0
- package/dist/mocks/mock-gate.js.map +1 -0
- package/dist/mocks/mock-service.d.ts +23 -0
- package/dist/mocks/mock-service.d.ts.map +1 -0
- package/dist/mocks/mock-service.js +41 -0
- package/dist/mocks/mock-service.js.map +1 -0
- package/dist/runner/test-runner.d.ts +29 -0
- package/dist/runner/test-runner.d.ts.map +1 -0
- package/dist/runner/test-runner.js +161 -0
- package/dist/runner/test-runner.js.map +1 -0
- package/dist/servers/github-mock-server.d.ts +20 -0
- package/dist/servers/github-mock-server.d.ts.map +1 -0
- package/dist/servers/github-mock-server.js +107 -0
- package/dist/servers/github-mock-server.js.map +1 -0
- package/dist/store/in-memory-store.d.ts +11 -0
- package/dist/store/in-memory-store.d.ts.map +1 -0
- package/dist/store/in-memory-store.js +110 -0
- package/dist/store/in-memory-store.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RunStore, type WorkflowDefinition, type ResponseEnvelope } from '@sensigo/realm';
|
|
2
|
+
/**
|
|
3
|
+
* Auto-responds to any open human gate on the run.
|
|
4
|
+
* Reads gate_id and step_name from run.pending_gate, looks up the choice in
|
|
5
|
+
* gateResponses[step_name], defaults to 'approve'. Calls submitHumanResponse.
|
|
6
|
+
* @throws Error if run.pending_gate is undefined.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createGateResponder(store: RunStore, definition: WorkflowDefinition, runId: string, gateResponses: Record<string, string>): Promise<ResponseEnvelope>;
|
|
9
|
+
//# sourceMappingURL=mock-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-gate.d.ts","sourceRoot":"","sources":["../../src/mocks/mock-gate.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,kBAAkB,EAC9B,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAW3B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// createGateResponder — auto-responds to open human gates in test scenarios.
|
|
2
|
+
import { submitHumanResponse, } from '@sensigo/realm';
|
|
3
|
+
/**
|
|
4
|
+
* Auto-responds to any open human gate on the run.
|
|
5
|
+
* Reads gate_id and step_name from run.pending_gate, looks up the choice in
|
|
6
|
+
* gateResponses[step_name], defaults to 'approve'. Calls submitHumanResponse.
|
|
7
|
+
* @throws Error if run.pending_gate is undefined.
|
|
8
|
+
*/
|
|
9
|
+
export async function createGateResponder(store, definition, runId, gateResponses) {
|
|
10
|
+
const run = await store.get(runId);
|
|
11
|
+
if (run.pending_gate === undefined) {
|
|
12
|
+
throw new Error('createGateResponder: run has no pending gate');
|
|
13
|
+
}
|
|
14
|
+
const choice = gateResponses[run.pending_gate.step_name] ?? 'approve';
|
|
15
|
+
return submitHumanResponse(store, definition, {
|
|
16
|
+
runId,
|
|
17
|
+
gateId: run.pending_gate.gate_id,
|
|
18
|
+
choice,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=mock-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-gate.js","sourceRoot":"","sources":["../../src/mocks/mock-gate.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,OAAO,EACL,mBAAmB,GAIpB,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAe,EACf,UAA8B,EAC9B,KAAa,EACb,aAAqC;IAErC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;IACtE,OAAO,mBAAmB,CAAC,KAAK,EAAE,UAAU,EAAE;QAC5C,KAAK;QACL,MAAM,EAAE,GAAG,CAAC,YAAY,CAAC,OAAO;QAChC,MAAM;KACP,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ServiceAdapter, type ServiceResponse } from '@sensigo/realm';
|
|
2
|
+
/** A single recorded call to the mock adapter. */
|
|
3
|
+
export interface RecordedCall {
|
|
4
|
+
method: 'fetch' | 'create' | 'update';
|
|
5
|
+
operation: string;
|
|
6
|
+
params: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* MockServiceRecorder implements ServiceAdapter. Returns pre-configured responses
|
|
10
|
+
* and records all calls for post-test assertions.
|
|
11
|
+
*/
|
|
12
|
+
export declare class MockServiceRecorder implements ServiceAdapter {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
private readonly responses;
|
|
15
|
+
/** Ordered list of all calls made to this adapter. */
|
|
16
|
+
readonly calls: RecordedCall[];
|
|
17
|
+
constructor(id: string, responses: Record<string, ServiceResponse>);
|
|
18
|
+
fetch(operation: string, params: Record<string, unknown>, _config: Record<string, unknown>, _signal?: AbortSignal): Promise<ServiceResponse>;
|
|
19
|
+
create(operation: string, params: Record<string, unknown>, _config: Record<string, unknown>, _signal?: AbortSignal): Promise<ServiceResponse>;
|
|
20
|
+
update(operation: string, params: Record<string, unknown>, _config: Record<string, unknown>, _signal?: AbortSignal): Promise<ServiceResponse>;
|
|
21
|
+
private lookupResponse;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=mock-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-service.d.ts","sourceRoot":"","sources":["../../src/mocks/mock-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE1F,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,cAAc;aAKtC,EAAE,EAAE,MAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAL5B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAM;gBAGlB,EAAE,EAAE,MAAM,EACT,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;IAGvD,KAAK,CACT,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,eAAe,CAAC;IAKrB,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,eAAe,CAAC;IAKrB,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,eAAe,CAAC;IAK3B,OAAO,CAAC,cAAc;CAevB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// MockServiceRecorder — records all calls and returns pre-configured responses.
|
|
2
|
+
import { WorkflowError } from '@sensigo/realm';
|
|
3
|
+
/**
|
|
4
|
+
* MockServiceRecorder implements ServiceAdapter. Returns pre-configured responses
|
|
5
|
+
* and records all calls for post-test assertions.
|
|
6
|
+
*/
|
|
7
|
+
export class MockServiceRecorder {
|
|
8
|
+
id;
|
|
9
|
+
responses;
|
|
10
|
+
/** Ordered list of all calls made to this adapter. */
|
|
11
|
+
calls = [];
|
|
12
|
+
constructor(id, responses) {
|
|
13
|
+
this.id = id;
|
|
14
|
+
this.responses = responses;
|
|
15
|
+
}
|
|
16
|
+
async fetch(operation, params, _config, _signal) {
|
|
17
|
+
this.calls.push({ method: 'fetch', operation, params });
|
|
18
|
+
return this.lookupResponse(operation);
|
|
19
|
+
}
|
|
20
|
+
async create(operation, params, _config, _signal) {
|
|
21
|
+
this.calls.push({ method: 'create', operation, params });
|
|
22
|
+
return this.lookupResponse(operation);
|
|
23
|
+
}
|
|
24
|
+
async update(operation, params, _config, _signal) {
|
|
25
|
+
this.calls.push({ method: 'update', operation, params });
|
|
26
|
+
return this.lookupResponse(operation);
|
|
27
|
+
}
|
|
28
|
+
lookupResponse(operation) {
|
|
29
|
+
const response = this.responses[operation];
|
|
30
|
+
if (response === undefined) {
|
|
31
|
+
throw new WorkflowError(`MockServiceRecorder: no response configured for operation '${operation}'`, {
|
|
32
|
+
code: 'ENGINE_ADAPTER_FAILED',
|
|
33
|
+
category: 'ENGINE',
|
|
34
|
+
agentAction: 'report_to_user',
|
|
35
|
+
retryable: false,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return response;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=mock-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-service.js","sourceRoot":"","sources":["../../src/mocks/mock-service.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,OAAO,EAAE,aAAa,EAA6C,MAAM,gBAAgB,CAAC;AAS1F;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IAKZ;IACC;IALnB,sDAAsD;IAC7C,KAAK,GAAmB,EAAE,CAAC;IAEpC,YACkB,EAAU,EACT,SAA0C;QAD3C,OAAE,GAAF,EAAE,CAAQ;QACT,cAAS,GAAT,SAAS,CAAiC;IAC1D,CAAC;IAEJ,KAAK,CAAC,KAAK,CACT,SAAiB,EACjB,MAA+B,EAC/B,OAAgC,EAChC,OAAqB;QAErB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,MAAM,CACV,SAAiB,EACjB,MAA+B,EAC/B,OAAgC,EAChC,OAAqB;QAErB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,MAAM,CACV,SAAiB,EACjB,MAA+B,EAC/B,OAAgC,EAChC,OAAqB;QAErB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAEO,cAAc,CAAC,SAAiB;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,aAAa,CACrB,8DAA8D,SAAS,GAAG,EAC1E;gBACE,IAAI,EAAE,uBAAuB;gBAC7B,QAAQ,EAAE,QAAQ;gBAClB,WAAW,EAAE,gBAAgB;gBAC7B,SAAS,EAAE,KAAK;aACjB,CACF,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ExtensionRegistry } from '@sensigo/realm';
|
|
2
|
+
/** Result of a single fixture test run. */
|
|
3
|
+
export interface TestResult {
|
|
4
|
+
/** Fixture name. */
|
|
5
|
+
name: string;
|
|
6
|
+
passed: boolean;
|
|
7
|
+
/** Error message if passed === false. */
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Options for runFixtureTests. */
|
|
11
|
+
export interface RunFixtureTestsOptions {
|
|
12
|
+
/** Path to workflow.yaml or to the directory containing workflow.yaml. */
|
|
13
|
+
workflowPath: string;
|
|
14
|
+
/** Path to a directory containing *.yaml fixture files. */
|
|
15
|
+
fixturesPath: string;
|
|
16
|
+
/**
|
|
17
|
+
* Optional ExtensionRegistry. If provided, handlers and adapters registered
|
|
18
|
+
* here are available as a fallback in the dispatcher. If omitted, a new empty
|
|
19
|
+
* registry is created per fixture run.
|
|
20
|
+
*/
|
|
21
|
+
registry?: ExtensionRegistry;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Runs all fixture tests for the workflow.
|
|
25
|
+
* Returns TestResult[] (one per fixture).
|
|
26
|
+
*/
|
|
27
|
+
export declare function runFixtureTests(options: RunFixtureTestsOptions): Promise<TestResult[]>;
|
|
28
|
+
/** Result of a single fixture test run. */
|
|
29
|
+
//# sourceMappingURL=test-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-runner.d.ts","sourceRoot":"","sources":["../../src/runner/test-runner.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,iBAAiB,EAKlB,MAAM,gBAAgB,CAAC;AAUxB,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mCAAmC;AACnC,MAAM,WAAW,sBAAsB;IACrC,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AA+KD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAY5F;AAED,2CAA2C"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Test runner — drives a workflow to completion per fixture and reports results.
|
|
2
|
+
import { loadWorkflowFromFile, findEligibleSteps, createDefaultRegistry, executeChain, propagateSkips, } from '@sensigo/realm';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { InMemoryStore } from '../store/in-memory-store.js';
|
|
6
|
+
import { loadFixturesFromDir } from '../fixtures/fixture-loader.js';
|
|
7
|
+
import { MockServiceRecorder } from '../mocks/mock-service.js';
|
|
8
|
+
import { createAgentDispatcher } from '../mocks/mock-agent.js';
|
|
9
|
+
import { createGateResponder } from '../mocks/mock-gate.js';
|
|
10
|
+
import { assertFinalState } from '../assertions/evidence.js';
|
|
11
|
+
async function runSingleFixture(fixture, definition, options) {
|
|
12
|
+
try {
|
|
13
|
+
const store = new InMemoryStore();
|
|
14
|
+
// Build per-fixture registry: start from built-in adapters, then overlay
|
|
15
|
+
// mock adapters so fixture mocks take precedence over the real ones.
|
|
16
|
+
const fixtureRegistry = createDefaultRegistry();
|
|
17
|
+
for (const [serviceName, mockOps] of Object.entries(fixture.mocks)) {
|
|
18
|
+
const serviceDef = definition.services?.[serviceName];
|
|
19
|
+
if (serviceDef !== undefined) {
|
|
20
|
+
const recorder = new MockServiceRecorder(serviceDef.adapter, mockOps);
|
|
21
|
+
fixtureRegistry.register('adapter', serviceDef.adapter, recorder);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const dispatcher = createAgentDispatcher(definition, fixtureRegistry, fixture.agent_responses, options.registry, fixture.agent_errors);
|
|
25
|
+
const run = await store.create({
|
|
26
|
+
workflowId: definition.id,
|
|
27
|
+
workflowVersion: definition.version,
|
|
28
|
+
params: fixture.params,
|
|
29
|
+
});
|
|
30
|
+
const runId = run.id;
|
|
31
|
+
const maxIterations = Object.keys(definition.steps).length * 2 + 10;
|
|
32
|
+
let iterations = 0;
|
|
33
|
+
let currentRun = run;
|
|
34
|
+
// Tracks how many times each step has been resumed after a mock error.
|
|
35
|
+
const stepResumeCount = {};
|
|
36
|
+
while (!currentRun.terminal_state) {
|
|
37
|
+
if (iterations++ >= maxIterations) {
|
|
38
|
+
return {
|
|
39
|
+
name: fixture.name,
|
|
40
|
+
passed: false,
|
|
41
|
+
error: 'Workflow stalled: exceeded maximum loop iterations',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const eligibleSteps = findEligibleSteps(definition, currentRun);
|
|
45
|
+
if (eligibleSteps.length === 0 && currentRun.pending_gate === undefined) {
|
|
46
|
+
return {
|
|
47
|
+
name: fixture.name,
|
|
48
|
+
passed: false,
|
|
49
|
+
error: `Workflow stalled: no eligible steps in phase '${currentRun.run_phase}'`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// If a gate is open, respond to it and continue.
|
|
53
|
+
if (currentRun.pending_gate !== undefined) {
|
|
54
|
+
const gateResult = await createGateResponder(store, definition, runId, fixture.gate_responses ?? {});
|
|
55
|
+
if (gateResult.status === 'error') {
|
|
56
|
+
return {
|
|
57
|
+
name: fixture.name,
|
|
58
|
+
passed: false,
|
|
59
|
+
error: gateResult.errors[0] ?? 'Unknown gate response error',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
currentRun = await store.get(runId);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const nextStep = eligibleSteps[0];
|
|
66
|
+
const stepDef = definition.steps[nextStep];
|
|
67
|
+
// Agent steps need the fixture's pre-built response as the input so the
|
|
68
|
+
// engine's input_schema validation passes before the dispatcher runs.
|
|
69
|
+
// Auto steps take no caller-provided input — the engine resolves it via
|
|
70
|
+
// input_map or the adapter/handler call.
|
|
71
|
+
const stepInput = stepDef?.execution === 'agent'
|
|
72
|
+
? (fixture.agent_responses[nextStep] ?? {})
|
|
73
|
+
: {};
|
|
74
|
+
const envelope = await executeChain(store, definition, {
|
|
75
|
+
runId,
|
|
76
|
+
command: nextStep,
|
|
77
|
+
input: stepInput,
|
|
78
|
+
dispatcher,
|
|
79
|
+
registry: fixtureRegistry,
|
|
80
|
+
});
|
|
81
|
+
if (envelope.status === 'error') {
|
|
82
|
+
// If this step has mock errors configured and we haven't exhausted them yet,
|
|
83
|
+
// simulate `realm run resume --from <step>`: remove the step from failed_steps
|
|
84
|
+
// to make it eligible again, then continue the loop.
|
|
85
|
+
const mockErrors = fixture.agent_errors?.[nextStep];
|
|
86
|
+
const resumesDone = stepResumeCount[nextStep] ?? 0;
|
|
87
|
+
if (mockErrors !== undefined && resumesDone < mockErrors.length) {
|
|
88
|
+
stepResumeCount[nextStep] = resumesDone + 1;
|
|
89
|
+
const failedRun = await store.get(runId);
|
|
90
|
+
const { terminal_reason: _tr, ...rest } = failedRun;
|
|
91
|
+
const newFailedSteps = rest.failed_steps.filter((s) => s !== nextStep);
|
|
92
|
+
// Recompute skipped_steps: the old skips were derived with nextStep in
|
|
93
|
+
// failed_steps. Re-derive from scratch so steps that were only skipped
|
|
94
|
+
// because of this failure become eligible again on the retry.
|
|
95
|
+
const tempRun = { ...rest, failed_steps: newFailedSteps, skipped_steps: [] };
|
|
96
|
+
const newSkippedSteps = propagateSkips(tempRun, definition);
|
|
97
|
+
await store.update({
|
|
98
|
+
...rest,
|
|
99
|
+
failed_steps: newFailedSteps,
|
|
100
|
+
skipped_steps: newSkippedSteps,
|
|
101
|
+
terminal_state: false,
|
|
102
|
+
});
|
|
103
|
+
currentRun = await store.get(runId);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
name: fixture.name,
|
|
108
|
+
passed: false,
|
|
109
|
+
error: envelope.errors[0] ?? 'Unknown error from step execution',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
currentRun = await store.get(runId);
|
|
113
|
+
}
|
|
114
|
+
// Assert final phase.
|
|
115
|
+
assertFinalState(currentRun, fixture.expected.final_state);
|
|
116
|
+
// Assert skipped_steps when declared — exact set equality.
|
|
117
|
+
if (fixture.expected.skipped_steps !== undefined) {
|
|
118
|
+
const actualSkipped = [...currentRun.skipped_steps].sort();
|
|
119
|
+
const expectedSkipped = [...fixture.expected.skipped_steps].sort();
|
|
120
|
+
if (JSON.stringify(actualSkipped) !== JSON.stringify(expectedSkipped)) {
|
|
121
|
+
throw new Error(`Expected skipped_steps ${JSON.stringify(expectedSkipped)} but got ${JSON.stringify(actualSkipped)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Assert expected evidence entries if provided.
|
|
125
|
+
if (fixture.expected.evidence !== undefined) {
|
|
126
|
+
for (const expected of fixture.expected.evidence) {
|
|
127
|
+
const snap = currentRun.evidence.find((e) => e.step_id === expected.step_id &&
|
|
128
|
+
e.kind !== 'gate_response' &&
|
|
129
|
+
(expected.status === undefined || e.status === expected.status));
|
|
130
|
+
if (snap === undefined) {
|
|
131
|
+
const statusClause = expected.status !== undefined ? ` with status '${expected.status}'` : '';
|
|
132
|
+
throw new Error(`Expected evidence for step '${expected.step_id}'${statusClause} not found`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { name: fixture.name, passed: true };
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
return {
|
|
140
|
+
name: fixture.name,
|
|
141
|
+
passed: false,
|
|
142
|
+
error: err instanceof Error ? err.message : String(err),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Runs all fixture tests for the workflow.
|
|
148
|
+
* Returns TestResult[] (one per fixture).
|
|
149
|
+
*/
|
|
150
|
+
export async function runFixtureTests(options) {
|
|
151
|
+
const workflowFilePath = options.workflowPath.endsWith('.yaml') || options.workflowPath.endsWith('.yml')
|
|
152
|
+
? options.workflowPath
|
|
153
|
+
: existsSync(join(options.workflowPath, 'workflow.yaml'))
|
|
154
|
+
? join(options.workflowPath, 'workflow.yaml')
|
|
155
|
+
: options.workflowPath;
|
|
156
|
+
const definition = loadWorkflowFromFile(workflowFilePath);
|
|
157
|
+
const fixtures = loadFixturesFromDir(options.fixturesPath);
|
|
158
|
+
return Promise.all(fixtures.map((fixture) => runSingleFixture(fixture, definition, options)));
|
|
159
|
+
}
|
|
160
|
+
/** Result of a single fixture test run. */
|
|
161
|
+
//# sourceMappingURL=test-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-runner.js","sourceRoot":"","sources":["../../src/runner/test-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle for a running GitHubMockServer instance.
|
|
3
|
+
* Call `close()` in afterAll to release the port.
|
|
4
|
+
*/
|
|
5
|
+
export interface GitHubMockServerHandle {
|
|
6
|
+
/** Base URL of the server, e.g. "http://localhost:3032". */
|
|
7
|
+
url: string;
|
|
8
|
+
/** Closes the server and resolves when fully shut down. */
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Starts a local HTTP server that serves responses from a JSON fixture file.
|
|
13
|
+
* Reads the fixture once at startup; does not re-read on each request.
|
|
14
|
+
*
|
|
15
|
+
* @param fixturePath - Absolute path to the fixture JSON file.
|
|
16
|
+
* @param port - Port to bind to (default 3032).
|
|
17
|
+
* @returns A handle with the server URL and a `close()` method.
|
|
18
|
+
*/
|
|
19
|
+
export declare function startGitHubMockServer(fixturePath: string, port?: number): Promise<GitHubMockServerHandle>;
|
|
20
|
+
//# sourceMappingURL=github-mock-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-mock-server.d.ts","sourceRoot":"","sources":["../../src/servers/github-mock-server.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAiCD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,IAAI,SAAO,GACV,OAAO,CAAC,sBAAsB,CAAC,CAqEjC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// GitHubMockServer — local HTTP server that replays fixture-defined GitHub API responses.
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
/**
|
|
5
|
+
* Matches a request method + URL path against a fixture key like "GET /repos/:owner/:repo/pulls/:pr".
|
|
6
|
+
* `:param` segments match any non-empty, non-slash string.
|
|
7
|
+
*/
|
|
8
|
+
function matchRoute(method, urlPath, fixtureKey) {
|
|
9
|
+
const spaceIdx = fixtureKey.indexOf(' ');
|
|
10
|
+
if (spaceIdx === -1)
|
|
11
|
+
return false;
|
|
12
|
+
const fixtureMethod = fixtureKey.slice(0, spaceIdx);
|
|
13
|
+
const fixturePath = fixtureKey.slice(spaceIdx + 1);
|
|
14
|
+
if (method !== fixtureMethod)
|
|
15
|
+
return false;
|
|
16
|
+
const urlSegments = urlPath.split('/');
|
|
17
|
+
const fixtureSegments = fixturePath.split('/');
|
|
18
|
+
if (urlSegments.length !== fixtureSegments.length)
|
|
19
|
+
return false;
|
|
20
|
+
for (let i = 0; i < fixtureSegments.length; i++) {
|
|
21
|
+
const fSeg = fixtureSegments[i];
|
|
22
|
+
const uSeg = urlSegments[i];
|
|
23
|
+
if (fSeg === undefined || uSeg === undefined)
|
|
24
|
+
return false;
|
|
25
|
+
if (fSeg.startsWith(':')) {
|
|
26
|
+
if (uSeg.length === 0)
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
else if (fSeg !== uSeg) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Starts a local HTTP server that serves responses from a JSON fixture file.
|
|
37
|
+
* Reads the fixture once at startup; does not re-read on each request.
|
|
38
|
+
*
|
|
39
|
+
* @param fixturePath - Absolute path to the fixture JSON file.
|
|
40
|
+
* @param port - Port to bind to (default 3032).
|
|
41
|
+
* @returns A handle with the server URL and a `close()` method.
|
|
42
|
+
*/
|
|
43
|
+
export async function startGitHubMockServer(fixturePath, port = 3032) {
|
|
44
|
+
const raw = fs.readFileSync(fixturePath, 'utf-8');
|
|
45
|
+
const fixture = JSON.parse(raw);
|
|
46
|
+
const server = http.createServer((req, res) => {
|
|
47
|
+
const method = req.method ?? 'GET';
|
|
48
|
+
const rawUrl = req.url ?? '/';
|
|
49
|
+
const urlPath = rawUrl.split('?')[0] ?? '/';
|
|
50
|
+
let matchedKey;
|
|
51
|
+
for (const key of Object.keys(fixture)) {
|
|
52
|
+
if (matchRoute(method, urlPath, key)) {
|
|
53
|
+
matchedKey = key;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (matchedKey === undefined) {
|
|
58
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
59
|
+
res.end(JSON.stringify({ error: 'no matching fixture route' }));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const entry = fixture[matchedKey];
|
|
63
|
+
if ('echo' in entry) {
|
|
64
|
+
const chunks = [];
|
|
65
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
66
|
+
req.on('end', () => {
|
|
67
|
+
try {
|
|
68
|
+
const body = JSON.parse(Buffer.concat(chunks).toString('utf-8'));
|
|
69
|
+
const responseBody = {};
|
|
70
|
+
for (const field of entry.echo) {
|
|
71
|
+
responseBody[field] = body[field];
|
|
72
|
+
}
|
|
73
|
+
res.writeHead(entry.status, { 'Content-Type': 'application/json' });
|
|
74
|
+
res.end(JSON.stringify(responseBody));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
78
|
+
res.end(JSON.stringify({ error: 'invalid JSON body' }));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
res.writeHead(entry.status, { 'Content-Type': 'application/json' });
|
|
84
|
+
res.end(JSON.stringify(entry.body));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
await new Promise((resolve, reject) => {
|
|
88
|
+
server.once('error', (err) => {
|
|
89
|
+
reject(new Error(`GitHubMockServer: cannot bind to port ${port}: ${err.message}`));
|
|
90
|
+
});
|
|
91
|
+
server.listen(port, '127.0.0.1', () => resolve());
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
url: `http://localhost:${port}`,
|
|
95
|
+
close() {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
server.close((err) => {
|
|
98
|
+
if (err !== undefined)
|
|
99
|
+
reject(err);
|
|
100
|
+
else
|
|
101
|
+
resolve();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=github-mock-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-mock-server.js","sourceRoot":"","sources":["../../src/servers/github-mock-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RunStore, type RunRecord, type CreateRunOptions, type WorkflowDefinition } from '@sensigo/realm';
|
|
2
|
+
/** In-memory implementation of RunStore. Uses a Map keyed by run ID. No I/O, no locking. */
|
|
3
|
+
export declare class InMemoryStore implements RunStore {
|
|
4
|
+
private readonly runs;
|
|
5
|
+
create(options: CreateRunOptions): Promise<RunRecord>;
|
|
6
|
+
get(runId: string): Promise<RunRecord>;
|
|
7
|
+
update(record: RunRecord): Promise<RunRecord>;
|
|
8
|
+
claimStep(runId: string, stepName: string, definition: WorkflowDefinition): Promise<RunRecord>;
|
|
9
|
+
list(workflowId?: string): Promise<RunRecord[]>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=in-memory-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-store.d.ts","sourceRoot":"","sources":["../../src/store/in-memory-store.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,4FAA4F;AAC5F,qBAAa,aAAc,YAAW,QAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgC;IAE/C,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC;IAsBrD,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAatC,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IA6B7C,SAAS,CACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,kBAAkB,GAC7B,OAAO,CAAC,SAAS,CAAC;IAoCf,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;CAOtD"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// InMemoryStore — in-memory implementation of RunStore for use in tests.
|
|
2
|
+
import { WorkflowError, findEligibleSteps, deriveRunPhase, } from '@sensigo/realm';
|
|
3
|
+
/** In-memory implementation of RunStore. Uses a Map keyed by run ID. No I/O, no locking. */
|
|
4
|
+
export class InMemoryStore {
|
|
5
|
+
runs = new Map();
|
|
6
|
+
async create(options) {
|
|
7
|
+
const now = new Date().toISOString();
|
|
8
|
+
const record = {
|
|
9
|
+
id: crypto.randomUUID(),
|
|
10
|
+
workflow_id: options.workflowId,
|
|
11
|
+
workflow_version: options.workflowVersion,
|
|
12
|
+
completed_steps: [],
|
|
13
|
+
in_progress_steps: [],
|
|
14
|
+
failed_steps: [],
|
|
15
|
+
skipped_steps: [],
|
|
16
|
+
run_phase: 'running',
|
|
17
|
+
version: 0,
|
|
18
|
+
params: options.params,
|
|
19
|
+
evidence: [],
|
|
20
|
+
created_at: now,
|
|
21
|
+
updated_at: now,
|
|
22
|
+
terminal_state: false,
|
|
23
|
+
};
|
|
24
|
+
this.runs.set(record.id, record);
|
|
25
|
+
return record;
|
|
26
|
+
}
|
|
27
|
+
async get(runId) {
|
|
28
|
+
const record = this.runs.get(runId);
|
|
29
|
+
if (record === undefined) {
|
|
30
|
+
throw new WorkflowError(`Run '${runId}' not found`, {
|
|
31
|
+
code: 'STATE_RUN_NOT_FOUND',
|
|
32
|
+
category: 'STATE',
|
|
33
|
+
agentAction: 'report_to_user',
|
|
34
|
+
retryable: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return record;
|
|
38
|
+
}
|
|
39
|
+
async update(record) {
|
|
40
|
+
const existing = this.runs.get(record.id);
|
|
41
|
+
if (existing === undefined) {
|
|
42
|
+
throw new WorkflowError(`Run '${record.id}' not found`, {
|
|
43
|
+
code: 'STATE_RUN_NOT_FOUND',
|
|
44
|
+
category: 'STATE',
|
|
45
|
+
agentAction: 'report_to_user',
|
|
46
|
+
retryable: false,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (existing.version !== record.version) {
|
|
50
|
+
throw new WorkflowError('Version conflict — run was modified by another process', {
|
|
51
|
+
code: 'STATE_SNAPSHOT_MISMATCH',
|
|
52
|
+
category: 'STATE',
|
|
53
|
+
agentAction: 'report_to_user',
|
|
54
|
+
retryable: true,
|
|
55
|
+
details: { runId: record.id, expected: record.version, actual: existing.version },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const updated = {
|
|
59
|
+
...record,
|
|
60
|
+
run_phase: deriveRunPhase(record),
|
|
61
|
+
version: record.version + 1,
|
|
62
|
+
updated_at: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
this.runs.set(updated.id, updated);
|
|
65
|
+
return updated;
|
|
66
|
+
}
|
|
67
|
+
async claimStep(runId, stepName, definition) {
|
|
68
|
+
const run = await this.get(runId);
|
|
69
|
+
const alreadyDone = [
|
|
70
|
+
...run.completed_steps,
|
|
71
|
+
...run.in_progress_steps,
|
|
72
|
+
...run.failed_steps,
|
|
73
|
+
...run.skipped_steps,
|
|
74
|
+
];
|
|
75
|
+
if (alreadyDone.includes(stepName)) {
|
|
76
|
+
throw new WorkflowError(`Step '${stepName}' is already claimed or done`, {
|
|
77
|
+
code: 'STATE_STEP_ALREADY_CLAIMED',
|
|
78
|
+
category: 'STATE',
|
|
79
|
+
agentAction: 'resolve_precondition',
|
|
80
|
+
retryable: false,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const eligible = findEligibleSteps(definition, run);
|
|
84
|
+
if (!eligible.includes(stepName)) {
|
|
85
|
+
throw new WorkflowError(`Step '${stepName}' is not eligible`, {
|
|
86
|
+
code: 'STATE_STEP_NOT_ELIGIBLE',
|
|
87
|
+
category: 'STATE',
|
|
88
|
+
agentAction: 'resolve_precondition',
|
|
89
|
+
retryable: false,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const updated = {
|
|
93
|
+
...run,
|
|
94
|
+
in_progress_steps: [...run.in_progress_steps, stepName],
|
|
95
|
+
run_phase: 'running',
|
|
96
|
+
version: run.version + 1,
|
|
97
|
+
updated_at: new Date().toISOString(),
|
|
98
|
+
};
|
|
99
|
+
this.runs.set(updated.id, updated);
|
|
100
|
+
return updated;
|
|
101
|
+
}
|
|
102
|
+
async list(workflowId) {
|
|
103
|
+
const all = [...this.runs.values()];
|
|
104
|
+
if (workflowId !== undefined) {
|
|
105
|
+
return all.filter((r) => r.workflow_id === workflowId);
|
|
106
|
+
}
|
|
107
|
+
return all;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=in-memory-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-store.js","sourceRoot":"","sources":["../../src/store/in-memory-store.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,cAAc,GAKf,MAAM,gBAAgB,CAAC;AAExB,4FAA4F;AAC5F,MAAM,OAAO,aAAa;IACP,IAAI,GAAG,IAAI,GAAG,EAAqB,CAAC;IAErD,KAAK,CAAC,MAAM,CAAC,OAAyB;QACpC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,MAAM,GAAc;YACxB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;YACvB,WAAW,EAAE,OAAO,CAAC,UAAU;YAC/B,gBAAgB,EAAE,OAAO,CAAC,eAAe;YACzC,eAAe,EAAE,EAAE;YACnB,iBAAiB,EAAE,EAAE;YACrB,YAAY,EAAE,EAAE;YAChB,aAAa,EAAE,EAAE;YACjB,SAAS,EAAE,SAAS;YACpB,OAAO,EAAE,CAAC;YACV,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,GAAG;YACf,cAAc,EAAE,KAAK;SACtB,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACjC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAa;QACrB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,aAAa,CAAC,QAAQ,KAAK,aAAa,EAAE;gBAClD,IAAI,EAAE,qBAAqB;gBAC3B,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,gBAAgB;gBAC7B,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAiB;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,aAAa,CAAC,QAAQ,MAAM,CAAC,EAAE,aAAa,EAAE;gBACtD,IAAI,EAAE,qBAAqB;gBAC3B,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,gBAAgB;gBAC7B,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;QACL,CAAC;QACD,IAAI,QAAQ,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC;YACxC,MAAM,IAAI,aAAa,CAAC,wDAAwD,EAAE;gBAChF,IAAI,EAAE,yBAAyB;gBAC/B,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,gBAAgB;gBAC7B,SAAS,EAAE,IAAI;gBACf,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,EAAE;aAClF,CAAC,CAAC;QACL,CAAC;QACD,MAAM,OAAO,GAAc;YACzB,GAAG,MAAM;YACT,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC;YACjC,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,CAAC;YAC3B,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACnC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,SAAS,CACb,KAAa,EACb,QAAgB,EAChB,UAA8B;QAE9B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,WAAW,GAAG;YAClB,GAAG,GAAG,CAAC,eAAe;YACtB,GAAG,GAAG,CAAC,iBAAiB;YACxB,GAAG,GAAG,CAAC,YAAY;YACnB,GAAG,GAAG,CAAC,aAAa;SACrB,CAAC;QACF,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,aAAa,CAAC,SAAS,QAAQ,8BAA8B,EAAE;gBACvE,IAAI,EAAE,4BAA4B;gBAClC,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,sBAAsB;gBACnC,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,aAAa,CAAC,SAAS,QAAQ,mBAAmB,EAAE;gBAC5D,IAAI,EAAE,yBAAyB;gBAC/B,QAAQ,EAAE,OAAO;gBACjB,WAAW,EAAE,sBAAsB;gBACnC,SAAS,EAAE,KAAK;aACjB,CAAC,CAAC;QACL,CAAC;QACD,MAAM,OAAO,GAAc;YACzB,GAAG,GAAG;YACN,iBAAiB,EAAE,CAAC,GAAG,GAAG,CAAC,iBAAiB,EAAE,QAAQ,CAAC;YACvD,SAAS,EAAE,SAAS;YACpB,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG,CAAC;YACxB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACnC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAmB;QAC5B,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACpC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
|