diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a81df28..d5274dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,8 @@ Dependencies are managed through normal Go module resolution. Review dependency DB-backed integration tests skip when no Postgres connection is configured. To run them, provide libpq environment variables or `READINESS_TEST_DSN`. Tests create throwaway schemas and drop them during cleanup. +See `docs/TEST_COVERAGE.md` for the current focused coverage roadmap and fixture rules. + ### CI and race detector policy The default required-looking CI job is `Build, Vet & Test`. Keep that job name stable unless repository branch protection is updated at the same time. diff --git a/docs/TEST_COVERAGE.md b/docs/TEST_COVERAGE.md new file mode 100644 index 0000000..50e4dd3 --- /dev/null +++ b/docs/TEST_COVERAGE.md @@ -0,0 +1,35 @@ +# Focused Test Coverage Roadmap + +This roadmap tracks high-value Health Dashboard test coverage without turning the suite into a slow infrastructure exercise. + +## Constraints + +- Default CI must stay pure and fast under `go test ./...`. +- Fixtures must be synthetic or anonymized. Do not commit personal health exports, screenshots, logs, or raw metric dumps. +- DB-backed tests should skip cleanly unless a Postgres connection is configured. +- Coverage thresholds are intentionally deferred until the active product contracts have a useful baseline. + +## Current Coverage Map + +| Area | Current state | Next useful coverage | +|---|---|---| +| `internal/health` formulas | Strong coverage for readiness, evidence payloads, EnergyBank v2, stress, i18n labels, and anomaly helpers. | Add tests only when formula or evidence contracts change. | +| Apple Health import | Synthetic XML fixtures cover mapped records, empty input, malformed input, sleep-stage mapping, and focused parser edge cases. | Add fixtures for real bug classes after reducing them to minimal synthetic XML. | +| UI and admin APIs | Contract tests cover admin pages, auth/session behavior, webhook dispatch, dashboard sections, and tenant scope. | Pin response-shape changes before frontend code starts depending on them. | +| Storage writers | Readiness redesign, EnergyBank, sleep gates, freshness, calibration, and tenant helpers have focused tests, with DB-backed tests gated. | Prefer bug-driven storage contract tests over broad repository-level sweeps. | +| Notifications | Morning/evening report, freshness banners, smart retry, Telegram webhook, and proactive framework tests cover key behavior. | Add regression tests when notification timing or skip conditions change. | +| CI policy | Default CI runs build, vet, and pure tests; race detector is manual and documented. | Keep race detector separate from default PR checks unless the runtime cost becomes acceptable. | + +## Delivered First Batch + +Issue #149 starts with a small Apple Health import safety batch: + +- `internal/applehealth/testdata/focused_edge_export.xml` is a synthetic fixture with no personal data. +- `TestParseXMLFocusedEdgeFixturePinsImportSafety` checks percent normalization boundaries, duration-derived category metrics, stand-hour mapping, unknown quantity fallback, invalid duration rejection, and unsupported correlation skipping. + +## Candidate Follow-Ups + +- Dashboard API response stability tests for fields consumed by `static/app.js` and `static/charts.js`. +- Import ZIP wrapper tests around missing `export.xml`, progress status, and parse error propagation. +- Storage contract tests for freshness/readiness behavior when a bug or product change proves the need. +- One informational `go test -cover` baseline after the focused lanes are less sparse, without enforcing a threshold. diff --git a/docs/ai-plans/2026-06-06-issue-149-focused-test-coverage-roadmap.html b/docs/ai-plans/2026-06-06-issue-149-focused-test-coverage-roadmap.html new file mode 100644 index 0000000..b258841 --- /dev/null +++ b/docs/ai-plans/2026-06-06-issue-149-focused-test-coverage-roadmap.html @@ -0,0 +1,162 @@ + + + + + Implementation Plan: Issue #149 Focused Test Coverage Roadmap + + + + +
+

Implementation Plan: Issue #149 Focused Test Coverage Roadmap

+

Approved and implemented

+ +
+

Task Summary

+

Create a current, focused test coverage roadmap for active Health Dashboard behavior. This should update the issue/doc roadmap and add only high-value missing tests, not introduce broad coverage thresholds or slow infrastructure.

+
+ +
+

Current Behavior

+ +
+ +
+

Desired Behavior

+ +
+ +
+

Assumptions And Unknowns

+ +
+ +
+

Files Likely To Change

+ +
+ +
+

Implementation Steps

+
    +
  1. Produce a compact test inventory grouped by package and behavior.
  2. +
  3. Map the issue's candidate lanes to current coverage and mark each as covered, partial, or missing.
  4. +
  5. Choose a first small batch of tests that protects active product behavior without DB/network requirements.
  6. +
  7. Add or update docs with the roadmap and "do not commit personal health data" fixture rule.
  8. +
  9. If GitHub child issues are desired, create them after user approval with clear scopes and dependencies.
  10. +
  11. Run the default test suite and vet.
  12. +
+
+ +
+

Impact

+ +
+ +
+

Test Plan

+ +
+ +
+

Risks And Edge Cases

+ +
+ +
+

Rollback Plan

+

Revert roadmap docs and any added tests. If child issues are created incorrectly, close or relabel them rather than deleting historical context.

+
+ +
+

Open Questions

+ +
+ +
+

Approval Gate

+

Approved by the user on 2026-06-06 before production code changes.

+
+ +
+

Implementation Result

+ +
+ +
+

Verification Run

+ +
+ +
+

Known Limitations And Follow-Up

+ +
+
+ + diff --git a/internal/applehealth/parse_test.go b/internal/applehealth/parse_test.go index dc256eb..6ea2331 100644 --- a/internal/applehealth/parse_test.go +++ b/internal/applehealth/parse_test.go @@ -26,6 +26,21 @@ func TestParseXMLSyntheticFixtureMapsHealthKitRecords(t *testing.T) { assertNoPoint(t, points, "sleep_total", "2026-01-02 02:00:00 +0000") } +func TestParseXMLFocusedEdgeFixturePinsImportSafety(t *testing.T) { + points := collectXMLFixturePoints(t, "testdata/focused_edge_export.xml") + + if got, want := len(points), 4; got != want { + t.Fatalf("point count = %d, want %d: %+v", got, want, points) + } + assertPoint(t, points, "blood_oxygen_saturation", "%", "2026-02-01 06:00:00 +0100", "Synthetic Watch", 98) + assertPoint(t, points, "mindful_minutes", "min", "2026-02-01 09:00:00 +0100", "Synthetic Phone", 15) + assertPoint(t, points, "apple_stand_hour", "count", "2026-02-01 10:00:00 +0100", "Synthetic Watch", 1) + assertPoint(t, points, "some_new_metric", "count", "2026-02-01 13:00:00 +0100", "Synthetic Device", 12.5) + + assertMetricCount(t, points, "mindful_minutes", 1) + assertNoMetric(t, points, "blood_pressure") +} + func TestParseXMLEmptyAndMalformedInputs(t *testing.T) { var emitted bool if err := parseXMLFixture("testdata/empty_export.xml", func(points []storage.MetricPoint) { @@ -87,6 +102,15 @@ func assertNoPoint(t *testing.T, points []storage.MetricPoint, metric, date stri } } +func assertNoMetric(t *testing.T, points []storage.MetricPoint, metric string) { + t.Helper() + for _, p := range points { + if p.MetricName == metric { + t.Fatalf("unexpected metric=%s point: %+v", metric, p) + } + } +} + func assertMetricCount(t *testing.T, points []storage.MetricPoint, metric string, want int) { t.Helper() got := 0 diff --git a/internal/applehealth/testdata/focused_edge_export.xml b/internal/applehealth/testdata/focused_edge_export.xml new file mode 100644 index 0000000..b8b75e0 --- /dev/null +++ b/internal/applehealth/testdata/focused_edge_export.xml @@ -0,0 +1,9 @@ + + + + + + + + +