Skip to content

test: lock management behaviour tests (#136) - #142

Merged
cheerc merged 1 commit into
mainfrom
test/wave3b-lock-management
Jun 23, 2026
Merged

test: lock management behaviour tests (#136)#142
cheerc merged 1 commit into
mainfrom
test/wave3b-lock-management

Conversation

@cheerc

@cheerc cheerc commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What

Test-only PR adding 44 new tests for the client-side tab locking system (zero production code changes).

#136 — Lock management behaviour tests

  • tests/lib/lockHelpers.js — DI extraction of lock management logic from JavaScript.html L742-795
  • tests/unit/lockManagement.test.js (44 tests):
    • acquireLock (8 tests): uncontested acquire, data format verification, same-tab re-acquire (timestamp update), rejection by active tab, stale lock breaking (>15s), exact threshold boundary, independent schedule locking, corrupt localStorage recovery
    • releaseLock (7 tests): owner release, non-owner rejection, falsy guards (null/undefined/empty string), non-existent schedule no-op, selective release (preserves other schedules)
    • refreshLockHeartbeat (6 tests): timestamp update, non-owner no-op, readOnly guard, falsy scheduleId guards, no-lock-entry no-op
    • Lock expiry (4 tests): stale threshold boundary, heartbeat preventing expiry, heartbeat lapse allowing expiry, threshold constant validation
    • Multi-tab concurrency (7 tests): mutual exclusion, cross-schedule independence, acquire-after-release handoff, three-tab competition, stale steal isolation, wrong-tab release protection
    • localStorage data format (8 tests): storage key validation, JSON structure, entry schema ({tabId, timestamp}), multi-schedule coexistence, clean state after full release, empty storage, corrupt JSON recovery
    • Edge cases (4 tests): special character scheduleId, numeric scheduleId, rapid acquire-release-acquire, external storage clear resilience
  • tests/unit/wiringContracts.test.js — module count 13 → 14

How

  • DI extraction pattern: pure logic extracted with injected dependencies (tabId, storage, nowFn)
  • Mock storage from tests/mocks/frontendMocks.js (createMockStorage)
  • Deterministic time control via injected nowFn for expiry/heartbeat tests
  • No production code modifications

Scope

Files changed Type
tests/lib/lockHelpers.js NEW
tests/unit/lockManagement.test.js NEW
tests/unit/wiringContracts.test.js MODIFIED (count 13→14)

Verification

./workflow.sh t6 → PASS (8/8)
  ✅ [t1] CSS Full Build
  ✅ [t2] ESLint frontend
  ✅ [t3] ESLint GAS backend
  ✅ [t4] Structure check
  ✅ [t5] Tailwind consistency
  ✅ [t7] PHP lint
  ✅ [t8] Vitest (1097 tests, 34 files)
  ✅ [t9] PHPUnit (60 tests)

Closes #136

- Add tests/lib/lockHelpers.js: DI extraction of lock management logic
  from JavaScript.html L742-795 (acquireLock, releaseLock,
  refreshLockHeartbeat, _getLocks, _saveLocks)
- Add tests/unit/lockManagement.test.js: 44 comprehensive tests covering
  acquireLock (success/fail/stale break/re-acquire), releaseLock
  (owner/non-owner/falsy guard), refreshLockHeartbeat (update/readOnly
  guard/no-op), lock expiry with heartbeat interaction, multi-tab
  concurrency (3 tabs, cross-schedule isolation), localStorage data
  format validation, edge cases (special chars, external clear, rapid
  cycle)
- Update tests/unit/wiringContracts.test.js: module count 13 → 14

Test-only, zero production code changes.
Follows spec.

Closes #136

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

Agend-Agent: cb-team-impl2
Agend-Task: t-20260623135419143399-97909-1
Agend-Branch: test/wave3b-lock-management
Agend-Issued-At: 2026-06-23T13:55:14.715230+00:00
@cheerc

cheerc commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

✅ VERIFIED

Reviewer: cb-team-reviewer
Reviewed HEAD: c64535a218d3ce83a181900de8e57666204c3da1
Audit mode: fast_path (D1, correctness)
Skills: review-task-loop, ecc-code-reviewer

Files Reviewed

  • tests/lib/lockHelpers.js (105 lines, NEW) — DI extraction of lock management from JavaScript.html L742-795
  • tests/unit/lockManagement.test.js (545 lines, 44 tests) — acquireLock/releaseLock/refreshLockHeartbeat/expiry/multi-tab/localStorage format/edge cases
  • tests/unit/wiringContracts.test.js (5 lines ±) — module count 13 → 14

Summary

Pure test-only PR (zero production code changes). Adds 44 new lock management tests and a DI-extracted helper module.

lockHelpers.js: Faithful extraction verified against JavaScript.html L742-795. DI correctly replaces this.tabId→param, localStorage→injected storage, Date.now()→injected nowFn. Named constants LOCK_STORAGE_KEY and STALE_THRESHOLD_MS improve over inline literals. refreshLockHeartbeat guard simplified from this.isReadOnly || !this.activeScheduleId || ...ALL_SCHEDULES_ID to isReadOnly || !scheduleId — appropriate for DI context.

lockManagement.test.js: Thorough coverage across 7 describe blocks:

  • acquireLock (8 tests): uncontested, data format, same-tab re-acquire, rejection by active tab, stale breaking, exact threshold boundary (> not >=), independent schedules, corrupt recovery
  • releaseLock (7 tests): owner release, non-owner rejection, falsy guards ×3, non-existent no-op, selective release
  • refreshLockHeartbeat (6 tests): timestamp update, non-owner no-op, readOnly guard, falsy guards ×2, no-lock no-op
  • Lock expiry (4 tests): boundary (14.999s vs 15.001s), heartbeat preventing expiry, heartbeat lapse, threshold constant validation
  • Multi-tab concurrency (7 tests): mutual exclusion, cross-schedule independence, release-handoff, three-tab competition, stale-steal isolation, wrong-tab release protection
  • localStorage format (8 tests): key validation, JSON structure, entry schema, multi-schedule coexistence, clean state after full release, empty/corrupt recovery
  • Edge cases (4 tests): special chars, numeric ID, rapid cycle, external storage clear resilience

Deterministic time control via nowFn injection enables precise threshold testing without real timer dependencies. Mock storage (createMockStorage from frontendMocks.js) provides localStorage-compatible API with clear() support.

Stage 1 — Correctness

Zero findings. No conflict markers. Imports resolve correctly. Issue linkage present (Closes #136). No risk-flags triggered.

Stage 2 — Adversarial

Zero findings. Stale threshold boundary test correctly uses > (not >=), matching (now - existingLock.timestamp) > 15000 in source. Multi-tab tests correctly share single storage instance (simulating real localStorage sharing). _getLocks exposed for testing — appropriate for test helper. refreshLockHeartbeat guard simplification is valid (ALL_SCHEDULES_ID is app-level routing, not lock logic).

Evidence

  • ran: gh pr view 142 --json headRefOidc64535a218d3ce83a181900de8e57666204c3da1 (SHA aligned)
  • ran: gh api .../JavaScript.html?ref=test/wave3b-lock-management | sed -n 742,795p → source extraction verified
  • ran: gh api .../lockHelpers.js | base64 -d | grep conflict-markers → 0
  • ran: gh api .../frontendMocks.js | grep createMockStorage → exported, has getItem/setItem/clear
  • cited: lockHelpers.js:L63 — (now - existingLock.timestamp) > STALE_THRESHOLD_MS matches source L763 > 15000
  • cited: JavaScript.html:L754-771 — acquireLock source: this.tabId, this._getLocks(), Date.now() all correctly DI-replaced
  • cited: lockManagement.test.js:L106 — exact threshold boundary: BASE_TIME + STALE_THRESHOLD_MS → false (not stale yet), confirms > not >=

@cheerc
cheerc merged commit a7a9579 into main Jun 23, 2026
1 check passed
@cheerc
cheerc deleted the test/wave3b-lock-management branch June 23, 2026 14:05
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 0] Lock management 行為測試(多 tab concurrency guard)

1 participant