"use strict";
/**
 * test_mission_checkpoint — Story 6.2 regression suite.
 *
 * Asserts the MissionCheckpointStore contract:
 *   1. Save + read round-trip; getLatest returns null before any save
 *   2. Resume semantics: resumeFromCheckpoint returns latest, list/clear
 *   3. Save overwrites previous checkpoint (latest wins); sanitization
 *      protects against directory traversal in missionId
 *
 * Path resolution honors OPENLIFE_STATE_DIR (so the test can sandbox in a
 * temp dir without touching the real `.openlife/`).
 *
 * Run via: `npm run test:mission-checkpoint`. Prints
 * TEST_MISSION_CHECKPOINT_OK on full pass.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const MissionState_1 = require("./orchestrator/MissionState");
function assert(cond, msg) {
    if (!cond)
        throw new Error(`assertion failed: ${msg}`);
}
function mkTmp() {
    return fs.mkdtempSync(path.join(os.tmpdir(), 'mission-ckpt-test-'));
}
function rmTmp(dir) {
    try {
        fs.rmSync(dir, { recursive: true, force: true });
    }
    catch { /* ignore */ }
}
// ─────────────────────────────────────────────────────────────
// Scenario 1: save + getLatest round-trip
// ─────────────────────────────────────────────────────────────
function scenarioRoundTrip() {
    const tmpRoot = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmpRoot, '.openlife');
        const store = new MissionState_1.MissionCheckpointStore();
        // Pre-condition: no checkpoint exists
        assert(store.getLatestCheckpoint('mission-001') === null, 'must return null before any save');
        assert(store.list().length === 0, 'list must be empty initially');
        // Save a checkpoint
        const state = { workflowRunId: 'run-abc', completedSteps: ['s1', 's2'], pendingSteps: ['s3'] };
        store.saveCheckpoint('mission-001', 'step-3', state);
        // Read back
        const ckpt = store.getLatestCheckpoint('mission-001');
        assert(ckpt !== null, 'getLatest must return saved checkpoint');
        const c = ckpt;
        assert(c.missionId === 'mission-001', 'missionId mismatch');
        assert(c.step === 'step-3', 'step mismatch');
        assert(JSON.stringify(c.state) === JSON.stringify(state), 'state round-trip mismatch');
        assert(typeof c.savedAt === 'string' && c.savedAt.length > 0, 'savedAt must be a non-empty ISO string');
        // File should live under <state>/checkpoints/
        const baseDir = store._baseDir();
        assert(baseDir.startsWith(process.env.OPENLIFE_STATE_DIR), 'baseDir must respect OPENLIFE_STATE_DIR');
        assert(fs.existsSync(path.join(baseDir, 'mission-001.json')), 'checkpoint file must exist on disk');
        // List must report the missionId
        const list = store.list();
        assert(list.length === 1 && list[0] === 'mission-001', `list mismatch: ${JSON.stringify(list)}`);
        console.log('✅ scenario 1: save + getLatest round-trip');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmpRoot);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 2: resume semantics + clear
// ─────────────────────────────────────────────────────────────
function scenarioResumeAndClear() {
    const tmpRoot = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmpRoot, '.openlife');
        const store = new MissionState_1.MissionCheckpointStore();
        store.saveCheckpoint('m-a', 'step-1', { foo: 'a' });
        store.saveCheckpoint('m-b', 'step-1', { foo: 'b' });
        store.saveCheckpoint('m-c', 'step-2', { foo: 'c' });
        const list = store.list().sort();
        assert(list.length === 3, `expected 3 missions with checkpoints, got ${list.length}`);
        assert(JSON.stringify(list) === JSON.stringify(['m-a', 'm-b', 'm-c']), `list contents: ${JSON.stringify(list)}`);
        // resumeFromCheckpoint should return the same as getLatest
        const resumeB = store.resumeFromCheckpoint('m-b');
        const getB = store.getLatestCheckpoint('m-b');
        assert(resumeB !== null && getB !== null, 'both must return non-null');
        assert(JSON.stringify(resumeB) === JSON.stringify(getB), 'resume and getLatest must return identical data');
        // Clear m-b
        store.clear('m-b');
        assert(store.getLatestCheckpoint('m-b') === null, 'after clear, getLatest must return null');
        assert(store.list().length === 2, 'list must shrink after clear');
        // Clearing a non-existent missionId is a no-op (no throw)
        store.clear('does-not-exist');
        console.log('✅ scenario 2: resume semantics + clear');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmpRoot);
    }
}
// ─────────────────────────────────────────────────────────────
// Scenario 3: overwrite + missionId sanitization
// ─────────────────────────────────────────────────────────────
function scenarioOverwriteAndSanitize() {
    const tmpRoot = mkTmp();
    const prev = process.env.OPENLIFE_STATE_DIR;
    try {
        process.env.OPENLIFE_STATE_DIR = path.join(tmpRoot, '.openlife');
        const store = new MissionState_1.MissionCheckpointStore();
        // Overwrite: saving twice keeps only the latest
        store.saveCheckpoint('m-x', 'step-1', { value: 'first' });
        const firstSavedAt = store.getLatestCheckpoint('m-x')?.savedAt;
        // Ensure measurable time gap before overwrite (ISO timestamps are ms precision)
        const deadline = Date.now() + 5;
        while (Date.now() < deadline) { /* spin briefly */ }
        store.saveCheckpoint('m-x', 'step-2', { value: 'second' });
        const after = store.getLatestCheckpoint('m-x');
        assert(after !== null, 'overwritten checkpoint must still be readable');
        assert(after.step === 'step-2', `latest step must be 'step-2', got '${after.step}'`);
        assert(after.state.value === 'second', 'latest state must reflect overwrite');
        assert(after.savedAt !== firstSavedAt, 'savedAt must change after overwrite');
        // List should still report m-x exactly once
        assert(store.list().filter((m) => m === 'm-x').length === 1, 'list must not duplicate after overwrite');
        // Sanitization: missionIds with path traversal attempts must be neutralized
        const sneaky = '../../../etc/passwd';
        store.saveCheckpoint(sneaky, 'step-1', { value: 'blocked' });
        const baseDir = store._baseDir();
        // The sanitized file must live INSIDE baseDir, never outside it
        const dangerousPath = path.join(baseDir, '..', '..', '..', 'etc', 'passwd.json');
        assert(!fs.existsSync(dangerousPath), 'sanitization must prevent path traversal');
        // And the checkpoint must still be retrievable using the original (un-sanitized) missionId
        const sneakyCkpt = store.getLatestCheckpoint(sneaky);
        assert(sneakyCkpt !== null, 'sanitized checkpoint must round-trip with the original missionId key');
        console.log('✅ scenario 3: overwrite + sanitization');
    }
    finally {
        if (prev === undefined)
            delete process.env.OPENLIFE_STATE_DIR;
        else
            process.env.OPENLIFE_STATE_DIR = prev;
        rmTmp(tmpRoot);
    }
}
// ─────────────────────────────────────────────────────────────
// Driver
// ─────────────────────────────────────────────────────────────
function main() {
    console.log('🧪 test_mission_checkpoint — Story 6.2 regression suite');
    scenarioRoundTrip();
    scenarioResumeAndClear();
    scenarioOverwriteAndSanitize();
    console.log('');
    console.log('TEST_MISSION_CHECKPOINT_OK');
}
try {
    main();
}
catch (err) {
    console.error('❌ test_mission_checkpoint FAILED:', err instanceof Error ? err.message : err);
    process.exit(1);
}
