Skip to content

test(connectors/google_mail,google_calendar): phase 14 audit cluster D1 (integration gaps + rotation + scope ext)#313

Merged
ozzy-3 merged 1 commit into
mainfrom
test/phase14-audit-cluster-d1-integration-gaps
May 31, 2026
Merged

test(connectors/google_mail,google_calendar): phase 14 audit cluster D1 (integration gaps + rotation + scope ext)#313
ozzy-3 merged 1 commit into
mainfrom
test/phase14-audit-cluster-d1-integration-gaps

Conversation

@ozzy-3
Copy link
Copy Markdown
Contributor

@ozzy-3 ozzy-3 commented May 31, 2026

Summary

Phase 14 audit cluster D1 (#308) — closes 3 integration test coverage gaps surfaced against the Phase 13 lifecycle baseline.

  • G-1 (light): auth -> client -> cursor -> projection wire round-trip for Gmail and Calendar via httpx.MockTransport hermetic handlers (test_phase14_sync_round_trip_via_mock_httpx_gmail / _calendar). The unit layer only mocks at the client class boundary; this lifts the contract pin to the wire.
  • G-2: 3-connector rotation continuation (test_phase14_rotation_propagates_to_all_three_connectors) — sync1 -> rotation -> sync2 x 3 for Drive + Gmail + Calendar, mirroring the Phase 13 lifecycle step 7 shape. Asserts no re-bootstrap on any connector after a simulated refresh-token rotation; the Drive get_start_page_token / Gmail get_profile_history_id / Calendar fetch_events_window counters stay pinned at 1 across the 2-sync window.
  • G-10: scope-extension survival pin (test_phase14_phase13_google_workspace_unaffected_by_scope_extension) — runs GoogleWorkspaceConnector end-to-end under the widened 3-scope DEFAULT_SCOPES and asserts the Drive cursor / projection round-trip is bit-for-bit unaffected. Includes a literal DEFAULT_SCOPES == [drive, gmail, calendar] pin alongside.

Design judgement

Option A (chosen): lifecycle 1-file consolidation. The Phase 14 G5 closeout decision was to consolidate Phase 14 integration coverage into one lifecycle file (lifecycle = projection + MCP read surface + mapper symmetry + write-back guard); fragmenting it now would re-introduce the shape the G5 audit collapsed. The new test functions read naturally next to the existing projection / write-back guards.

Option B (3 separate files per the original phase-14-plan.md §7.2 listing) was rejected — the contract layers (auth / cursor / projection / MCP read surface) reuse the Phase 13 shape verbatim, so per-connector round-trip pins read better when co-located with the projection / write-back guards.

docs/phase-14-plan.md §7.2 updated to reflect the consolidated structure with explicit pointers to each new test function.

Test plan

  • uv run pytest tests/integration/test_phase14_google_mail_calendar_lifecycle.py — 9/9 pass (4 new tests added on top of 5 pre-existing G5 lifecycle pins)
  • uv run pytest tests/integration/ — 166 pass, 2 skipped (real-Box / real-OneDrive contract tests)
  • uv run ruff check + uv run ruff format --check + uv run pyright on the touched test file — clean
  • markdownlint-cli2 docs/phase-14-plan.md — clean
  • hermeticity: every httpx call routes through httpx.MockTransport; no real Google endpoint reached

Closes #308

🤖 Generated with Claude Code

…D1 (integration gaps + rotation + scope ext)

Closes Phase 14 audit cluster D1 (#308) integration test coverage gaps:

- G-1 (light): adds `auth -> client -> cursor -> projection` round-trip
  pins for Gmail and Calendar via `httpx.MockTransport` hermetic
  handlers so the wire-shape contract is observed end-to-end (unit
  layer only mocked at the client class boundary).
- G-2: adds 3-connector rotation continuation pin
  (`sync1 -> rotation -> sync2` x 3 for Drive + Gmail + Calendar)
  mirroring the Phase 13 lifecycle step 7 shape; asserts no
  re-bootstrap on any of the three connectors after a simulated
  refresh-token rotation.
- G-10: adds scope-extension survival pin — runs the Phase 13
  `GoogleWorkspaceConnector` end-to-end under the widened 3-scope
  `DEFAULT_SCOPES` and asserts the Drive cursor / projection round-trip
  is bit-for-bit unaffected.

Design judgement: Option A (lifecycle 1-file consolidation) per the
G5 closeout decision. `docs/phase-14-plan.md` §7.2 updated to reflect
the consolidated structure (was originally listing 3 separate files).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ozzy-3
Copy link
Copy Markdown
Contributor Author

ozzy-3 commented May 31, 2026

レビュー結果 (mode: quick, axes: correctness, security, conventions, testing, maintainability, documentation):

correctness

指摘事項なし。9/9 tests pass。アサーションは pre/post state を明示的に pin (rotation での call_count == 1 などの構造的不変条件含む)。

security

指摘事項なし。fixture の token / secret は全て合成値 (INITIAL_REFRESH_TOKEN / fake-cid / fake-secret)。_seed_refresh_token_secret は store を per-test に閉じ込め、レアル keyring backend に書き込まない (refresh slot 以外は real_set_secret に delegate; テストは refresh slot しか触らないため実害ゼロ)。

conventions

指摘事項なし。Conventional Commits 形式 (test(scope): ...)、branch 名 test/phase14-audit-cluster-d1-integration-gaps も規約通り。

testing

指摘事項なし。

  • 4 新規 test 関数全てが hermetic (httpx.MockTransport で network call ゼロ)
  • 既存 5 テストと parity (signature / fixture 使い方が一致)
  • rotation pin は sync1 → rotation simulation → sync2 形を Drive / Gmail / Calendar 3 connector で再現し、get_start_page_token.call_count == 1 / get_profile_history_id.call_count == 1 / fetch_events_window.call_count == 1 でブートストラップ非再実行を pin
  • scope-extension pin は DEFAULT_SCOPES literal list 比較 + e2e round-trip の二段構え

[Info] tests/integration/test_phase14_google_mail_calendar_lifecycle.py:638-645
問題: _seed_refresh_token_secretsecrets_module.get_secret (オブジェクト経由) と "opshub.core.secrets.get_secret" (文字列経由) を二重に monkeypatch している
理由: 両者は同じ module attribute を指すため形式上冗長
提案: コメントで「load-bearing は string path」と明記しているため意図的な defense-in-depth として残す判断は妥当 (auth module の lazy import 順序を考えると安全側に倒す価値あり)。修正不要。

documentation

指摘事項なし。phase-14-plan.md §7.2 のリスト記述が実装と一致するよう更新済み (Option A 採用判断を引用付きで明記、3 ファイル分割計画から lifecycle 1 本構成に変更した理由 + 各新規 test 関数名へのポインタ含む)。

maintainability

指摘事項なし。helper 関数 (_install_httpx_mock_transport / _seed_refresh_token_secret) はこのファイル内に閉じ込め (将来の用途が出たら conftest.py に lift する旨コメント済み)。del isolated_env / del tmp_path の繰り返しは pytest fixture pattern として一般的。

サマリー

Critical: 0 件
Warning: 0 件
Info: 1 件 (修正不要判断)

by_axis:
correctness: C0 W0 I0
security: C0 W0 I0
conventions: C0 W0 I0
testing: C0 W0 I1
maintainability: C0 W0 I0
documentation: C0 W0 I0

判定: drive_loop exit criteria 全観点満たす (Critical / Warning ゼロ)。merge-ready。

@ozzy-3 ozzy-3 merged commit 75bfdc1 into main May 31, 2026
3 checks passed
@ozzy-3 ozzy-3 deleted the test/phase14-audit-cluster-d1-integration-gaps branch May 31, 2026 17:01
ozzy-3 added a commit that referenced this pull request May 31, 2026
…D2 (mapper edge case + 401 insufficient scope + fixture activation) (#314)

Closes #309.

Phase 14 audit cluster D2 — unit-level coverage gaps surfaced by the
read-only audit (4 perspectives, parallel). Cluster D1 (#313,
integration) and clusters B1 / B2 / C (docs) are merged; this PR
finishes the audit cycle.

## Summary

### Mapper edge cases (G-4 / G-5 / G-7)

- **G-4 Calendar mapper**: `recurrence: tuple[str, ...]` may carry RRULE
+ RDATE + EXDATE + EXRULE per RFC 5545 —
`test_master_event_body_includes_rdate_and_exdate` pins that every
component lands in body verbatim,
`test_master_event_body_preserves_complex_rrule_byday` pins that a
complex RRULE (`BYDAY=MO,WE,FR;UNTIL=...;COUNT=52`) round-trips without
normalisation.
- **G-5 Calendar mapper**: `test_body_handles_single_attendee` pins the
1-attendee boundary (`Attendees:\nalice@example.com` — no special-case
suppression vs the 0-attendee / multi-attendee paths).
- **G-7 Both mappers**: kanji / emoji / control-char preservation.
Gmail-side pins that `_decode_gmail_body`'s `errors="replace"` is on the
UTF-8 boundary only (decoded control chars + U+FFFD round-trip intact).
Calendar-side pins symmetric behaviour on description + location
(text-only family — no sanitisation per ADR-0010 §Phase 14 改訂 (k)).

### Client edge cases (G-6 / G-9)

- **G-6 Calendar client**: `start.timeZone` / `end.timeZone` are kept on
`RawCalendarEvent.raw` only (no dedicated dataclass field — Phase 14 G4
deliberate design because the mapper consumes `dateTime` with its offset
directly). Pinned so a future regression that drops or filters `raw`
trips before silently losing forensic context for Phase 15+ projection
work.
- **G-9 Both clients**: 401 `insufficient_scope` detection added. Code
change in both `client.py` files: a new
`_is_insufficient_scope(response)` helper inspects the
`WWW-Authenticate` header AND the JSON body (`{"error": "invalid_token",
"error_subtype": "insufficient_scope"}` OR
`error.status="PERMISSION_DENIED"` + `insufficient` in message). Matches
→ `GoogleAuthError` (subclass of `ConfigError`) with actionable message
naming the recovery command (`opshub connector auth set
google_workspace`). Non-`insufficient_scope` 401s still raise
`ConnectorFailedError` (`test_non_rate_limit_4xx_fails_fast` /
`test_other_4xx_fails_fast` continue to pin generic behaviour).

This is the Phase 14 G2 OQ6 scenario: operator carrying a Phase 13
drive-only refresh token forward into Phase 14 G3 / G4 without
re-running the paste-code flow. The actionable error short-circuits the
retry loop the operator would otherwise enter.

### Fixture activation (F-1)

- Calendar `test_client.py` now uses `_fixture(name)` helper mirroring
the Gmail-side pattern. The three previously-unreferenced fixtures all
back live tests:
- `events_single.json` →
`test_events_single_fixture_normalises_into_raw_calendar_event` +
`test_normaliser_preserves_time_zone_field_on_raw_payload` (G-6)
- `events_recurring_with_override.json` →
`test_events_recurring_with_override_fixture_yields_master_plus_override`
- `sync_token_gone.json` →
`test_fetch_events_delta_410_raises_sync_token_expired` (refactored from
inline JSON)
- New `tests/fixtures/google_calendar/README.md` documents the fixture
set, mirrors Gmail-side `README.md` structure, and explains the
inline-JSON / fixture split: pagination / retry / sentinel tests stay
inline because the fixture file shape would couple the pin to fixture
content drift (the Gmail-side `history_page.json` activation made the
same split).

### Phase 14 plan §7.5 alignment

§7.5 lists `single / recurring + override / 410 GONE` for calendar
fixtures. All three are now activated. No `events_all_day.json` fixture
added — §7.5 does not list it and the all-day shape is adequately pinned
by inline JSON in the existing `test_normaliser_handles_all_day_events`
test.

### Existing 401 test vs new insufficient_scope test — boundary

-
`tests/unit/connectors/google_mail/test_client.py::test_non_rate_limit_4xx_fails_fast`
— plain 401 (no `insufficient_scope` signal) → `ConnectorFailedError` (1
call, no retry)
-
`tests/unit/connectors/google_calendar/test_client.py::test_other_4xx_fails_fast`
— generic 4xx (400 here) → `ConnectorFailedError` (1 call, no retry)
- New `test_<gmail|calendar>_401_insufficient_scope_actionable_message`
+
`test_<gmail|calendar>_401_insufficient_scope_via_www_authenticate_header`
— 401 with the OAuth signal → `GoogleAuthError` (1 call, no retry,
actionable message)

## Test plan

- [x] `uv run pytest tests/unit/connectors/google_calendar/
tests/unit/connectors/google_mail/` → 116 passed
- [x] `uv run pytest tests/unit/connectors/test_mapper_symmetry.py
tests/unit/connectors/google_auth/` → 37 passed (no regression)
- [x] `uv run pytest tests/unit/` (with `--all-extras`) → 2384 passed
- [x] `uv run pytest tests/integration/ -k "google_mail or
google_calendar"` → 9 passed
- [x] `uv run ruff check . && uv run ruff format --check .` → clean
- [x] `uv run pyright src/opshub/connectors/google_calendar/
src/opshub/connectors/google_mail/
tests/unit/connectors/google_calendar/
tests/unit/connectors/google_mail/` → 0 errors
- [x] `uv run mypy src/opshub/connectors/google_calendar/
src/opshub/connectors/google_mail/` → no issues

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 14 audit cluster D1: integration test gap (rotation e2e + Phase 13 round-trip + scope 拡張 round-trip)

1 participant