416 lines
13 KiB
JavaScript
416 lines
13 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
|
|
import {
|
|
parseDeploymentProfileYaml,
|
|
validateDeploymentProfileSchema,
|
|
generateDeploymentProfileArtifact,
|
|
generateDeploymentProfileArtifactFromFile,
|
|
} from '../src/storage/profile-generator.mjs';
|
|
import { validateDeploymentProfileArtifact } from '../src/storage/profile-artifact.mjs';
|
|
|
|
const packageRoot = path.resolve(import.meta.dirname, '..');
|
|
const repoRoot = path.resolve(packageRoot, '..', '..');
|
|
|
|
const complexProfileYaml = `apiVersion: reporting-governance/v1alpha1
|
|
kind: DeploymentProfile
|
|
metadata:
|
|
id: demo
|
|
version: 1.0.0
|
|
runtime: openclaw
|
|
title: Demo Profile
|
|
summary: >-
|
|
Complex profile for YAML parser coverage with nested objects,
|
|
arrays, and folded scalars.
|
|
spec:
|
|
package:
|
|
pluginVersion: 0.1.0-mainline
|
|
compatibility:
|
|
adapterInterface: reporting-governance-adapter-interface
|
|
deploymentModel: reporting-governance-deployment-model
|
|
policies:
|
|
enabledPacks:
|
|
- no-silence
|
|
- verified-completion-only
|
|
overrides:
|
|
completion:
|
|
minQuality: moderate
|
|
checkpoints:
|
|
overdueAction: force_checkpoint
|
|
escalationAfterMisses: 2
|
|
adapters:
|
|
hook:
|
|
enabled: true
|
|
watchdog:
|
|
enabled: true
|
|
scheduleMode: cron
|
|
queue:
|
|
enabled: true
|
|
dispatcher:
|
|
enabled: true
|
|
bridge:
|
|
enabled: true
|
|
truthfulDeliveryStates:
|
|
- dispatched
|
|
- acked
|
|
sender:
|
|
mode: openclaw-cli
|
|
orchestrator:
|
|
enabled: true
|
|
notifications:
|
|
operatorVisibleRecoveryRequired: true
|
|
allowedTerminalStates:
|
|
- acked
|
|
- blocked
|
|
audit:
|
|
portableArtifactsRequired: true
|
|
requiredArtifacts:
|
|
- canonical_events
|
|
- queue_items
|
|
- bridge_receipts
|
|
- profile_snapshot
|
|
capability_expectations:
|
|
required:
|
|
- create_queue_items
|
|
preferred:
|
|
- direct_sender_binding
|
|
`;
|
|
|
|
test('deployment profile yaml parser reads strict-manager-mode profile shape', () => {
|
|
const artifact = generateDeploymentProfileArtifactFromFile(path.join(repoRoot, 'profiles', 'strict-manager-mode.yaml'));
|
|
|
|
assert.equal(artifact.metadata.id, 'strict-manager-mode');
|
|
assert.equal(artifact.metadata.runtime, 'openclaw');
|
|
assert.equal(artifact.spec.package.pluginVersion, '0.1.0-mainline');
|
|
assert.equal(artifact.metadata.source_profile, 'profiles/strict-manager-mode.yaml');
|
|
});
|
|
|
|
test('deployment profile schema validator rejects malformed profile', () => {
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({ kind: 'DeploymentProfile' }),
|
|
/deployment profile schema validation failed: .*missing required property apiVersion/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
apiVersion: 'reporting-governance/v1alpha1',
|
|
kind: 'DeploymentProfile',
|
|
metadata: { id: 'x', version: '1.0.0', runtime: 'openclaw' },
|
|
spec: {
|
|
package: { pluginVersion: 7 },
|
|
adapters: {
|
|
hook: { enabled: true },
|
|
watchdog: { enabled: true },
|
|
queue: { enabled: true },
|
|
dispatcher: { enabled: true },
|
|
bridge: { enabled: true },
|
|
sender: { mode: 'openclaw-cli' },
|
|
orchestrator: { enabled: true },
|
|
},
|
|
notifications: {
|
|
operatorVisibleRecoveryRequired: true,
|
|
allowedTerminalStates: ['acked'],
|
|
},
|
|
audit: { portableArtifactsRequired: true, requiredArtifacts: ['canonical_events', 'queue_items', 'bridge_receipts'] },
|
|
capability_expectations: { required: [] },
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/package\/pluginVersion must be string/
|
|
);
|
|
});
|
|
|
|
test('deployment profile schema validator rejects tightened semantic violations', () => {
|
|
const validBase = {
|
|
apiVersion: 'reporting-governance/v1alpha1',
|
|
kind: 'DeploymentProfile',
|
|
metadata: { id: 'strict', version: '1.0.0', runtime: 'openclaw' },
|
|
spec: {
|
|
package: { pluginVersion: '0.1.0-mainline' },
|
|
adapters: {
|
|
hook: { enabled: true },
|
|
watchdog: { enabled: true },
|
|
queue: { enabled: true },
|
|
dispatcher: { enabled: true },
|
|
bridge: { enabled: true },
|
|
sender: { mode: 'openclaw-cli' },
|
|
orchestrator: { enabled: true },
|
|
},
|
|
notifications: {
|
|
operatorVisibleRecoveryRequired: true,
|
|
allowedTerminalStates: ['acked'],
|
|
},
|
|
audit: {
|
|
portableArtifactsRequired: true,
|
|
requiredArtifacts: ['canonical_events', 'queue_items', 'bridge_receipts'],
|
|
},
|
|
capability_expectations: { required: [] },
|
|
},
|
|
};
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
audit: {
|
|
portableArtifactsRequired: true,
|
|
requiredArtifacts: [],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/audit\/requiredArtifacts must NOT have fewer than 1 items/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
notifications: {
|
|
operatorVisibleRecoveryRequired: 'yes',
|
|
allowedTerminalStates: ['acked'],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/notifications\/operatorVisibleRecoveryRequired must be boolean/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
notifications: {
|
|
operatorVisibleRecoveryRequired: true,
|
|
allowedTerminalStates: ['done'],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/notifications\/allowedTerminalStates\/0 must be equal to one of the allowed values/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
audit: {
|
|
portableArtifactsRequired: 'true',
|
|
requiredArtifacts: ['canonical_events', 'queue_items', 'bridge_receipts'],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/audit\/portableArtifactsRequired must be boolean/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
notifications: {
|
|
operatorVisibleRecoveryRequired: true,
|
|
allowedTerminalStates: [],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/notifications\/allowedTerminalStates must NOT have fewer than 1 items/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
notifications: {
|
|
operatorVisibleRecoveryRequired: true,
|
|
allowedTerminalStates: ['acked', 'acked'],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/notifications\/allowedTerminalStates must NOT have duplicate items/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
audit: {
|
|
portableArtifactsRequired: true,
|
|
requiredArtifacts: ['canonical_events', 'queue_items', 'queue_items', 'bridge_receipts'],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/audit\/requiredArtifacts must NOT have duplicate items/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
audit: {
|
|
portableArtifactsRequired: true,
|
|
requiredArtifacts: ['queue_items', 'bridge_receipts'],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/audit\/requiredArtifacts must contain at least 1 valid item\(s\)/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
audit: {
|
|
portableArtifactsRequired: true,
|
|
requiredArtifacts: ['canonical_events', 'queue_items', 'bridge_receipts', 'made_up_artifact'],
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/audit\/requiredArtifacts\/3 must be equal to one of the allowed values/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
adapters: {
|
|
...validBase.spec.adapters,
|
|
queue: { enabled: 'true' },
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/adapters\/queue\/enabled must be boolean/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
adapters: {
|
|
...validBase.spec.adapters,
|
|
sender: { mode: 'shim' },
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/adapters\/sender\/mode must be equal to one of the allowed values/
|
|
);
|
|
|
|
assert.throws(
|
|
() => validateDeploymentProfileSchema({
|
|
...validBase,
|
|
spec: {
|
|
...validBase.spec,
|
|
adapters: {
|
|
watchdog: { enabled: true },
|
|
queue: { enabled: true },
|
|
dispatcher: { enabled: true },
|
|
bridge: { enabled: true },
|
|
sender: { mode: 'openclaw-cli' },
|
|
orchestrator: { enabled: true },
|
|
},
|
|
},
|
|
}),
|
|
/deployment profile schema validation failed: .*\/spec\/adapters missing required property hook/
|
|
);
|
|
});
|
|
|
|
test('deployment profile yaml parser covers composite nested yaml structure', () => {
|
|
const profile = parseDeploymentProfileYaml(complexProfileYaml);
|
|
|
|
assert.equal(profile.metadata.summary, 'Complex profile for YAML parser coverage with nested objects, arrays, and folded scalars.');
|
|
assert.deepEqual(profile.spec.policies.enabledPacks, ['no-silence', 'verified-completion-only']);
|
|
assert.equal(profile.spec.policies.overrides.checkpoints.overdueAction, 'force_checkpoint');
|
|
assert.deepEqual(profile.spec.adapters.bridge.truthfulDeliveryStates, ['dispatched', 'acked']);
|
|
assert.deepEqual(profile.spec.notifications.allowedTerminalStates, ['acked', 'blocked']);
|
|
});
|
|
|
|
test('yaml to artifact generation produces validator-compatible package artifact', () => {
|
|
const profile = parseDeploymentProfileYaml(complexProfileYaml);
|
|
const artifact = generateDeploymentProfileArtifact(profile, { sourceProfile: 'profiles/demo.yaml' });
|
|
|
|
assert.equal(artifact.kind, 'DeploymentProfileArtifact');
|
|
assert.equal(artifact.metadata.source_profile, 'profiles/demo.yaml');
|
|
assert.equal(artifact.spec.bindings.entrypoint, 'scripts/watchdog_auto_notify_orchestrator.mjs');
|
|
assert.doesNotThrow(() => validateDeploymentProfileArtifact(artifact));
|
|
});
|
|
|
|
test('strict-manager YAML generation matches checked-in package artifact snapshot', () => {
|
|
const generated = generateDeploymentProfileArtifactFromFile(path.join(repoRoot, 'profiles', 'strict-manager-mode.yaml'));
|
|
const checkedIn = validateDeploymentProfileArtifact(
|
|
JSON.parse(
|
|
fs.readFileSync(
|
|
path.join(packageRoot, 'profiles', 'strict-manager-mode.profile.json'),
|
|
'utf8'
|
|
)
|
|
)
|
|
);
|
|
|
|
assert.deepEqual(generated, checkedIn);
|
|
});
|
|
|
|
test('profile artifact generator CLI exits 1 with actionable error on malformed profile', () => {
|
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'reporting-governance-profile-cli-'));
|
|
const malformedProfilePath = path.join(tempDir, 'malformed.yaml');
|
|
const outputArtifactPath = path.join(tempDir, 'artifact.json');
|
|
|
|
fs.writeFileSync(malformedProfilePath, `apiVersion: reporting-governance/v1alpha1
|
|
kind: DeploymentProfile
|
|
metadata:
|
|
id: malformed
|
|
version: 1.0.0
|
|
runtime: openclaw
|
|
spec:
|
|
package:
|
|
pluginVersion: 123
|
|
adapters:
|
|
hook:
|
|
enabled: true
|
|
watchdog:
|
|
enabled: true
|
|
queue:
|
|
enabled: true
|
|
dispatcher:
|
|
enabled: true
|
|
bridge:
|
|
enabled: true
|
|
sender:
|
|
mode: openclaw-cli
|
|
orchestrator:
|
|
enabled: true
|
|
notifications:
|
|
operatorVisibleRecoveryRequired: true
|
|
allowedTerminalStates:
|
|
- acked
|
|
audit:
|
|
portableArtifactsRequired: true
|
|
requiredArtifacts:
|
|
- canonical_events
|
|
- queue_items
|
|
- bridge_receipts
|
|
capability_expectations:
|
|
required: []
|
|
`, 'utf8');
|
|
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[path.join(repoRoot, 'scripts', 'generate_reporting_governance_profile_artifact.mjs'), malformedProfilePath, outputArtifactPath],
|
|
{ encoding: 'utf8' }
|
|
);
|
|
|
|
assert.equal(result.status, 1);
|
|
assert.match(result.stderr, /profile artifact generation failed:/);
|
|
assert.match(result.stderr, /deployment profile schema validation failed:/);
|
|
assert.match(result.stderr, /\/spec\/package\/pluginVersion must be string/);
|
|
assert.equal(fs.existsSync(outputArtifactPath), false);
|
|
});
|