/**
* Git-push scratch sites, sweep and metering against a running dev server.
*
* SITES_E2E_URL=http://localhost:3000 bun run e2e/sites-scratch-git.e2e.ts
*/
import { createScratchRepo, d1, DOMAIN, gitPush, HTTP_OK, log, runCron, send, waitForSite, WORKER } from "./helpers";
const HTTP_NOT_FOUND = 404;
const HTTP_GONE = 410;
let failures = 0;
function check(name: string, ok: boolean, detail = ""): void {
log(`${ok ? "ok " : "FAIL"} ${name}${ok || !detail ? "" : ` -- ${detail}`}`);
if (!ok) failures += 1;
}
const scratch = await createScratchRepo();
const host = new URL(scratch.url).hostname;
const siteId = host.split("--")[0];
check(
"POST /api/v1/scratch returns a remote, push token and claim token",
scratch.pushToken.startsWith("gitfor_scratch_") &&
scratch.claimToken.startsWith("gitfor_claim_") &&
scratch.remote.endsWith(".git"),
);
const marker = `git-${Date.now()}`;
const pushed = await gitPush(scratch, { "index.html": `
${marker}
`, "_worker.js": WORKER });
check("git push with the scratch token succeeds", pushed.code === 0, pushed.output);
const live = await waitForSite(host, marker);
check("the push deploys to the scratch host", live.body.includes(marker), `${live.status} ${live.body.slice(0, 200)}`);
const previewMarker = `preview-${Date.now()}`;
const branchPush = await gitPush(scratch, { "index.html": `${previewMarker}
` }, "feature");
check("pushing another branch succeeds", branchPush.code === 0, branchPush.output);
const preview = await waitForSite(`feature--${host.replace(`.${DOMAIN}`, "")}.${DOMAIN}`, previewMarker);
check(
"each branch push deploys a preview on its own host",
preview.body.includes(previewMarker),
`${preview.status} ${preview.body.slice(0, 200)}`,
);
const wrong = await gitPush({ ...scratch, pushToken: "gitfor_scratch_wrong" }, { "index.html": "x" });
check("a wrong push token is refused", wrong.code !== 0);
const tag = await gitPush(scratch, { "index.html": "tag" }, "main", "HEAD:refs/tags/v1");
check("scratch repos refuse tag pushes", tag.code !== 0 && tag.output.includes("branch pushes only"), tag.output);
await send(host, "/");
await Bun.sleep(1500);
const usage = await d1<{ requests: number; bytes_out: number }>(
"SELECT requests, bytes_out FROM site_usage WHERE site_id = ?",
[siteId],
);
check(
"site requests and bytes are metered",
(usage[0]?.requests ?? 0) > 0 && (usage[0]?.bytes_out ?? 0) > 0,
JSON.stringify(usage),
);
const fetched = await send(host, "/echo");
check("dynamic scratch sites still run", fetched.status === HTTP_OK, `${fetched.status}`);
const doomed = await createScratchRepo();
const doomedHost = new URL(doomed.url).hostname;
const doomedId = doomedHost.split("--")[0];
await gitPush(doomed, { "index.html": "doomed
" });
await waitForSite(doomedHost, "doomed");
await d1("UPDATE sites SET expires_at = 1 WHERE id = ?", [doomedId]);
await d1("UPDATE repos SET created_at = 1 WHERE name = ? AND owner_id = 'sitesscratchaccount0'", [doomedId]);
await runCron();
await Bun.sleep(3000);
await runCron();
await Bun.sleep(5000);
const remaining = await d1<{ id: string }>("SELECT key AS id FROM scope_locators WHERE kind = 'site' AND key = ?", [
doomedId,
]);
const repos = await d1<{ id: string }>(
"SELECT r.id FROM repos r JOIN accounts a ON a.id = r.owner_id WHERE a.name = 'scratch' AND r.name = ?",
[doomedId],
);
check("the sweep deletes an expired scratch site", remaining.length === 0, JSON.stringify(remaining));
check("the sweep deletes its scratch repository", repos.length === 0, JSON.stringify(repos));
const gone = await send(doomedHost, "/");
check(
"an expired scratch host stops serving",
gone.status === HTTP_NOT_FOUND || gone.status === HTTP_GONE,
`${gone.status}`,
);
log(`\nscratch repo: ${scratch.repo} site: ${scratch.url} claim: ${scratch.claimUrl}`);
log(failures ? `\n${failures} check(s) failed` : "\nall checks passed");
process.exit(failures ? 1 : 0);