## ADDED Requirements ### Requirement: Layer 0 dep-graph isolation The `@suaveplan/testing-integration-minio` package's `dependencies` MUST be exactly `@suaveplan/testing-integration` (`workspace:^`) and `testcontainers` (`catalog:integration`). No other `@suaveplan/*` runtime dep is permitted. `devDependencies` MAY include `@suaveplan/testing` for the package's own tests. #### Scenario: Runtime closure clean - **WHEN** `bunx turbo ls --filter=@suaveplan/testing-integration-minio...` is run - **THEN** the only `@suaveplan/*` package in the runtime closure is `@suaveplan/testing-integration` ### Requirement: Coexistence with Garage S3 fixture The MinIO fixture MUST exist alongside `@suaveplan/testing-integration-garage-s3` rather than replacing it. The two fixtures expose deliberately different surfaces: Garage masks several AWS-S3 quirks (admin-port health, layout-assignment init), while MinIO mirrors AWS-S3 surface much more closely. Tests that need AWS-API parity MUST target the MinIO fixture; tests covering the Garage init dance MUST target the Garage fixture. #### Scenario: Both fixtures coexist - **WHEN** a test environment registers both `MinioFixture` and `GarageS3Fixture` - **THEN** both fixtures start in parallel with no shared port or env-var conflicts ### Requirement: Image-pinning policy The default image MUST be a pinned `minio/minio:RELEASE.` tag (latest stable RELEASE at implementation time). The `constants/index.ts` file MUST carry a `# TODO: replace with sha256:` comment until the digest is recorded. #### Scenario: Pinned RELEASE tag - **WHEN** the constants file is read - **THEN** the image string starts with `minio/minio:RELEASE.` AND a sha256 TODO line is present ### Requirement: Env-bridge passthrough The fixture MUST consult `MINIO_S3_ENDPOINT`, `MINIO_S3_ACCESS_KEY_ID`, `MINIO_S3_SECRET_ACCESS_KEY`, `MINIO_S3_REGION`, and `MINIO_S3_BUCKET`. When the first four are present and non-empty, `start()` MUST skip container creation and return a handle whose config points at the live MinIO. `MINIO_S3_BUCKET` is optional in passthrough mode. #### Scenario: Live MinIO present - **WHEN** the four required env vars are set - **THEN** `start()` returns without invoking `containerFactory` AND the returned config's `endpoint` equals `MINIO_S3_ENDPOINT` #### Scenario: Partial env triggers container - **WHEN** `MINIO_S3_ENDPOINT` is set but `MINIO_S3_SECRET_ACCESS_KEY` is empty - **THEN** `start()` falls back to container mode ### Requirement: Healthcheck The fixture MUST poll `HTTP GET /minio/health/live` and treat a `200` response as ready. The default `healthTimeoutMs` MUST be `60000`. Callers MAY override. #### Scenario: Healthcheck succeeds - **WHEN** `/minio/health/live` returns 200 - **THEN** `start()` resolves #### Scenario: Healthcheck times out - **WHEN** the endpoint never returns 200 within `healthTimeoutMs` - **THEN** `start()` rejects with code `INTEGRATION.MINIO.HEALTHCHECK_TIMEOUT` ### Requirement: Random keys per run In container mode, when `accessKeyId`/`secretAccessKey` are not supplied, the fixture MUST generate cryptographically random keys for each run (≥ 20 chars, alphanumeric) and pass them as `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` env vars to the container. #### Scenario: Random keys - **WHEN** the fixture starts twice without explicit keys - **THEN** the two runs use different `accessKeyId` values ### Requirement: Bucket pre-seeding When `bucket` is supplied, the fixture MUST create the bucket via the MinIO admin API after the healthcheck passes, using the injectable `fetchFn`. The fixture MUST NOT resolve `start()` until the bucket exists (verified via `HEAD /` returning `200`). #### Scenario: Bucket created - **WHEN** `start({ bucket: 'app-uploads' })` resolves - **THEN** `HEAD /app-uploads` returns 200 against the returned endpoint ### Requirement: Config shape The exported `MinioConfig` interface MUST include `accessKeyId`, `secretAccessKey`, `region`, optional `bucket?: string`, optional `console?: boolean`, and the resolved `endpoint`. A sibling `minio-fixture.schemas.ts` MUST export a Zod `MinioConfigSchema` that validates the same shape. #### Scenario: Schema parity - **WHEN** `MinioConfigSchema.safeParse` runs against a `MinioConfig` value - **THEN** valid configs parse OK AND missing required fields fail with `ZodError` ### Requirement: Error-code prefix All errors thrown from the fixture MUST carry a `code` matching `^INTEGRATION\.MINIO\.[A-Z_]+$`. #### Scenario: Coded error - **WHEN** any fixture method throws - **THEN** `code` starts with `INTEGRATION.MINIO.` ### Requirement: Injectable fakes The fixture constructor MUST accept `containerFactory?: () => GenericContainer` and `fetchFn?: typeof fetch`. Unit tests MUST drive 100% Istanbul branch coverage without Docker or network. #### Scenario: Fake injection - **WHEN** the fixture is constructed with both fakes - **THEN** real Docker / real `fetch` are never called during the test ### Requirement: Plugin scenarios — AWS-S3 parity The fixture MUST support, end-to-end against a real container with an AWS S3 SDK client (`@aws-sdk/client-s3`), the following flows in its smoke test: #### Scenario: PutObject and GetObject round-trip - **WHEN** an SDK client puts an object then gets it back - **THEN** the retrieved body bytes equal the put body bytes AND the `ETag` header is present #### Scenario: Presigned GET URL works - **WHEN** a presigned GET URL is created via `@aws-sdk/s3-request-presigner` and fetched directly with no SDK signing - **THEN** the response is `200` with the object body #### Scenario: Multipart upload works - **WHEN** an object larger than the part-size threshold (≥ 5 MB) is uploaded via `CreateMultipartUpload` + `UploadPart` + `CompleteMultipartUpload` - **THEN** the assembled object's body equals the concatenated source AND `HeadObject` reports the full content length #### Scenario: Bucket policy attach - **WHEN** a `PutBucketPolicy` request attaches a public-read policy - **THEN** `GetBucketPolicy` returns the same JSON document