"use strict";
// src/test_design_extractor.ts
// v1.15.0 — Phase A2 regression. Tests the TS wrapper around the bundled
// design-md-extractor skill. Real network + LLM extraction is NOT
// exercised here (that would need claude-cli or an OPENROUTER_API_KEY +
// internet). We assert:
//   1. URL validation — SSRF guards reject localhost/private IPs.
//   2. URL validation — well-formed public https:// URLs pass.
//   3. slugFromUrl — deterministic + safe slugs.
//   4. resolveProvider — honors user override.
//   5. resolveProvider — falls back to openrouter when key is set.
//   6. resolveProvider — returns null when nothing is available.
//   7. resolveSkillRoot — finds the bundled .catalog skill from cwd.
//   8. extract() — short-circuits on blocked URL without spawning child.
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 DesignMdExtractor_1 = require("./design/DesignMdExtractor");
function assert(cond, msg) {
    if (!cond)
        throw new Error(`assertion failed: ${msg}`);
}
function setEnv(key, value) {
    const prev = process.env[key];
    if (value === undefined)
        delete process.env[key];
    else
        process.env[key] = value;
    return () => { if (prev === undefined)
        delete process.env[key];
    else
        process.env[key] = prev; };
}
function scenarioUrlBlocklist() {
    const blocked = [
        'http://localhost/',
        'http://127.0.0.1:8080/',
        'http://10.0.0.1/',
        'http://192.168.1.1/',
        'http://172.16.0.1/',
        'http://169.254.169.254/latest/meta-data/', // AWS metadata
        'http://[::1]/',
        'http://example.local/',
        'file:///etc/passwd',
        'ftp://example.com/',
        'not-a-url',
    ];
    for (const url of blocked) {
        const r = DesignMdExtractor_1._internals.validateExtractUrl(url);
        assert(!r.ok, `should block ${url}`);
    }
    console.log(`✅ scenario 1: ${blocked.length} SSRF/protocol vectors blocked`);
}
function scenarioUrlAllowlist() {
    const allowed = [
        'https://stripe.com',
        'https://www.apple.com/',
        'https://example.org/path/page?q=1',
        'http://public-site.com/', // http is allowed (not just https)
    ];
    for (const url of allowed) {
        const r = DesignMdExtractor_1._internals.validateExtractUrl(url);
        assert(r.ok, `should accept ${url}, got reason=${r.reason}`);
    }
    console.log(`✅ scenario 2: ${allowed.length} public URLs accepted`);
}
function scenarioSlug() {
    assert(DesignMdExtractor_1._internals.slugFromUrl('https://stripe.com') === 'stripe.com', 'stripe slug');
    assert(DesignMdExtractor_1._internals.slugFromUrl('https://www.apple.com/') === 'apple.com', 'www stripped');
    assert(DesignMdExtractor_1._internals.slugFromUrl('https://github.com/user/repo') === 'github.com-user-repo', 'path joined');
    assert(/^url-[0-9a-f]+$/.test(DesignMdExtractor_1._internals.slugFromUrl('not-a-url')), 'malformed → hash fallback');
    console.log('✅ scenario 3: slugFromUrl deterministic + safe');
}
function scenarioResolveProviderOverride() {
    const r = DesignMdExtractor_1._internals.resolveProvider({ provider: 'openrouter' });
    assert(r?.provider === 'openrouter', 'user override honored');
    assert(r?.reason === 'user-override', 'reason recorded');
    console.log('✅ scenario 4: explicit provider override wins');
}
function scenarioResolveProviderOpenrouter() {
    const restore = setEnv('OPENROUTER_API_KEY', 'fake-key-for-test');
    try {
        // Force router preference to NOT be anthropic so we exercise the openrouter branch.
        // The router might return anthropic if claude-cli is present; in that case the
        // test scenario short-circuits to claude-cli and we accept either decision.
        const r = DesignMdExtractor_1._internals.resolveProvider({});
        assert(r !== null, 'should resolve when openrouter key set');
        assert(['claude-cli', 'openrouter'].includes(r.provider), `got ${r.provider}`);
    }
    finally {
        restore();
    }
    console.log('✅ scenario 5: openrouter key triggers fallback when no anthropic CLI');
}
function scenarioResolveProviderNone() {
    const restore = setEnv('OPENROUTER_API_KEY', '');
    try {
        // We can't easily force the router to return non-anthropic here; if anthropic
        // CLI is on the test machine the router will pick it. Tolerate both outcomes
        // — we assert resolveProvider doesn't crash and returns either null OR claude-cli.
        const r = DesignMdExtractor_1._internals.resolveProvider({});
        if (r !== null) {
            assert(r.provider === 'claude-cli', `expected null OR claude-cli, got ${r.provider}`);
        }
    }
    finally {
        restore();
    }
    console.log('✅ scenario 6: no key + router-anthropic-only path covered');
}
function scenarioSkillRootFromRepo() {
    // When this test is run from the repo (which it is, in test:all), the
    // bundled skill must be found at .catalog/skills/design-md-extractor/.
    const skillRoot = DesignMdExtractor_1._internals.resolveSkillRoot();
    if (skillRoot === null) {
        console.log('✓ scenario 7 skipped (running outside repo — no bundled skill)');
        return;
    }
    assert(fs.existsSync(path.join(skillRoot, 'run.cjs')), 'run.cjs must exist in skill root');
    assert(fs.existsSync(path.join(skillRoot, 'SKILL.md')), 'SKILL.md must exist in skill root');
    console.log(`✅ scenario 7: bundled skill resolved at ${skillRoot}`);
}
async function scenarioExtractShortCircuitOnBadUrl() {
    const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'extract-smoke-'));
    try {
        const extractor = new DesignMdExtractor_1.DesignMdExtractor(tmp);
        const result = await extractor.extract('http://localhost:9999/');
        assert(!result.ok, 'localhost should be blocked');
        assert(result.exitCode === -1, `expected exitCode -1, got ${result.exitCode}`);
        assert(result.errors?.[0].includes('url_blocked'), `expected url_blocked error, got ${result.errors?.[0]}`);
    }
    finally {
        try {
            fs.rmSync(tmp, { recursive: true, force: true });
        }
        catch { /* */ }
    }
    console.log('✅ scenario 8: extract() short-circuits on blocked URL without spawning child');
}
async function scenarioExtractNoProvider() {
    // To force "no provider", we ensure OPENROUTER_API_KEY is unset AND
    // we delete claude binary from PATH for this call. We can't realistically
    // delete claude from PATH, so instead we use a fresh tmp HOME that wipes
    // any provider-probe cache, and pre-write a fake probe with no CLIs
    // available. ModelTierRouter's pickPreferenceCli will then return non-anthropic
    // and resolveProvider falls back to openrouter; with no key, it returns null.
    const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'extract-home-'));
    const restores = [
        setEnv('HOME', tmpHome),
        setEnv('OPENROUTER_API_KEY', ''),
    ];
    // Seed the probe cache so ModelTierRouter doesn't actually probe.
    try {
        const probeDir = path.join(tmpHome, '.openlife');
        fs.mkdirSync(probeDir, { recursive: true });
        fs.writeFileSync(path.join(probeDir, 'provider-probe.json'), JSON.stringify({ ts: Date.now(), entries: [] }, null, 2));
        // Force-write a router.json that prefers nothing (so router returns null).
        fs.writeFileSync(path.join(probeDir, 'router.json'), JSON.stringify({ mode: 'cli-only', preference: [] }, null, 2));
        const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'extract-out-'));
        try {
            const extractor = new DesignMdExtractor_1.DesignMdExtractor(tmp);
            const result = await extractor.extract('https://example.com');
            // When router and openrouter are both unavailable, expect no_provider error.
            assert(!result.ok, 'no provider should fail');
            assert(result.exitCode === -2, `expected exitCode -2, got ${result.exitCode}`);
            assert(result.errors?.[0].includes('no_provider_available'), `expected no_provider_available, got ${result.errors?.[0]}`);
        }
        finally {
            try {
                fs.rmSync(tmp, { recursive: true, force: true });
            }
            catch { /* */ }
        }
    }
    finally {
        for (const r of restores)
            r();
        try {
            fs.rmSync(tmpHome, { recursive: true, force: true });
        }
        catch { /* */ }
    }
    console.log('✅ scenario 9: extract() with no provider returns actionable error');
}
(async () => {
    try {
        scenarioUrlBlocklist();
        scenarioUrlAllowlist();
        scenarioSlug();
        scenarioResolveProviderOverride();
        scenarioResolveProviderOpenrouter();
        scenarioResolveProviderNone();
        scenarioSkillRootFromRepo();
        await scenarioExtractShortCircuitOnBadUrl();
        await scenarioExtractNoProvider();
        console.log('TEST_DESIGN_EXTRACTOR_OK');
    }
    catch (err) {
        console.error('TEST_DESIGN_EXTRACTOR_FAIL', err);
        process.exit(1);
    }
})();
