"use strict";
/**
 * Regression test for the v-15-f/g daemon-crash bug:
 *
 *   POST /tracks/abort while a track is suspended in __ccweb_ask_user
 *   used to bubble the reject up through bridge.requestInput →
 *   builtin → train.runFile → runner.run → registry.start's `void
 *   runner.run(...).then(...)`. Because that .then had no .catch, the
 *   rejection escaped as `unhandledRejection`, and the global fatal
 *   handler in logger.ts called process.exit(1) — ccweb daemon died.
 *
 * This script:
 *   1. Installs an unhandledRejection listener that flips a flag.
 *   2. Starts a registry-driven track that blocks on __ccweb_ask_user.
 *   3. Calls registry.abort() once the ask_user push fires.
 *   4. Verifies:
 *      - flag stays false (no escaping promise)
 *      - registry.getState() reports status === 'cancelled'
 *      - track_run_complete broadcast has ok=false, errorType=UserCancelError
 *
 * Run:  cd backend && npx ts-node src/tracks/__tests__/verify-track-cancel.ts
 */
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/promises"));
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const __1 = require("..");
let failed = 0;
function check(name, cond, msg) {
    if (cond) {
        console.log(`  ✓ ${name}`);
    }
    else {
        failed++;
        console.error(`  ✗ ${name}${msg ? ': ' + msg : ''}`);
    }
}
async function writeWf(p, w) {
    await fs.writeFile(p, JSON.stringify(w, null, 2), 'utf8');
}
async function testAbortDuringAskUser() {
    console.log('\n=== abort during pending ask_user (regression v-15-f/g) ===');
    let unhandled = null;
    const onUnhandled = (reason) => {
        unhandled = reason;
    };
    process.on('unhandledRejection', onUnhandled);
    try {
        const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ccweb-cancel-'));
        const projectDir = path.join(tmpDir, 'proj');
        await fs.mkdir(path.join(projectDir, '.ccweb'), { recursive: true });
        const wfPath = path.join(projectDir, '.ccweb', 'workflow_data.json');
        await writeWf(wfPath, { constants: {}, variables: {}, task_progress: [] });
        (0, __1.saveTrack)(projectDir, 'pause.tr', `func main() -> any {
  let r = __ccweb_ask_user({
    fields: [
      { key: "decision", label: "go?", type: "enum", variants: ["yes","no"] }
    ]
  })
  return r.decision
}
export main
`);
        const trackAbs = path.join(projectDir, '.ccweb', 'tracks', 'pause.tr');
        const broadcastEvents = [];
        const registry = (0, __1.createTrackRegistry)({
            getProjectFolder: (id) => (id === 'proj' ? projectDir : null),
            injectIntoPty: () => { },
            broadcast: (_projectId, msg) => {
                broadcastEvents.push({ msg });
            },
        });
        const start = await registry.start('proj', trackAbs, 'pause.tr', []);
        check('registry.start ok', start.ok);
        // Wait until the bridge actually pushed the ask_user event — that's
        // the signal that bridge.requestInput is parked on the Promise and
        // the abort path will trip the signalHandler reject.
        const startTs = Date.now();
        while (broadcastEvents.find((e) => e.msg.type === 'track_ask_user') === undefined &&
            Date.now() - startTs < 8000) {
            await new Promise((r) => setTimeout(r, 20));
        }
        check('track_ask_user broadcast fired before abort', broadcastEvents.find((e) => e.msg.type === 'track_ask_user') !== undefined);
        check('ask_user is pending before abort', registry.getPendingAskUser('proj') !== null);
        // The actual abort — this is what used to crash ccweb.
        const aborted = registry.abort('proj');
        check('registry.abort returned true', aborted);
        // Give the promise chain enough ticks to settle and the fatal
        // handler enough chance to fire if it would have.
        await new Promise((r) => setTimeout(r, 200));
        check('no unhandledRejection escaped (would have killed ccweb)', unhandled === null, unhandled instanceof Error ? unhandled.message : String(unhandled));
        const finalState = registry.getState('proj');
        check('final state non-null', finalState !== null);
        check('final status is cancelled (not failed/running)', finalState?.status === 'cancelled', `got: ${finalState?.status}`);
        check('final error.errorType is UserCancelError', finalState?.error?.errorType === 'UserCancelError', `got: ${finalState?.error?.errorType}`);
        check('isRunning false after abort', !registry.isRunning('proj'));
        const completes = broadcastEvents.filter((e) => e.msg.type === 'track_run_complete');
        check('exactly one track_run_complete broadcast', completes.length === 1, `got ${completes.length}`);
        if (completes.length === 1) {
            const c = completes[0].msg;
            check('track_run_complete ok=false', c.ok === false);
            check('track_run_complete errorType=UserCancelError', c.error?.errorType === 'UserCancelError', `got: ${c.error?.errorType}`);
        }
        await fs.rm(tmpDir, { recursive: true, force: true });
    }
    finally {
        process.off('unhandledRejection', onUnhandled);
    }
}
async function main() {
    console.log('=== verify-track-cancel ===');
    await testAbortDuringAskUser();
    console.log(`\n${failed === 0 ? '✅ ALL CANCEL CHECKS PASSED' : `❌ ${failed} CHECK(S) FAILED`}`);
    process.exit(failed === 0 ? 0 : 1);
}
main().catch((e) => {
    console.error('verify-track-cancel crashed:', e);
    process.exit(2);
});
//# sourceMappingURL=verify-track-cancel.js.map