Skip to content

test(connectors/google_mail,google_calendar): phase 14 audit cluster D2 (mapper edge case + 401 insufficient scope + fixture activation)#314

Merged
ozzy-3 merged 1 commit into
mainfrom
test/phase-14-audit-cluster-d2-mapper-edge-401-fixture
May 31, 2026
Merged

test(connectors/google_mail,google_calendar): phase 14 audit cluster D2 (mapper edge case + 401 insufficient scope + fixture activation)#314
ozzy-3 merged 1 commit into
mainfrom
test/phase-14-audit-cluster-d2-mapper-edge-401-fixture

Conversation

@ozzy-3
Copy link
Copy Markdown
Contributor

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

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.jsontest_events_single_fixture_normalises_into_raw_calendar_event + test_normaliser_preserves_time_zone_field_on_raw_payload (G-6)
    • events_recurring_with_override.jsontest_events_recurring_with_override_fixture_yields_master_plus_override
    • sync_token_gone.jsontest_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

  • uv run pytest tests/unit/connectors/google_calendar/ tests/unit/connectors/google_mail/ → 116 passed
  • uv run pytest tests/unit/connectors/test_mapper_symmetry.py tests/unit/connectors/google_auth/ → 37 passed (no regression)
  • uv run pytest tests/unit/ (with --all-extras) → 2384 passed
  • uv run pytest tests/integration/ -k "google_mail or google_calendar" → 9 passed
  • uv run ruff check . && uv run ruff format --check . → clean
  • 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
  • uv run mypy src/opshub/connectors/google_calendar/ src/opshub/connectors/google_mail/ → no issues

…D2 (mapper edge case + 401 insufficient scope + fixture activation)

Phase 14 audit cluster D2 — pins test coverage gaps and adds an
actionable error path for the OQ6 re-consent scenario surfaced by
the read-only audit (4 perspectives, parallel).

Mapper edge cases (G-4 / G-5 / G-7):
- Calendar mapper: RDATE / EXDATE / EXRULE preservation in body
  (`recurrence: tuple[str, ...]` may carry multiple components, not
  just RRULE); complex RRULE (BYDAY multi-day + UNTIL + COUNT) pin
- Calendar mapper: 1-attendee body shape (`Attendees:\nalice@...`,
  the boundary between empty and multi-attendee)
- Calendar mapper: kanji / emoji / control-char preservation in
  description + location (text-only family, no sanitisation per
  ADR-0010 §Phase 14 改訂 (k))
- Gmail mapper: kanji / emoji / control-char preservation; pins
  that `_decode_gmail_body`'s `errors="replace"` is on the UTF-8
  boundary only — decoded control chars and U+FFFD round-trip

Client edge cases (G-6 / G-9):
- Calendar client: `start.timeZone` / `end.timeZone` retained on
  `RawCalendarEvent.raw` (no dedicated dataclass field — Phase 14
  G4 deliberate design, mapper consumes `dateTime` offset directly)
- Both clients: 401 `insufficient_scope` detection (JSON body
  `error_subtype` or `WWW-Authenticate` header) → `GoogleAuthError`
  with actionable re-auth hint (`opshub connector auth set
  google_workspace`). Phase 14 G2 OQ6 scenario coverage. Non-
  insufficient-scope 401s still raise `ConnectorFailedError`.

Fixture activation (F-1):
- Calendar `test_client.py`: `_fixture(name)` helper mirrors the
  Gmail-side pattern. `events_single.json` /
  `events_recurring_with_override.json` / `sync_token_gone.json`
  now back live tests (previously unreferenced).
- `tests/fixtures/google_calendar/README.md` documents the fixture
  set + the inline-JSON / fixture split rationale (matches Phase 14
  plan §7.5 listing: `single / recurring + override / 410 GONE`).
- 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
  `test_normaliser_handles_all_day_events`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ozzy-3 ozzy-3 merged commit 01abd29 into main May 31, 2026
3 checks passed
@ozzy-3 ozzy-3 deleted the test/phase-14-audit-cluster-d2-mapper-edge-401-fixture branch May 31, 2026 17:18
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 D2: mapper edge case + 401 insufficient scope + fixture 活用

1 participant