feat: disk-usage, file-count endpoints + OpenAPI CI artifact#178
Conversation
64M was insufficient when scanning 6 hours of RAW data for calibration baselines. The calibrator was killed by systemd OOM (signal 9). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements #166, #164, #163: - system.getDiskUsage: root filesystem usage via df -B1 / with graceful fallback - biometrics.getFileCount: RAW file count and total size for iOS Status screen - Export listRawFiles from raw.ts for reuse in biometrics router - OpenAPI spec published as CI artifact on push to dev/main (90-day retention) - Swift contract tests decode REST responses against iOS model types - Snapshot script curls 11 REST endpoints; seed script populates test data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR establishes a contract testing framework by adding GitHub Actions workflows for running tests and publishing OpenAPI specs, introducing a Swift test suite with helper scripts for seeding data and snapshotting API responses, and adding new server endpoints (getFileCount, getDiskUsage) while exporting an internal function (listRawFiles) to support the contract tests. The calibrator service memory limit is also increased. Changes
Sequence Diagram(s)sequenceDiagram
actor GHA as GitHub Actions
participant Server as Node.js Server
participant DB as SQLite Database
participant Swift as Swift Tests
GHA->>GHA: Checkout code & setup
GHA->>Server: Start server (background)
GHA->>Server: Poll /api/health (max 60s)
Server-->>GHA: Health OK
GHA->>DB: Execute seed-test-data.sh
DB-->>GHA: Test records inserted
GHA->>Server: Snapshot API endpoints
Server->>DB: Query biometrics, health, system
Server-->>GHA: API responses (JSON fixtures)
GHA->>GHA: Save fixtures locally
GHA->>Swift: Run contract tests
Swift->>Swift: Load & decode fixtures
Swift->>Swift: Assert expected shapes
Swift-->>GHA: Test results
GHA->>Server: Terminate background process
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds new API surface area and CI checks to support iOS “Status” UX and keep the server/iOS models aligned via contract testing, plus a small calibrator resource bump.
Changes:
- Add
/system/disk-usageand/biometrics/file-countendpoints for status/telemetry. - Introduce Swift-based contract tests + scripts to snapshot real server responses into fixtures, and a CI workflow to run them.
- Add a CI workflow to publish the generated OpenAPI spec as an artifact; increase calibrator service memory limit.
Reviewed changes
Copilot reviewed 10 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/server/routers/system.ts | Adds getDiskUsage endpoint for root filesystem usage. |
| src/server/routers/raw.ts | Exports listRawFiles() for reuse outside the raw router. |
| src/server/routers/biometrics.ts | Adds getFileCount endpoint using RAW file stats. |
| modules/calibrator/sleepypod-calibrator.service | Raises MemoryMax to 256M. |
| contract-tests/Tests/ContractTests/ContractTests.swift | Adds Swift contract tests that decode JSON fixtures. |
| contract-tests/scripts/snapshot-responses.sh | Snapshots live API responses into JSON fixtures. |
| contract-tests/scripts/seed-test-data.sh | Seeds biometrics SQLite with test rows for non-empty responses. |
| contract-tests/Package.swift | Defines SwiftPM package for contract tests + fixtures resources. |
| .github/workflows/openapi.yml | Builds/starts server and downloads OpenAPI spec as artifact. |
| .github/workflows/contract-test.yml | Runs end-to-end contract test flow on PRs (start server, seed, snapshot, swift test). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| - name: Start server | ||
| run: pnpm start & |
| - name: Stop server | ||
| run: kill %1 2>/dev/null || true |
| - name: Checkout iOS models | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: sleepypod/ios | ||
| token: ${{ secrets.IOS_REPO_TOKEN }} | ||
| path: ios-checkout | ||
| sparse-checkout: Sleepypod/Models | ||
| sparse-checkout-cone-mode: false | ||
|
|
| - name: Start server | ||
| run: pnpm start & | ||
|
|
| - name: Stop server | ||
| if: always() | ||
| run: kill %1 2>/dev/null || true |
| # Timestamps are unix epoch (seconds) — matching the integer column mode in the schema. | ||
| set -euo pipefail | ||
|
|
||
| DB_PATH="${BIOMETRICS_DB_PATH:-biometrics.dev.db}" |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
contract-tests/Tests/ContractTests/ContractTests.swift (1)
29-31: AssertusedPercentupper bound as part of the contract.Line 30 only checks non-negative values. Add
<= 100so impossible percentages fail the contract test.💡 Proposed fix
let result = try JSONDecoder().decode(DiskUsage.self, from: data) `#expect`(result.usedPercent >= 0) + `#expect`(result.usedPercent <= 100) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@contract-tests/Tests/ContractTests/ContractTests.swift` around lines 29 - 31, Update the contract test assertion to enforce an upper bound for DiskUsage percentages: change the single non-negative check on result.usedPercent to assert it is within 0 and 100 (inclusive). Locate the test that decodes DiskUsage (where result is assigned from JSONDecoder().decode(DiskUsage.self, from: data)) and replace the lone usedPercent >= 0 expectation with a combined expectation that validates 0 <= result.usedPercent <= 100 so impossible percentages fail the contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/contract-test.yml:
- Around line 51-53: The "Start server" step currently backgrounds the process
with `pnpm start &` but doesn't record its PID, so later use of `%1` is
unreliable; update the "Start server" step (the step named "Start server") to
capture the background PID with `$!` and persist it to the workflow environment
(e.g. `pnpm start & echo "SERVER_PID=$!" >> $GITHUB_ENV`), and then update the
later step that references the job-control token `%1` to instead use the
persisted env var `${{ env.SERVER_PID }}` (or shell `$SERVER_PID`) when killing
or waiting for the process.
In @.github/workflows/openapi.yml:
- Around line 50-51: The "Stop server" step currently uses "kill %1", which
fails in non-interactive GitHub Action shells; replace job-control usage by
tracking the server PID when you start it (capture $! into a file like
server.pid) and then in the "Stop server" step read and kill that PID (e.g.,
kill $(cat server.pid) 2>/dev/null || true), or alternatively use a safer
pattern such as pkill -f "<server command>" to terminate by command name; update
the start step to write the PID and the stop step to use that PID file or pkill
instead of "kill %1".
In `@contract-tests/Tests/ContractTests/ContractTests.swift`:
- Around line 6-8: The helper loadFixture(_:) currently force-unwraps the URL
which will crash if the JSON is missing; change loadFixture to safely unwrap
Bundle.module.url(forResource:withExtension:subdirectory:) and, if nil, throw a
descriptive error (including the fixture name and subdirectory) instead of
force-unwrapping so tests fail with a clear message; keep the existing try
Data(contentsOf:) to propagate file-read errors once the URL is validated.
---
Nitpick comments:
In `@contract-tests/Tests/ContractTests/ContractTests.swift`:
- Around line 29-31: Update the contract test assertion to enforce an upper
bound for DiskUsage percentages: change the single non-negative check on
result.usedPercent to assert it is within 0 and 100 (inclusive). Locate the test
that decodes DiskUsage (where result is assigned from
JSONDecoder().decode(DiskUsage.self, from: data)) and replace the lone
usedPercent >= 0 expectation with a combined expectation that validates 0 <=
result.usedPercent <= 100 so impossible percentages fail the contract.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4b9f4420-bace-4c0c-8071-95a459907795
📒 Files selected for processing (12)
.github/workflows/contract-test.yml.github/workflows/openapi.ymlcontract-tests/Package.swiftcontract-tests/Sources/SleepypodModels/.gitkeepcontract-tests/Tests/ContractTests/ContractTests.swiftcontract-tests/Tests/ContractTests/Fixtures/.gitkeepcontract-tests/scripts/seed-test-data.shcontract-tests/scripts/snapshot-responses.shmodules/calibrator/sleepypod-calibrator.servicesrc/server/routers/biometrics.tssrc/server/routers/raw.tssrc/server/routers/system.ts
- Track server PID via $GITHUB_ENV instead of kill %1 (broken across GHA steps) in both contract-test.yml and openapi.yml - Guard fixture URL unwrap in Swift tests to fail with a clear message instead of crashing - Assert usedPercent <= 100 in disk usage contract test - Align seed script to read BIOMETRICS_DATABASE_URL (strip file: prefix) matching the server's env var, with fallback chain
Contract tests gate iOS model changes against the core API, so the CI workflow belongs in the iOS repo where it blocks iOS PRs that break decoding. Core publishes the OpenAPI spec artifact (#164) as the shared reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
🎉 This PR is included in version 1.1.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
system.getDiskUsage(root filesystem viadf -B1 /, graceful fallback) andbiometrics.getFileCount(RAW file count/size for iOS Status screen). ExportslistRawFilesfromraw.tsfor reuse.openapi.ymlworkflow publishes the OpenAPI spec as a CI artifact on push todev/mainwith 90-day retention.sleepypod/iosrepo (see feat: contract tests — validate models against core API ios#6) — iOS PRs are the ones that should be gated against the core API, not the other way around.Test plan
pnpm tscpasses with new endpointsdev/maintriggers OpenAPI artifact workflow/api/openapi.json🤖 Generated with Claude Code