## ADDED Requirements ### Requirement: Layer 0 dep-graph isolation The `@suaveplan/testing-integration-mysql` 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-mysql...` is run - **THEN** the only `@suaveplan/*` package in the runtime closure is `@suaveplan/testing-integration` ### Requirement: Image-pinning policy The default image MUST be `mysql:8.4`. The `constants/index.ts` file MUST carry a `# TODO: replace with sha256:` comment until the digest is recorded. #### Scenario: Pinned tag - **WHEN** the constants file is read - **THEN** the image string equals `mysql:8.4` AND a sha256 TODO line is present ### Requirement: Env-bridge passthrough The fixture MUST consult `MYSQL_URL`, `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD`, and `MYSQL_ROOT_PASSWORD`. When all five are present and non-empty, `start()` MUST skip container creation and return a handle whose config points at the live MySQL. #### Scenario: Live MySQL present - **WHEN** all five env vars are set - **THEN** `start()` returns without invoking `containerFactory` AND the returned config's `url` equals `MYSQL_URL` #### Scenario: Partial env triggers container - **WHEN** `MYSQL_URL` is set but `MYSQL_ROOT_PASSWORD` is empty - **THEN** `start()` falls back to container mode ### Requirement: Healthcheck The fixture MUST run `mysqladmin ping -h localhost -p$MYSQL_ROOT_PASSWORD` in-container via the injectable `execFn` and treat exit code `0` with stdout containing `mysqld is alive` as ready. The default `healthTimeoutMs` MUST be `90000` (initial DB init can be slow on cold image pulls). Callers MAY override. #### Scenario: Healthcheck succeeds - **WHEN** in-container `mysqladmin ping` returns exit code 0 with `mysqld is alive` - **THEN** `start()` resolves #### Scenario: Healthcheck times out - **WHEN** the ping never succeeds within `healthTimeoutMs` - **THEN** `start()` rejects with code `INTEGRATION.MYSQL.HEALTHCHECK_TIMEOUT` ### Requirement: Charset and collation overrides The fixture MUST default to `charset: 'utf8mb4'` and `collation: 'utf8mb4_0900_ai_ci'` and MUST pass `--character-set-server=` and `--collation-server=` to `mysqld` when overrides are supplied via config. #### Scenario: UTF-8mb4 default - **WHEN** the fixture is started with no charset override - **THEN** `SHOW VARIABLES LIKE 'character_set_server'` returns `utf8mb4` #### Scenario: Charset override applied - **WHEN** the fixture is started with `charset: 'utf8'` - **THEN** `SHOW VARIABLES LIKE 'character_set_server'` returns `utf8` ### Requirement: Config shape The exported `MySqlConfig` interface MUST include `database`, `user`, `password`, `rootPassword`, optional `charset?: string`, optional `collation?: string`, and the resolved `url`. A sibling `mysql-fixture.schemas.ts` MUST export a Zod `MySqlConfigSchema` that validates the same shape. #### Scenario: Schema parity - **WHEN** `MySqlConfigSchema.safeParse` is run against a `MySqlConfig` 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\.MYSQL\.[A-Z_]+$`. #### Scenario: Coded error - **WHEN** any fixture method throws - **THEN** `code` starts with `INTEGRATION.MYSQL.` ### Requirement: Injectable fakes The fixture constructor MUST accept `containerFactory?: () => GenericContainer` and `execFn?: (cmd: string[]) => Promise<{ stdout: string; stderr: string; exitCode: number }>`. Unit tests MUST drive 100% Istanbul branch coverage without Docker. #### Scenario: Fake injection - **WHEN** the fixture is constructed with both fakes - **THEN** real Docker is never invoked during the test ### Requirement: Plugin scenarios The fixture MUST support, end-to-end against a real container, the following flows in its smoke test: #### Scenario: Transaction round-trip - **WHEN** a transaction inserts rows and commits - **THEN** the rows are visible to a subsequent connection AND a rolled-back transaction's rows are not visible #### Scenario: DDL post-start - **WHEN** `CREATE TABLE`, `ALTER TABLE ADD COLUMN`, and `DROP TABLE` statements run after `start()` resolves - **THEN** each statement succeeds AND `INFORMATION_SCHEMA.TABLES` reflects the change