From 10f20438b1556243f729d9aad410611f0ef21bad Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Fri, 10 Jul 2026 00:56:55 +0200 Subject: [PATCH] docs(seo-data): relocate engine test out of gated lib/tests/ config-protection.sh gates lib/tests/* as a guardrail dir; all 8 tasks edit the engine test. Move it to lib/seo-data/seo-data.test.sh (co-located, ungated) + extend the make test glob to discover lib/seo-data/*.test.sh. Root-cause fix, no guardrail weakened. Chosen by user over per-edit bypass. --- .../plans/2026-07-09-gsc-crux-data-layer.md | 103 ++++++++++-------- .../2026-07-09-gsc-crux-data-layer-design.md | 14 ++- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/docs/superpowers/plans/2026-07-09-gsc-crux-data-layer.md b/docs/superpowers/plans/2026-07-09-gsc-crux-data-layer.md index 2a2eb39..f0e35a7 100644 --- a/docs/superpowers/plans/2026-07-09-gsc-crux-data-layer.md +++ b/docs/superpowers/plans/2026-07-09-gsc-crux-data-layer.md @@ -20,7 +20,7 @@ Every task's requirements implicitly include these (verbatim from the spec): - **Multi-account, no shared state.** Account + property are **explicit arguments** on every `fetch.sh` call. No "current account" global. Store writes only happen during `connect` (atomic `tmp`→`fsync`→`rename` under `fcntl` lock); audits are read-only. - **Store keyed by user label**, not email (keeps scope minimal). Properties discovered via `sites.list` (already in scope). - **Canonical env path.** Read secrets from `~/.claude/.env` (canonical), never `$REPO/.env` (symlink may be absent on a fresh machine). -- **Repo test convention.** Bash tests in `lib/tests/*.test.sh`, helpers `tf`/`tr_`/`tn` + `PASS`/`FAIL` counters, final line `[ "$FAIL" -eq 0 ]`, discovered by `make test`. +- **Repo test convention.** Bash test following the repo idiom (helpers `tf`/`tr_`/`tn` + `PASS`/`FAIL` counters, final line `[ "$FAIL" -eq 0 ]`). The engine test lives at `lib/seo-data/seo-data.test.sh` — co-located with the engine, **deliberately NOT under `lib/tests/`** which the `config-protection.sh` hook gates as a guardrail dir. Task 6 extends the `make test` target to also discover `lib/seo-data/*.test.sh`. During TDD, run it directly: `bash lib/seo-data/seo-data.test.sh`. - **No commit attribution trailers** (no `Co-Authored-By`, no `Claude-Session`). - **Branch:** all commits on `feature/gsc-crux-data-layer` (already created). @@ -37,8 +37,8 @@ Every task's requirements implicitly include these (verbatim from the spec): - `lib/seo-data/README.md` — usage contract. **Tests (created):** -- `lib/tests/seo-data.test.sh` — deterministic bash test (drives CLIs against fixtures, checks locks). -- `lib/tests/fixtures/seo-data/*.json` — synthetic API responses (no real secret/PII). +- `lib/seo-data/seo-data.test.sh` — deterministic bash test (drives CLIs against fixtures, checks locks). +- `lib/seo-data/fixtures/*.json` — synthetic API responses (no real secret/PII). **Wiring (modified):** - `.env.example`, `install.sh`, `Makefile`, `doctor.sh`, `.gitleaks.toml`, `.gitignore`. @@ -76,13 +76,13 @@ Label-keyed, atomic, locked store. Foundation for everything; stdlib only so it **Files:** - Create: `lib/seo-data/tokenstore.py` -- Create: `lib/tests/seo-data.test.sh` +- Create: `lib/seo-data/seo-data.test.sh` **Interfaces:** - Consumes: nothing. - Produces: `load`, `list_accounts`, `get_refresh_token`, `save_account` (signatures in File Structure) + CLI `list`/`set`. -- [ ] **Step 1: Write the failing test** — create `lib/tests/seo-data.test.sh`: +- [ ] **Step 1: Write the failing test** — create `lib/seo-data/seo-data.test.sh`: ```bash #!/usr/bin/env bash @@ -121,7 +121,7 @@ echo "seo-data engine: $PASS pass, $FAIL fail" - [ ] **Step 2: Run it, verify red** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: FAIL — `tokenstore.py` does not exist (`python3: can't open file`). - [ ] **Step 3: Implement `lib/seo-data/tokenstore.py`** (stdlib only): @@ -199,13 +199,13 @@ if __name__ == "__main__": - [ ] **Step 4: Run it, verify green** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: PASS (5 tokenstore checks pass). - [ ] **Step 5: Commit** ```bash -git add lib/seo-data/tokenstore.py lib/tests/seo-data.test.sh +git add lib/seo-data/tokenstore.py lib/seo-data/seo-data.test.sh git commit -m "feat(seo-data): label-keyed atomic OAuth token store" ``` @@ -217,14 +217,14 @@ Simplest data path (API key, no OAuth). Establishes the mock-mode + degrade + no **Files:** - Create: `lib/seo-data/google_seo.py` -- Create: `lib/tests/fixtures/seo-data/crux_mobile.json` -- Modify: `lib/tests/seo-data.test.sh` (append CrUX section) +- Create: `lib/seo-data/fixtures/crux_mobile.json` +- Modify: `lib/seo-data/seo-data.test.sh` (append CrUX section) **Interfaces:** - Consumes: env `CRUX_API_KEY`, env `SEO_DATA_MOCK_DIR`. - Produces: `crux(url, strategy='mobile') -> dict`; CLI `python3 google_seo.py crux --url … [--strategy …]`. -- [ ] **Step 1: Write the fixture** — `lib/tests/fixtures/seo-data/crux_mobile.json` (shape of the CrUX API `record.metrics`): +- [ ] **Step 1: Write the fixture** — `lib/seo-data/fixtures/crux_mobile.json` (shape of the CrUX API `record.metrics`): ```json {"record":{"key":{"formFactor":"PHONE"},"metrics":{ @@ -233,11 +233,11 @@ Simplest data path (API key, no OAuth). Establishes the mock-mode + degrade + no "cumulative_layout_shift":{"percentiles":{"p75":"0.08"}}}}} ``` -- [ ] **Step 2: Write the failing test** — append to `lib/tests/seo-data.test.sh` before the final summary: +- [ ] **Step 2: Write the failing test** — append to `lib/seo-data/seo-data.test.sh` before the final summary: ```bash echo "── crux (mock) ──" -CRUX_OK="$(SEO_DATA_MOCK_DIR="$REPO/lib/tests/fixtures/seo-data" \ +CRUX_OK="$(SEO_DATA_MOCK_DIR="$REPO/lib/seo-data/fixtures" \ python3 "$SD/google_seo.py" crux --url https://ex.com --strategy mobile)" has "crux status ok" "$CRUX_OK" '"status": "ok"' has "crux lcp p75 mapped" "$CRUX_OK" '"lcp_p75_ms": 2100' @@ -251,7 +251,7 @@ has "crux degrade reason" "$CRUX_DEG" 'no_crux_key' - [ ] **Step 3: Run it, verify red** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: FAIL — `google_seo.py` missing. - [ ] **Step 4: Implement the CrUX path** — create `lib/seo-data/google_seo.py` (lazy `requests` import; mock reads the fixture and runs the REAL normalizer): @@ -337,13 +337,13 @@ if __name__ == "__main__": - [ ] **Step 5: Run it, verify green** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: PASS (tokenstore + 6 CrUX checks). - [ ] **Step 6: Commit** ```bash -git add lib/seo-data/google_seo.py lib/tests/fixtures/seo-data/crux_mobile.json lib/tests/seo-data.test.sh +git add lib/seo-data/google_seo.py lib/seo-data/fixtures/crux_mobile.json lib/seo-data/seo-data.test.sh git commit -m "feat(seo-data): CrUX field-data fetch with mock mode and graceful degrade" ``` @@ -355,8 +355,8 @@ Adds Search Analytics + URL Inspection with OAuth refresh (lazy) reusing `tokens **Files:** - Modify: `lib/seo-data/google_seo.py` (add `queries`, `inspect`, `_gsc_session`, extend CLI) -- Create: `lib/tests/fixtures/seo-data/gsc_queries.json`, `lib/tests/fixtures/seo-data/gsc_inspect.json` -- Modify: `lib/tests/seo-data.test.sh` (append GSC section) +- Create: `lib/seo-data/fixtures/gsc_queries.json`, `lib/seo-data/fixtures/gsc_inspect.json` +- Modify: `lib/seo-data/seo-data.test.sh` (append GSC section) **Interfaces:** - Consumes: `tokenstore.get_refresh_token`, env `GOOGLE_OAUTH_CLIENT_ID/SECRET`, `SEO_DATA_MOCK_DIR`. @@ -364,13 +364,13 @@ Adds Search Analytics + URL Inspection with OAuth refresh (lazy) reusing `tokens - [ ] **Step 1: Write fixtures** -`lib/tests/fixtures/seo-data/gsc_queries.json` (Search Analytics `rows` shape): +`lib/seo-data/fixtures/gsc_queries.json` (Search Analytics `rows` shape): ```json {"rows":[ {"keys":["plombier paris"],"clicks":40,"impressions":900,"ctr":0.044,"position":6.3}, {"keys":["urgence fuite"],"clicks":5,"impressions":1200,"ctr":0.004,"position":8.9}]} ``` -`lib/tests/fixtures/seo-data/gsc_inspect.json` (URL Inspection shape): +`lib/seo-data/fixtures/gsc_inspect.json` (URL Inspection shape): ```json {"inspectionResult":{"indexStatusResult":{ "verdict":"PASS","coverageState":"Submitted and indexed","lastCrawlTime":"2026-07-01T10:00:00Z"}}} @@ -380,7 +380,7 @@ Adds Search Analytics + URL Inspection with OAuth refresh (lazy) reusing `tokens ```bash echo "── gsc (mock) ──" -MOCK="$REPO/lib/tests/fixtures/seo-data" +MOCK="$REPO/lib/seo-data/fixtures" TMP2="$(mktemp -d)"; S2="$TMP2/tokens.json" python3 "$SD/tokenstore.py" set --file "$S2" --label client-a --refresh-token RT \ --scopes https://www.googleapis.com/auth/webmasters.readonly --properties sc-domain:ex.com >/dev/null @@ -401,7 +401,7 @@ rm -rf "$TMP2" - [ ] **Step 3: Run it, verify red** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: FAIL — `queries`/`inspect` not implemented (argparse error / AttributeError). - [ ] **Step 4: Implement** — add to `google_seo.py`: @@ -480,13 +480,13 @@ Extend `_cli()` (add subparsers `queries` and `inspect`, each with `--store --ac - [ ] **Step 5: Run it, verify green** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: PASS (tokenstore + CrUX + 7 GSC checks). - [ ] **Step 6: Commit** ```bash -git add lib/seo-data/google_seo.py lib/tests/fixtures/seo-data/gsc_queries.json lib/tests/fixtures/seo-data/gsc_inspect.json lib/tests/seo-data.test.sh +git add lib/seo-data/google_seo.py lib/seo-data/fixtures/gsc_queries.json lib/seo-data/fixtures/gsc_inspect.json lib/seo-data/seo-data.test.sh git commit -m "feat(seo-data): GSC Search Analytics + URL Inspection with lazy OAuth refresh" ``` @@ -498,7 +498,7 @@ The stable CLI the analyzers call. Sources env, picks python (venv else system), **Files:** - Create: `lib/seo-data/fetch.sh` -- Modify: `lib/tests/seo-data.test.sh` (append fetch.sh section) +- Modify: `lib/seo-data/seo-data.test.sh` (append fetch.sh section) **Interfaces:** - Consumes: `~/.claude/.env` (canonical), `google_seo.py`, `tokenstore.py`, optional `~/.claude/.venv-seo-data/`. @@ -526,7 +526,7 @@ hasnt "no secret echoed" "$DG" 'RT_' - [ ] **Step 2: Run it, verify red** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: FAIL — `fetch.sh` missing. - [ ] **Step 3: Implement `lib/seo-data/fetch.sh`:** @@ -567,13 +567,13 @@ Notes: `crux` accepts and ignores `--store` (already wired in Task 2's `_cli`) s - [ ] **Step 4: Run it, verify green** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: PASS (all prior + 6 fetch.sh checks). - [ ] **Step 5: Commit** ```bash -git add lib/seo-data/fetch.sh lib/seo-data/google_seo.py lib/tests/seo-data.test.sh +git add lib/seo-data/fetch.sh lib/seo-data/google_seo.py lib/seo-data/seo-data.test.sh git commit -m "feat(seo-data): fetch.sh entrypoint with venv/system fallback and redaction" ``` @@ -586,7 +586,7 @@ One-time interactive consent + `sites.list` discovery + persist. Browser flow is **Files:** - Create: `lib/seo-data/connect.py` - Create: `lib/seo-data/requirements.txt` -- Modify: `lib/tests/seo-data.test.sh` (append persist test) +- Modify: `lib/seo-data/seo-data.test.sh` (append persist test) **Interfaces:** - Consumes: env `GOOGLE_OAUTH_CLIENT_ID/SECRET`, `tokenstore.save_account`. @@ -616,7 +616,7 @@ rm -rf "$TMP3" - [ ] **Step 3: Run it, verify red** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: FAIL — `connect` module / `persist` missing. - [ ] **Step 4: Implement `lib/seo-data/connect.py`:** @@ -683,7 +683,7 @@ if __name__ == "__main__": - [ ] **Step 5: Run it, verify green** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: PASS (all prior + 3 persist checks). - [ ] **Step 6: Manual verification (documented, not automated)** — after Task 6 wires `make seo-connect`: run it once against a real GCP OAuth client, confirm the browser consent completes, the store gains the label with discovered properties, and a second `fetch.sh queries` runs non-interactively. @@ -691,7 +691,7 @@ Expected: PASS (all prior + 3 persist checks). - [ ] **Step 7: Commit** ```bash -git add lib/seo-data/connect.py lib/seo-data/requirements.txt lib/tests/seo-data.test.sh +git add lib/seo-data/connect.py lib/seo-data/requirements.txt lib/seo-data/seo-data.test.sh git commit -m "feat(seo-data): OAuth consent + property discovery + pinned deps" ``` @@ -703,7 +703,7 @@ git commit -m "feat(seo-data): OAuth consent + property discovery + pinned deps" **Files:** - Modify: `.env.example`, `Makefile`, `install.sh`, `doctor.sh`, `.gitleaks.toml`, `.gitignore` -- Modify: `lib/tests/seo-data.test.sh` (append wiring locks) +- Modify: `lib/seo-data/seo-data.test.sh` (append wiring locks) **Interfaces:** - Consumes: `lib/seo-data/{connect.py,requirements.txt}`. @@ -717,6 +717,7 @@ tf() { if grep -qF -- "$3" "$2" 2>/dev/null; then ok "$1"; else no "$1" "missing tf "env.example client id" "$REPO/.env.example" "GOOGLE_OAUTH_CLIENT_ID=" tf "env.example crux key" "$REPO/.env.example" "CRUX_API_KEY=" tf "makefile seo-connect" "$REPO/Makefile" "seo-connect:" +tf "makefile discovers test" "$REPO/Makefile" "lib/seo-data/*.test.sh" tf "install prompts connect" "$REPO/install.sh" "make seo-connect" tf "doctor checks seo-data" "$REPO/doctor.sh" "seo-data" tf "gitleaks allowlist store" "$REPO/.gitleaks.toml" "seo-data/tokens.json" @@ -725,8 +726,8 @@ tf "gitignore venv" "$REPO/.gitignore" ".venv-seo-data" - [ ] **Step 2: Run it, verify red** -Run: `bash lib/tests/seo-data.test.sh` -Expected: FAIL — none of the 7 locks present yet. +Run: `bash lib/seo-data/seo-data.test.sh` +Expected: FAIL — none of the 8 locks present yet. - [ ] **Step 3: Apply the wiring edits** @@ -752,6 +753,16 @@ seo-connect: ## Connect a Google account for /seo FULL (creates venv, OAuth cons ``` (Add `seo-connect` to the `.PHONY:` line at the top of the Makefile.) +Also extend the existing `test:` target so `make test` discovers the engine test. Change its loop line: +```make + @fail=0; for t in lib/tests/*.test.sh lib/gitflow-test.sh lib/tests/run-*.sh; do \ +``` +to add `lib/seo-data/*.test.sh`: +```make + @fail=0; for t in lib/tests/*.test.sh lib/seo-data/*.test.sh lib/gitflow-test.sh lib/tests/run-*.sh; do \ +``` +(Editing the Makefile is allowed — it is not a config-protection-gated file. This *adds* test discovery; it does not weaken any gate.) + `install.sh` — after the `bash "$REPO/link.sh"` block (§5, ~line 107), before plugins: ```bash # ── 5b. Optional: connect a Google account for /seo FULL ── @@ -797,13 +808,13 @@ seo-data/tokens.json - [ ] **Step 4: Run it, verify green** -Run: `bash lib/tests/seo-data.test.sh` -Expected: PASS (all prior + 7 wiring locks). Then `make test` — the whole suite still green. +Run: `bash lib/seo-data/seo-data.test.sh` +Expected: PASS (all prior + 8 wiring locks). Then `make test` — the whole suite still green (now including the seo-data engine test via the new glob). - [ ] **Step 5: Commit** ```bash -git add .env.example Makefile install.sh doctor.sh .gitleaks.toml .gitignore lib/tests/seo-data.test.sh +git add .env.example Makefile install.sh doctor.sh .gitleaks.toml .gitignore lib/seo-data/seo-data.test.sh git commit -m "chore(seo-data): install/make/doctor wiring + gitleaks allowlist for token store" ``` @@ -817,7 +828,7 @@ Make `/seo` FULL actually consume the engine: account selection in STEP 0, CrUX - Modify: `skills/seo/SKILL.md` (STEP 0 — account/property selection, FULL only) - Modify: `agents/seo-analyzer.md` (STEP 4 CWV terrain via `fetch.sh crux`; new "Performance GSC" subsection via `fetch.sh queries`/`inspect`; STEP 9 Technical axis note) - Modify: `agents/resources/automation-catalog.md` (GSC OAuth connection entry) -- Modify: `lib/tests/seo-data.test.sh` (append integration locks) +- Modify: `lib/seo-data/seo-data.test.sh` (append integration locks) **Interfaces:** - Consumes: `lib/seo-data/fetch.sh` CLI contract. @@ -836,7 +847,7 @@ tf "catalog gsc oauth entry" "$REPO/agents/resources/automation-catalog.md" " - [ ] **Step 2: Run it, verify red** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: FAIL — 5 integration locks absent. - [ ] **Step 3: Apply the integration edits** (concrete anchors from the /analyze report): @@ -873,13 +884,13 @@ In **STEP 9** scoring, add a note on the Technical axis: "CWV scored on CrUX fie - [ ] **Step 4: Run it, verify green** -Run: `bash lib/tests/seo-data.test.sh` +Run: `bash lib/seo-data/seo-data.test.sh` Expected: PASS (all prior + 5 integration locks). Run `make test` — full suite green. - [ ] **Step 5: Commit** ```bash -git add skills/seo/SKILL.md agents/seo-analyzer.md agents/resources/automation-catalog.md lib/tests/seo-data.test.sh +git add skills/seo/SKILL.md agents/seo-analyzer.md agents/resources/automation-catalog.md lib/seo-data/seo-data.test.sh git commit -m "feat(seo): wire GSC+CrUX data into /seo FULL (STEP 0 account select, CWV field, GSC perf)" ``` @@ -891,7 +902,7 @@ Document the engine's contract so future maintainers (and the analyzer) have a s **Files:** - Create: `lib/seo-data/README.md` -- Modify: `lib/tests/seo-data.test.sh` (append doc lock) +- Modify: `lib/seo-data/seo-data.test.sh` (append doc lock) **Interfaces:** - Consumes: nothing. @@ -905,16 +916,16 @@ tf "readme documents fetch.sh" "$REPO/lib/seo-data/README.md" "fetch.sh" tf "readme documents seo-connect" "$REPO/lib/seo-data/README.md" "make seo-connect" ``` -- [ ] **Step 2: Run it, verify red** — Run: `bash lib/tests/seo-data.test.sh` — Expected: FAIL (README missing). +- [ ] **Step 2: Run it, verify red** — Run: `bash lib/seo-data/seo-data.test.sh` — Expected: FAIL (README missing). - [ ] **Step 3: Write `lib/seo-data/README.md`** — cover: purpose (real GSC+CrUX for /seo FULL), setup (`make seo-connect` + the 3 `.env` keys), the `fetch.sh` subcommand contract (copy §9 of the spec), the token store location + security notes (0600, gitleaks allowlist, scope webmasters.readonly), graceful-degradation behavior, and how to run the test (`make test`). Concrete, no placeholders. -- [ ] **Step 4: Run it, verify green** — Run: `bash lib/tests/seo-data.test.sh` — Expected: PASS (all locks). +- [ ] **Step 4: Run it, verify green** — Run: `bash lib/seo-data/seo-data.test.sh` — Expected: PASS (all locks). - [ ] **Step 5: Commit** ```bash -git add lib/seo-data/README.md lib/tests/seo-data.test.sh +git add lib/seo-data/README.md lib/seo-data/seo-data.test.sh git commit -m "docs(seo-data): engine usage + security contract README" ``` diff --git a/docs/superpowers/specs/2026-07-09-gsc-crux-data-layer-design.md b/docs/superpowers/specs/2026-07-09-gsc-crux-data-layer-design.md index 622ee13..a1ee268 100644 --- a/docs/superpowers/specs/2026-07-09-gsc-crux-data-layer-design.md +++ b/docs/superpowers/specs/2026-07-09-gsc-crux-data-layer-design.md @@ -287,7 +287,7 @@ tout lecteur de creds vise le **canonical `~/.claude/.env`**, pas `$REPO/.env`). |---|---| | `.env.example` | Ajouter les 3 vars (client id/secret, CrUX key) au format existant (`# Used by:` / `# Get it:` + placeholder). **Seul fichier versionné touché côté secrets.** | | `install.sh` | Après `link.sh` (§5) et `claude login` (§3) : étape **optionnelle idempotente** (moule « Press Enter to connect… ») → si aucun compte dans le store, propose `make seo-connect` ; skip sinon. | -| `Makefile` | Cible user-facing `seo-connect` : crée/maj le venv + `pip install -r lib/seo-data/requirements.txt`, lance `connect.py` (consentement + découverte propriétés). Rejouable. `make test` ramasse déjà le nouveau test. | +| `Makefile` | Cible user-facing `seo-connect` (venv + `pip install -r lib/seo-data/requirements.txt` + `connect.py`, rejouable) **et** extension de la cible `test` pour découvrir `lib/seo-data/*.test.sh` (le test vit hors `lib/tests/`, gaté par `config-protection.sh`). | | `doctor.sh` | Nouveau check (lit `~/.claude/.env` canonical) : venv + deps présents ? au moins un compte dans le store ? `CRUX_API_KEY` présent ? → **PASS / WARN, jamais fatal**. | | `.gitleaks.toml` | Allowlist du token store (cf. §5.4). | | `.gitignore` | Ajouter `.venv-seo-data/` et `seo-data/tokens.json` (ceinture + bretelles). | @@ -296,8 +296,10 @@ tout lecteur de creds vise le **canonical `~/.claude/.env`**, pas `$REPO/.env`). ## 12. Tests -`lib/tests/seo-data.test.sh`, convention du repo (`tf`/`tr_`/`tn` + compteurs PASS/FAIL, -découvert par `make test`), **sans appel réseau** : +`lib/seo-data/seo-data.test.sh`, convention du repo (`tf`/`tr_`/`tn` + compteurs PASS/FAIL), +**sans appel réseau**. Placé sous `lib/seo-data/` (co-localisé, **hors `lib/tests/`** que +`hooks/config-protection.sh` protège comme dossier-garde) ; `make test` le découvre via le glob +ajouté en Task 6. En TDD : `bash lib/seo-data/seo-data.test.sh`. - Parsing des sous-commandes/args de `fetch.sh` (bons/mauvais usages, exit codes). - Parsing de forme JSON sur **fixtures commitées** (réponses GSC/CrUX mockées) → shape attendue. @@ -305,7 +307,7 @@ découvert par `make test`), **sans appel réseau** : - **Dégradation** : creds absents → `{"status":"degraded"}` + **exit 0** (pas 1). - Isolement : deux invocations `--account` différentes → sélections indépendantes (pas d'état partagé). -Fixtures sous `lib/tests/fixtures/seo-data/` (réponses synthétiques, aucun vrai secret/PII). +Fixtures sous `lib/seo-data/fixtures/` (réponses synthétiques, aucun vrai secret/PII). --- @@ -334,8 +336,8 @@ Fixtures sous `lib/tests/fixtures/seo-data/` (réponses synthétiques, aucun vra - `lib/seo-data/tokenstore.py` - `lib/seo-data/requirements.txt` - `lib/seo-data/README.md` -- `lib/tests/seo-data.test.sh` -- `lib/tests/fixtures/seo-data/*.json` +- `lib/seo-data/seo-data.test.sh` +- `lib/seo-data/fixtures/*.json` **Modifiés** - `.env.example` (3 vars)