diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index efdfd1a16..4e18b68e8 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -23,10 +23,13 @@ jobs: - name: Start MinIO run: docker compose up -d minio + # the bucket, not the health endpoint: MinIO answers health/live while it is still + # bootstrapping, so a run whose bucket never got created would only surface much later + # as a 500 in the one test that uploads an image - name: Wait for MinIO to be ready run: | for i in {1..30}; do - if curl -sf http://127.0.0.1:9000/minio/health/live; then + if curl -sf -o /dev/null http://127.0.0.1:9000/sendou/; then echo "MinIO is ready" exit 0 fi diff --git a/compose.yaml b/compose.yaml index 561853582..3d364e23f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -12,9 +12,9 @@ services: isAlive() { curl -sf http://127.0.0.1:9000/minio/health/live; } # check if Minio is alive minio $$0 "$$@" --quiet & echo $$! > /tmp/minio.pid # start Minio in the background while ! isAlive; do sleep 0.1; done # wait until Minio is alive - mc alias set minio http://127.0.0.1:9000 minio-user minio-password # setup Minio client - mc mb minio/sendou || true # create a test bucket - mc anonymous set public minio/sendou # make the test bucket public + until mc alias set minio http://127.0.0.1:9000 minio-user minio-password; do sleep 0.5; done # setup Minio client (health/live can answer before credentials are accepted) + until mc mb --ignore-existing minio/sendou; do sleep 0.5; done # create a test bucket + until mc anonymous set public minio/sendou; do sleep 0.5; done # make the test bucket public kill -s INT $$(cat /tmp/minio.pid) && rm /tmp/minio.pid # stop Minio while isAlive; do sleep 0.1; done # wait until Minio is stopped exec minio $$0 "$$@" # start Minio in the foreground diff --git a/e2e/global-setup.ts b/e2e/global-setup.ts index 8ac434fcd..ddab6e3a9 100644 --- a/e2e/global-setup.ts +++ b/e2e/global-setup.ts @@ -7,6 +7,9 @@ import { E2E_BASE_PORT, e2eWorkerPort } from "./helpers/playwright"; const DEBUG = process.env.E2E_DEBUG === "true"; const SERVER_PROCESSES: ChildProcess[] = []; const MINIO_MARKER_FILE = ".e2e-minio-started"; +const STORAGE_BUCKET = "sendou"; +/** Anonymously listable only once the bucket exists and its public policy is set. */ +const MINIO_BUCKET_URL = `http://127.0.0.1:9000/${STORAGE_BUCKET}/`; const BUILD_MARKER_FILE = ".e2e-build-marker"; const BUILD_INPUTS = [ "app", @@ -21,16 +24,26 @@ declare global { var __E2E_SERVERS__: ChildProcess[]; } +/** + * Whether the image storage is usable, which takes more than MinIO answering its health check: + * the container bootstraps the bucket only after startup, and a run whose bucket never got created + * would otherwise fail deep inside the one test that uploads an image (`art.spec.ts`) with an + * opaque 500. + */ +async function isMinioBucketReady(): Promise { + try { + const response = await fetch(MINIO_BUCKET_URL); + return response.ok; + } catch { + return false; + } +} + async function waitForMinio(timeout = 60000): Promise { const start = Date.now(); while (Date.now() - start < timeout) { - try { - const response = await fetch("http://127.0.0.1:9000/minio/health/live"); - if (response.ok) { - return true; - } - } catch { - // MinIO not ready yet + if (await isMinioBucketReady()) { + return true; } await new Promise((resolve) => setTimeout(resolve, 1000)); } @@ -39,15 +52,10 @@ async function waitForMinio(timeout = 60000): Promise { async function ensureMinioRunning(): Promise { // Check if MinIO is already running - try { - const response = await fetch("http://127.0.0.1:9000/minio/health/live"); - if (response.ok) { - // biome-ignore lint/suspicious/noConsole: CLI script output - console.log("MinIO is already running"); - return false; - } - } catch { - // MinIO not running, we need to start it + if (await isMinioBucketReady()) { + // biome-ignore lint/suspicious/noConsole: CLI script output + console.log("MinIO is already running"); + return false; } // biome-ignore lint/suspicious/noConsole: CLI script output @@ -56,7 +64,9 @@ async function ensureMinioRunning(): Promise { const isReady = await waitForMinio(); if (!isReady) { - throw new Error("MinIO failed to start within timeout"); + throw new Error( + `MinIO did not become usable within timeout (${MINIO_BUCKET_URL} never answered OK). If MinIO is running, its "${STORAGE_BUCKET}" bucket is missing or not public — recreate the container with "docker compose up -d --force-recreate minio".`, + ); } // biome-ignore lint/suspicious/noConsole: CLI script output @@ -225,7 +235,7 @@ async function globalSetup(config: FullConfig) { STORAGE_ACCESS_KEY: "minio-user", STORAGE_SECRET: "minio-password", STORAGE_REGION: "us-east-1", - STORAGE_BUCKET: "sendou", + STORAGE_BUCKET, // no system messages to a shared skalop instance (see build env above) SKALOP_SYSTEM_MESSAGE_URL: "", SKALOP_TOKEN: "",