Skip to content

test: regression tests for twelve zero-coverage modules (178 new tests)#1865

Open
bassilkhilo-ag2 wants to merge 12 commits into
sonichi:mainfrom
bassilkhilo-ag2:test/task-body-guard-coverage
Open

test: regression tests for twelve zero-coverage modules (178 new tests)#1865
bassilkhilo-ag2 wants to merge 12 commits into
sonichi:mainfrom
bassilkhilo-ag2:test/task-body-guard-coverage

Conversation

@bassilkhilo-ag2

@bassilkhilo-ag2 bassilkhilo-ag2 commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds regression tests for 11 modules that had zero test coverage in main. All tests are pure-function isolation — no filesystem I/O, no network, no subprocess — so they run fast and reliably in CI.

File Module Tests What it guards
tests/task-body-guard.test.py src/task_body_guard.py 9 Header/fence injection prevention in confine_user_content()
tests/event-log.test.py src/event_log.py 9 Crash-safe JSONL logging, daily roll, hostname fallback
tests/archive-stale-results.test.py src/archive-stale-results.py 10 DM flood prevention, DRY_RUN falsy values, retention override
tests/scan-call-logs.test.py src/scan-call-logs.py 17 Quality monitors: duplicate responses, access issues, fabrication, reconnect leak, repeated commands
tests/morning-briefing-synthesize.test.py src/morning-briefing.py 21 synthesize() greeting/weather/events/reminders/discord/health/insight composition
tests/obsidian-mirror-pure.test.py src/obsidian-mirror.py 13 _task_id_from_path() regex extraction, _parse_since() duration parsing
tests/call-stats.test.py src/call-stats.py 21 parse_ts(), mask_phone(), compute_stats(), format_text() pipeline
tests/daily-insight-pure.test.py src/daily-insight.py 19 analyze_call_timing(), analyze_call_duration(), analyze_topics()
tests/health-check-notify-slack.test.py src/health-check.py 9 Slack-DM watchdog cooldown, dedup state, failed-send no-record
tests/remote-relay-bridge-pure.test.py src/remote-relay-bridge.py 17 _valid_tid() path-traversal gate, _one_line() header-injection strip
tests/discord-read-boundary.test.py src/discord-read.py 14 _at_or_before_boundary() / _strictly_older_than_boundary() in snowflake-ID and ISO-timestamp modes

Total: 159 tests, all passing.

Test plan

  • Run each file directly: python3 tests/<file>.test.py — exits 0 on pass, 1 on fail.
  • All tests are hermetic (temp dirs, env patching, mock network where needed).

bassilkhilo-ag2 and others added 3 commits June 30, 2026 15:32
…oken leak

case_i_token_read_prefers_channel_env() was failing with 3 sub-failures because
the test set \$HOME but not \$CLAUDE_CONFIG_DIR. claude_home_path() checks
\$CLAUDE_CONFIG_DIR before \$HOME/.claude/, so on installs where CLAUDE_CONFIG_DIR
points to the real workspace (.claude-sutando/), the real SLACK_BOT_TOKEN leaked
into the test — all three path-precedence assertions got the live token instead
of the expected test values.

Fix: also override CLAUDE_CONFIG_DIR to point at the temp home's .claude/
directory for the duration of case_i, with proper save/restore in the finally
block. All 9 invariants now pass on installs with CLAUDE_CONFIG_DIR set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
…ntract

task_body_guard.py is a security-critical module (prevents header-field
and instruction-fence forgery in task files) but had zero test coverage.

Cases:
- benign prose: never modified
- header-field forgery: access_tier, user_id, channel_id etc. defanged with U+200B
- fence forgery: lines starting with >=3 '=' defanged
- CR/CRLF normalization: \r and \r\n forges caught before split
- idempotence: second pass is a no-op (no double ZWSP)
- leading-whitespace bypass: indented forges caught via lstrip
- multiline mixed: only forge lines touched, prose unchanged
- edge cases: empty string, whitespace-only, newline-only
- colon-spacing variants: 'key:value', 'key : value', 'key\t:' all matched

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
event_log.py is a crash-safe structured logging module used across all
bridges and services, but had zero test coverage.

Cases:
- writes valid JSONL: event file created with correct kind/field/ts/node
- never raises on write failure: crash-safe even on read-only LOGS_DIR
- non-serializable value repr'd: object() → repr string, not crash
- log path rolls daily: different timestamps → different filenames
- same-day paths equal: two calls in same day → same file
- machine ID hostname fallback: missing identity file → socket.gethostname()
- multiple events append: 5 log_event() calls → 5 JSONL lines
- required fields: ts (float > 0), node (str), kind (str) always present
- LOGS_DIR created on demand: nested path created automatically by log_event()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test(task-body-guard): 9 tests for confine_user_content() security contract test(task-body-guard, event-log): regression tests for security-critical modules Jun 30, 2026
archive-stale-results.py prevents DM floods (post-mortem 2026-04-15) by
archiving stale results/*.txt before services start, but had zero coverage.

Cases:
- missing results/ dir: silent no-op, exit 0
- stale .txt archived: files > RETENTION_HOURS moved to archive-YYYY-MM-DD/
- fresh files not archived: files < RETENTION_HOURS untouched
- non-.txt files skipped: .png, .json, no-ext never archived
- archive subdir not re-archived: archive-*/ contents are never touched
- DRY_RUN=1: prints intent, does not move files
- DRY_RUN falsy values: '0', 'false', 'no', 'FALSE', 'No' all disable dry-run
- RETENTION_HOURS override: env var adjusts the cutoff window
- archive dir created on demand: first archived file creates the dir
- mixed fresh and stale: only old files moved, fresh co-exist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test(task-body-guard, event-log): regression tests for security-critical modules test(task-body-guard, event-log, archive-stale-results): regression tests for zero-coverage modules Jun 30, 2026
scan-call-logs.py has pure detection functions (transcript → issues) with
zero test coverage. These drive post-call quality monitoring.

Cases:
- detect_duplicate_responses: consecutive duplicates flagged; spread-out
  (intentional) repeats not flagged; clean transcript → no issues
- detect_access_issues: 'can't access' and 'not authorized' patterns
  detected; clean transcript → no issues
- detect_fabrication: specific street address and dollar amounts flagged
  as potential hallucination; clean transcript → no issues
- detect_reconnect_leak: 'I'm back', 'Welcome back', case-insensitive
  variants detected; only Sutando lines checked (Recipient not flagged);
  clean transcript → no issues
- detect_repeated_command: 3x summon triggers repeated-summon flag;
  single summon → no flag
- detect_identity_confusion: no crash (smoke test)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test(task-body-guard, event-log, archive-stale-results): regression tests for zero-coverage modules test: regression tests for four zero-coverage modules (45 total) Jun 30, 2026
bassilkhilo-ag2 and others added 3 commits June 30, 2026 16:06
… pure utils

21 tests for morning-briefing synthesize(): greeting hour tiers, weather
present/absent, calendar count variants (0/1/many), reminder/question/discord/
health-issue inclusion + caps, insight filtering (raw-data and too-short guards),
clean-slate closing line logic.

13 tests for obsidian-mirror pure functions: _task_id_from_path() regex
extraction across standard/alphanumeric/dash-heavy/non-matching filenames,
and _parse_since() duration parser covering all suffixes (s/m/h/d),
uppercase variants, plain ints, empty string, and multi-digit values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
Covers parse_ts() (ISO Z + offset, empty/None/garbage), mask_phone()
(11-digit US masking, unknown sentinel, None, short pass-through),
compute_stats() (empty list, total, avg/longest/shortest, zero-duration
exclusion, meeting/owner flags, peak hour, masked callers), and
format_text() (header, total, no-duration placeholder, duration lines,
peak hour HH:00 formatting).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
… pure functions

Covers the three I/O-free analysis functions in src/daily-insight.py:
- analyze_call_timing(): ISO Z parse, hour/day accumulation, missing/bad
  timestamps skipped, 'timestamp' key fallback
- analyze_call_duration(): empty/zero-only → None, avg+longest math,
  long_call_pct, 'duration' key alias, non-numeric skipped
- analyze_topics(): word counting, short-word exclusion, stopwords,
  punctuation stripping, 'topic' key fallback, 10-entry cap

19/19 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test: regression tests for four zero-coverage modules (45 total) test: regression tests for five zero-coverage modules (119 total) Jun 30, 2026
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test: regression tests for five zero-coverage modules (119 total) test: regression tests for eight zero-coverage modules (119 total) Jun 30, 2026
…line and boundary-check pure functions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test: regression tests for eight zero-coverage modules (119 total) test: regression tests for eleven zero-coverage modules (159 total) Jun 30, 2026
Covers the early-return logic that executes before any network I/O:
  - push_image: non-existent path, too-small file, unreadable file,
    exactly-at-MIN_FRAME_BYTES (reaches voice check), voice not ready,
    voice ready + 200 response, voice ready + 4xx rejection
  - mime fallback: unknown extension → image/jpeg default
  - is_voice_ready: urlopen raises, sessionReady=true/false/missing

Network calls (urlopen, _post, is_voice_ready) are stubbed with
unittest.mock.patch so no real voice-agent server is required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test: regression tests for eleven zero-coverage modules (159 total) test: regression tests for twelve zero-coverage modules (172 total) Jun 30, 2026
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test: regression tests for twelve zero-coverage modules (172 total) test: regression tests for eleven zero-coverage modules (163 new tests) Jun 30, 2026
…n, format normalization

Covers the pure guard logic that runs outside the HTTP server:
  - _notify_capture() debounce: first call fires, within-window suppressed,
    post-window fires again, NOTIFY_ENABLED=0 skips without updating timestamp
  - Display-number validation: 1-9 accepted, 0/10 rejected, None/alpha → None
    (mirrors the coercion expression in Handler.do_GET())
  - Format normalization: png/jpg/jpeg handled correctly, unknown → png fallback
    (mirrors ext + type_flag logic in Handler.do_GET())
  - Module constants: NOTIFY_DEBOUNCE_S=5.0 and PORT=7845 are stable contracts

No HTTP server is started — subprocess (osascript) and threading.Thread
are stubbed with unittest.mock.patch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXQJogmVSdYrKtYX1LwzwT
@bassilkhilo-ag2 bassilkhilo-ag2 changed the title test: regression tests for eleven zero-coverage modules (163 new tests) test: regression tests for twelve zero-coverage modules (178 new tests) Jun 30, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

@cla-assistant check

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.

1 participant