"use strict";
/**
 * test_remote_publish.ts — Story 14.1 (v1.5)
 *
 * Asserts:
 *   1. RemotePublisher.isConfigured() reflects the env flag.
 *   2. Without OPENLIFE_REMOTE_PUBLISH_URL, publishAsset returns
 *      `remote_publish_not_configured` without making a network call.
 *   3. With a stubbed fetch and a valid PUT URL, publishAsset issues
 *      a single PUT carrying:
 *        - Content-Type: text/markdown
 *        - X-Asset-Sha256 / X-Asset-Kind / X-Asset-Id headers
 *        - Authorization: Bearer <token> when token env is set
 *        - The asset body
 *   4. Non-2xx response surfaces as `http_<status>`; never throws.
 *   5. fetch throwing surfaces as `fetch_error`.
 *   6. SquadCreator.publishWithRemote composes local publish + remote PUT
 *      and carries both results on the envelope.
 *   7. SHA mismatch (caller-provided != content-hash) is caught locally
 *      and returns `sha_mismatch_local` without fetch ever being called.
 */
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 crypto = __importStar(require("crypto"));
function assertTrue(cond, label) {
    if (!cond)
        throw new Error(`ASSERT_FAILED[${label}]`);
}
function mockFetch(captured, response) {
    return (async (input, init) => {
        const initObj = (init || {});
        const headers = {};
        if (initObj.headers && typeof initObj.headers === 'object') {
            for (const [k, v] of Object.entries(initObj.headers))
                headers[k] = v;
        }
        captured.calls += 1;
        captured.lastUrl = String(input);
        captured.lastInit = { method: initObj.method, headers, body: typeof initObj.body === 'string' ? initObj.body : undefined };
        const respHeaders = new Map();
        if (response.etag)
            respHeaders.set('etag', response.etag);
        return {
            status: response.status,
            headers: { get: (k) => respHeaders.get(k.toLowerCase()) ?? null },
            text: async () => response.body || '',
        };
    });
}
async function main() {
    const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'remote-pub-'));
    process.env.OPENLIFE_STATE_DIR = tmp;
    try {
        const { RemotePublisher } = require('./orchestrator/RemotePublisher');
        // ── Scenario 1 + 2: unconfigured returns stable code, no fetch ─────
        delete process.env.OPENLIFE_REMOTE_PUBLISH_URL;
        delete process.env.OPENLIFE_REMOTE_PUBLISH_TOKEN;
        const captured0 = { calls: 0 };
        const pub0 = new RemotePublisher({ fetchImpl: mockFetch(captured0, { status: 200 }) });
        assertTrue(!pub0.isConfigured(), '[14.1] unconfigured isConfigured=false');
        const assetFile = path.join(tmp, 'asset.md');
        fs.writeFileSync(assetFile, '# hi\n', 'utf-8');
        const sha = crypto.createHash('sha256').update('# hi\n').digest('hex');
        const r0 = await pub0.publishAsset('squad', 'demo', sha, assetFile);
        assertTrue(!r0.ok && r0.error === 'remote_publish_not_configured', `[14.1] unconfigured error code (got ${r0.error})`);
        assertTrue(captured0.calls === 0, '[14.1] unconfigured does not fetch');
        console.log('[14.1] unconfigured → stable error code, no fetch ✓');
        // ── Scenario 3: happy path with token ──────────────────────────────
        const captured1 = { calls: 0 };
        const pub1 = new RemotePublisher({
            baseUrl: 'https://registry.example/openlife',
            token: 'tk-test',
            fetchImpl: mockFetch(captured1, { status: 201, etag: 'W/"deadbeef"' }),
        });
        const r1 = await pub1.publishAsset('squad', 'demo', sha, assetFile);
        assertTrue(r1.ok && r1.status === 201, `[14.1] happy path status=201 (got ${r1.status})`);
        assertTrue(r1.etag === 'W/"deadbeef"', '[14.1] etag passthrough');
        assertTrue(captured1.calls === 1, '[14.1] exactly one PUT');
        assertTrue(captured1.lastInit?.method === 'PUT', `[14.1] verb is PUT (got ${captured1.lastInit?.method})`);
        assertTrue(captured1.lastUrl === `https://registry.example/openlife/squad/demo/${encodeURIComponent(sha)}.md`, `[14.1] URL shape correct (got ${captured1.lastUrl})`);
        assertTrue(captured1.lastInit?.headers?.['Content-Type'] === 'text/markdown', '[14.1] content-type set');
        assertTrue(captured1.lastInit?.headers?.['X-Asset-Sha256'] === sha, '[14.1] sha header set');
        assertTrue(captured1.lastInit?.headers?.['X-Asset-Kind'] === 'squad', '[14.1] kind header set');
        assertTrue(captured1.lastInit?.headers?.['X-Asset-Id'] === 'demo', '[14.1] id header set');
        assertTrue(captured1.lastInit?.headers?.['Authorization'] === 'Bearer tk-test', '[14.1] bearer auth set');
        assertTrue(captured1.lastInit?.body === '# hi\n', '[14.1] body is the asset contents');
        console.log('[14.1] happy path PUT request shape ✓');
        // ── Scenario 4: non-2xx surfaces as http_<status> ──────────────────
        const captured2 = { calls: 0 };
        const pub2 = new RemotePublisher({ baseUrl: 'https://r.example', fetchImpl: mockFetch(captured2, { status: 403 }) });
        const r2 = await pub2.publishAsset('skill', 'foo', sha, assetFile);
        assertTrue(!r2.ok && r2.error === 'http_403', `[14.1] 403 → http_403 (got ${r2.error})`);
        console.log('[14.1] non-2xx → http_<status> ✓');
        // ── Scenario 5: fetch throws → fetch_error ────────────────────────
        const throwingFetch = (async () => { throw new Error('network down'); });
        const pub3 = new RemotePublisher({ baseUrl: 'https://r.example', fetchImpl: throwingFetch });
        const r3 = await pub3.publishAsset('squad', 'foo', sha, assetFile);
        assertTrue(!r3.ok && r3.error === 'fetch_error', `[14.1] thrown fetch → fetch_error (got ${r3.error})`);
        console.log('[14.1] fetch throws → fetch_error, no crash ✓');
        // ── Scenario 7: SHA mismatch caught locally ────────────────────────
        const captured4 = { calls: 0 };
        const pub4 = new RemotePublisher({ baseUrl: 'https://r.example', fetchImpl: mockFetch(captured4, { status: 200 }) });
        const r4 = await pub4.publishAsset('squad', 'demo', '0'.repeat(64), assetFile);
        assertTrue(!r4.ok && r4.error === 'sha_mismatch_local', `[14.1] sha mismatch caught (got ${r4.error})`);
        assertTrue(captured4.calls === 0, '[14.1] sha mismatch → no fetch');
        console.log('[14.1] sha mismatch detected locally, no fetch ✓');
        // ── Scenario 6: SquadCreator.publishWithRemote composes both ───────
        // We stub the RemotePublisher require to inject a mock fetch.
        const pubPath = require.resolve('./orchestrator/RemotePublisher');
        const origPub = require.cache[pubPath];
        const captured5 = { calls: 0 };
        require.cache[pubPath] = {
            ...origPub,
            exports: {
                RemotePublisher: class {
                    async publishAsset(kind, id, sha256) {
                        captured5.calls += 1;
                        return { ok: true, url: `https://stub/${kind}/${id}/${sha256}.md`, status: 201 };
                    }
                },
            },
        };
        try {
            const { SquadCreator } = require('./orchestrator/SquadCreator');
            const sc = new SquadCreator({
                catalogRoot: path.join(tmp, '.catalog', 'squads'),
                templateRoot: path.resolve(__dirname, '..', 'dist-templates', 'squad-template'),
            });
            const seed = sc.create({
                id: 'compose-squad', name: 'Compose', description: 'demo', version: '0.1.0', status: 'draft',
                agents: [{ id: 'lead', name: 'Lead', role: 'lead' }],
            });
            assertTrue(seed.ok, '[14.1] seed squad created');
            const composed = await sc.publishWithRemote('compose-squad');
            assertTrue(composed.ok, '[14.1] local publish ok');
            assertTrue(composed.remote && composed.remote.ok, '[14.1] remote publish ok');
            assertTrue(captured5.calls === 1, '[14.1] composed call invokes RemotePublisher exactly once');
        }
        finally {
            if (origPub)
                require.cache[pubPath] = origPub;
            else
                delete require.cache[pubPath];
        }
        console.log('[14.1] SquadCreator.publishWithRemote composes local + remote ✓');
    }
    finally {
        delete process.env.OPENLIFE_STATE_DIR;
        delete process.env.OPENLIFE_REMOTE_PUBLISH_URL;
        delete process.env.OPENLIFE_REMOTE_PUBLISH_TOKEN;
        fs.rmSync(tmp, { recursive: true, force: true });
    }
    console.log('TEST_REMOTE_PUBLISH_OK');
}
main().catch((err) => {
    console.error('[remote-publish] FAILED:', err instanceof Error ? err.message : err);
    process.exit(1);
});
