/**
 * @specverse/engines/scenario-harness
 *
 * Framework-agnostic HTTP driver for API-level behavioral scenario tests.
 * Generated scenario tests import these verbs to drive a realized backend's
 * CURVED REST endpoints. Route conventions come from
 * `@specverse/types/spec-rules` (`deriveBasePath` + the canonical evolve
 * path), so the harness stays in lockstep with what realize emits — there
 * are no hard-coded route strings here.
 *
 * No frontend dependency: works against any realized backend (incl.
 * backend-only manifests). Uses global `fetch` (Node 18+).
 */
import { deriveBasePath } from '@specverse/types/spec-rules';
/**
 * Create a client pointing at a running realized backend. Defaults to
 * `SPECVERSE_API_URL` then `http://127.0.0.1:3000`.
 */
export function scenarioClient(baseUrl) {
    const resolved = baseUrl || process.env.SPECVERSE_API_URL || 'http://127.0.0.1:3000';
    return { baseUrl: resolved.replace(/\/+$/, '') };
}
export class ScenarioHttpError extends Error {
    op;
    model;
    status;
    bodyText;
    constructor(op, model, status, bodyText) {
        super(`${op} ${model} failed: HTTP ${status} ${bodyText}`.trim());
        this.op = op;
        this.model = model;
        this.status = status;
        this.bodyText = bodyText;
        this.name = 'ScenarioHttpError';
    }
}
function resourceUrl(client, model, suffix = '') {
    return `${client.baseUrl}${deriveBasePath(model)}${suffix}`;
}
async function bodyText(res) {
    try {
        return (await res.text()).slice(0, 500);
    }
    catch {
        return '';
    }
}
/**
 * Realized create/retrieve handlers return the entity; some wrap it as
 * `{ data: {...} }`. Unwrap so callers reference top-level fields + `.id`.
 */
function unwrap(json) {
    if (json && typeof json === 'object' && 'data' in json && json.data && typeof json.data === 'object') {
        return json.data;
    }
    return json;
}
export async function create(client, model, data) {
    const res = await fetch(resourceUrl(client, model), {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(data),
    });
    if (!res.ok)
        throw new ScenarioHttpError('create', model, res.status, await bodyText(res));
    return unwrap(await res.json());
}
/** PATCH /api/<plural>/:id/evolve with `{ toState }` — matches the realized route. */
export async function evolve(client, model, id, toState) {
    const res = await fetch(resourceUrl(client, model, `/${id}/evolve`), {
        method: 'PATCH',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ toState }),
    });
    if (!res.ok)
        throw new ScenarioHttpError('evolve', model, res.status, await bodyText(res));
    return unwrap(await res.json());
}
export async function retrieve(client, model, id) {
    const res = await fetch(resourceUrl(client, model, `/${id}`), { method: 'GET' });
    if (!res.ok)
        throw new ScenarioHttpError('retrieve', model, res.status, await bodyText(res));
    return unwrap(await res.json());
}
export async function update(client, model, id, data) {
    const res = await fetch(resourceUrl(client, model, `/${id}`), {
        method: 'PUT',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(data),
    });
    if (!res.ok)
        throw new ScenarioHttpError('update', model, res.status, await bodyText(res));
    return unwrap(await res.json());
}
export async function remove(client, model, id) {
    const res = await fetch(resourceUrl(client, model, `/${id}`), { method: 'DELETE' });
    if (!res.ok)
        throw new ScenarioHttpError('delete', model, res.status, await bodyText(res));
}
/** Assert a resolved entity carries field === value (tolerant of `{data}` wrap). */
export function assertField(entity, field, value) {
    const actual = entity?.[field] ?? entity?.data?.[field];
    if (actual !== value) {
        throw new Error(`expected ${field} === ${JSON.stringify(value)}, got ${JSON.stringify(actual)}`);
    }
}
/** Assert the operation is rejected (rejects / throws). Throws if it succeeds. */
export async function expectRejected(p) {
    try {
        await p;
    }
    catch {
        return; // expected
    }
    throw new Error('expected the operation to be rejected, but it succeeded');
}
//# sourceMappingURL=index.js.map