mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 12:22:14 +03:00
* feat(homolog): scaffolding da suíte de homologação E2E (deps + npm run homolog) * feat(homolog): L0 avaliador de paridade de deploy (TDD) * feat(homolog): L1a ciclo de vida de API key efêmera (login admin -> create -> revoke) * feat(homolog): L1b suite httpYac de API (models, chat, auth de management, health) * feat(homolog): L1c checker SSE de streaming real (TDD no parser) * feat(homolog): L2 smoke de providers reais via promptfoo gerado do catálogo * feat(homolog): L4a Playwright homolog config + login storageState * feat(homolog): L4b smoke de todas as rotas do dashboard (descoberta via fs) * feat(homolog): L4c fluxo criar/revogar API key pela UI * fix(homolog): resiliencia real-environment — stream:false no smoke promptfoo, retry de socket keep-alive, key efemera com sufixo unico * feat(homolog): L5 orquestrador npm run homolog + relatorio CTRF unificado * docs(homolog): guia de operacao da suite + fragment de changelog + allowlist env-doc-sync * fix(homolog): paraleliza o sweep de rotas do dashboard (fullyParallel + 8 workers) * fix(homolog): isola outputs crus em homolog-report/raw para nao quebrar o ctrf merge * fix(homolog): outputDir absoluto do reporter CTRF da UI (path relativo escapava do worktree) * chore(quality): allowlist the 5 homolog-suite devDependencies (ctrf-io trio, httpyac, promptfoo) after registry verification * chore(quality): register the homolog Playwright suite as a test-discovery collector (run.mjs -> tests/homolog/ui)
66 lines
2.3 KiB
JavaScript
66 lines
2.3 KiB
JavaScript
// Cookie confirmado em src/app/api/auth/login/route.ts (cookieStore.set("auth_token", ...))
|
|
// e em src/shared/utils/apiAuth.ts (isDashboardSessionAuthenticated lê "auth_token").
|
|
const TOKEN_COOKIE = "auth_token";
|
|
|
|
export function extractJwtCookie(setCookies) {
|
|
for (const c of setCookies || []) {
|
|
const m = c.match(new RegExp(`^(${TOKEN_COOKIE}=[^;]+)`));
|
|
if (m) return m[1];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function extractApiKey(body) {
|
|
if (!body?.key || !body?.id) throw new Error("POST /api/keys sem key/id no corpo");
|
|
return { key: body.key, id: body.id };
|
|
}
|
|
|
|
// fetch com 1 retry para erros de socket (keep-alive reciclado pelo servidor
|
|
// entre requests espaçados derruba o 1º write com EPIPE/other side closed).
|
|
async function fetchRetry(url, init, retries = 1) {
|
|
try {
|
|
return await fetch(url, init);
|
|
} catch (err) {
|
|
if (retries > 0) {
|
|
await new Promise((r) => setTimeout(r, 1_000));
|
|
return fetchRetry(url, init, retries - 1);
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/** Login admin → cria API key efêmera. Retorna {key, id, cookie, revoke()}. */
|
|
export async function createEphemeralKey(baseUrl, password) {
|
|
const login = await fetchRetry(`${baseUrl}/api/auth/login`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ password }),
|
|
});
|
|
if (!login.ok) throw new Error(`login falhou: HTTP ${login.status}`);
|
|
const cookie = extractJwtCookie(login.headers.getSetCookie());
|
|
if (!cookie) throw new Error("login sem cookie de sessão");
|
|
|
|
// sufixo único por run: dois runs paralelos (ou um cleanup por nome) nunca colidem
|
|
const name = `homolog-${new Date().toISOString().slice(0, 10)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
const create = await fetchRetry(`${baseUrl}/api/keys`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", cookie },
|
|
body: JSON.stringify({ name }),
|
|
});
|
|
if (!create.ok) throw new Error(`criação de key falhou: HTTP ${create.status}`);
|
|
const { key, id } = extractApiKey(await create.json());
|
|
|
|
return {
|
|
key,
|
|
id,
|
|
cookie,
|
|
async revoke() {
|
|
const del = await fetchRetry(`${baseUrl}/api/keys/${id}`, {
|
|
method: "DELETE",
|
|
headers: { cookie },
|
|
});
|
|
if (!del.ok) throw new Error(`revogação da key ${id} falhou: HTTP ${del.status}`);
|
|
},
|
|
};
|
|
}
|