/**
 * RED-GREEN tests for signed-manifest push (W2.x infrastructure).
 *
 * @connexum/ai-governance  F-NEW-PRODUCT-AGENT-DISCOVERY-CLI push transport
 */
import { describe, it } from 'node:test';
import * as assert from 'node:assert/strict';
import { generateOrgKeyPair, exportPublicKey, importPublicKey, signManifest, verifySignedManifest, ManifestSignatureError, DEFAULT_MAX_AGE_SECONDS, } from './signed-manifest.js';
const baseManifest = () => ({
    agentId: 'agent-1',
    name: 'Agent 1',
    tools: ['Bash', 'WebFetch'],
    hooks: { preToolUse: true, postToolUse: true, postResponse: true },
});
describe('signed-manifest push transport', () => {
    it('sign and verify with the same keypair succeeds', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const signed = signManifest(baseManifest(), 'org-x', privateKey);
        const payload = verifySignedManifest(signed, publicKey, { expectedOrgId: 'org-x' });
        assert.equal(payload.orgId, 'org-x');
        assert.equal(payload.manifest.agentId, 'agent-1');
        assert.equal(payload.nonce.length, 32);
    });
    it('export/import public key round-trips', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const signed = signManifest(baseManifest(), 'org-x', privateKey);
        const exported = exportPublicKey(publicKey);
        const reImported = importPublicKey(exported);
        const payload = verifySignedManifest(signed, reImported, { expectedOrgId: 'org-x' });
        assert.equal(payload.orgId, 'org-x');
    });
    it('verification rejects a tampered payload (INVALID_SIGNATURE)', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const signed = signManifest(baseManifest(), 'org-x', privateKey);
        // Tamper with the agentId after signing.
        const tampered = {
            ...signed,
            payload: {
                ...signed.payload,
                manifest: { ...signed.payload.manifest, agentId: 'attacker-agent' },
            },
        };
        assert.throws(() => verifySignedManifest(tampered, publicKey, { expectedOrgId: 'org-x' }), (e) => e instanceof ManifestSignatureError && e.code === 'INVALID_SIGNATURE');
    });
    it('verification rejects when signed by a different private key (INVALID_SIGNATURE)', () => {
        const { privateKey: pkA } = generateOrgKeyPair();
        const { publicKey: pubB } = generateOrgKeyPair();
        const signed = signManifest(baseManifest(), 'org-x', pkA);
        assert.throws(() => verifySignedManifest(signed, pubB, { expectedOrgId: 'org-x' }), (e) => e instanceof ManifestSignatureError && e.code === 'INVALID_SIGNATURE');
    });
    it('verification rejects an org-id mismatch (ORG_MISMATCH)', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const signed = signManifest(baseManifest(), 'org-x', privateKey);
        assert.throws(() => verifySignedManifest(signed, publicKey, { expectedOrgId: 'org-y' }), (e) => e instanceof ManifestSignatureError && e.code === 'ORG_MISMATCH');
    });
    it('verification rejects an expired payload (EXPIRED)', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const past = Date.now() - (DEFAULT_MAX_AGE_SECONDS + 60) * 1000;
        const signed = signManifest(baseManifest(), 'org-x', privateKey, { nowMs: past });
        assert.throws(() => verifySignedManifest(signed, publicKey, { expectedOrgId: 'org-x' }), (e) => e instanceof ManifestSignatureError && e.code === 'EXPIRED');
    });
    it('verification rejects a future-dated payload beyond skew tolerance', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const future = Date.now() + (DEFAULT_MAX_AGE_SECONDS + 60) * 1000;
        const signed = signManifest(baseManifest(), 'org-x', privateKey, { nowMs: future });
        assert.throws(() => verifySignedManifest(signed, publicKey, { expectedOrgId: 'org-x' }), (e) => e instanceof ManifestSignatureError && e.code === 'MALFORMED');
    });
    it('verification accepts a recent payload within max-age', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const recent = Date.now() - 60 * 1000; // 1 minute ago
        const signed = signManifest(baseManifest(), 'org-x', privateKey, { nowMs: recent });
        const payload = verifySignedManifest(signed, publicKey, { expectedOrgId: 'org-x' });
        assert.equal(payload.orgId, 'org-x');
    });
    it('different manifests produce different signatures (nonce + payload entropy)', () => {
        const { privateKey } = generateOrgKeyPair();
        const s1 = signManifest(baseManifest(), 'org-x', privateKey);
        const s2 = signManifest(baseManifest(), 'org-x', privateKey);
        assert.notEqual(s1.signature, s2.signature, 'nonce should make signatures non-deterministic');
        assert.notEqual(s1.payload.nonce, s2.payload.nonce);
    });
    it('canonical JSON makes signatures key-order-independent (forward compat across SDKs)', () => {
        const { publicKey, privateKey } = generateOrgKeyPair();
        const m1 = baseManifest();
        const signed = signManifest(m1, 'org-x', privateKey);
        // Reconstruct the manifest with different key insertion order; the
        // canonical serialiser sorts keys, so verification must still succeed.
        const reorderedManifest = {
            tools: m1.tools,
            hooks: m1.hooks,
            name: m1.name,
            agentId: m1.agentId,
        };
        const signedReorder = {
            ...signed,
            payload: { ...signed.payload, manifest: reorderedManifest },
        };
        // Same data; expect signature to still verify.
        const payload = verifySignedManifest(signedReorder, publicKey, { expectedOrgId: 'org-x' });
        assert.equal(payload.orgId, 'org-x');
    });
    it('empty orgId on sign throws MALFORMED', () => {
        const { privateKey } = generateOrgKeyPair();
        assert.throws(() => signManifest(baseManifest(), '', privateKey), (e) => e instanceof ManifestSignatureError && e.code === 'MALFORMED');
    });
    it('signature is a non-empty base64url string', () => {
        const { privateKey } = generateOrgKeyPair();
        const signed = signManifest(baseManifest(), 'org-x', privateKey);
        assert.ok(signed.signature.length > 0);
        assert.match(signed.signature, /^[A-Za-z0-9_-]+$/);
        assert.equal(signed.alg, 'ed25519');
    });
});
//# sourceMappingURL=signed-manifest.test.js.map