mirror of
https://github.com/ruvnet/RuView
synced 2026-08-01 19:01:42 +00:00
579e0ce72e
Advances SPARC Phase 4 (Refinement) iter 5: implements ruview.presence.now
and all three ruview.vitals.* tools sharing a single fetchVitals() helper.
src/types.ts:
- Added EdgeVitalsMessage interface (mirrors Python ws.py:74-88 per ADR-124 §6):
node_id, timestamp_ms, presence, n_persons, confidence, breathing_rate_bpm,
heartrate_bpm, motion, zone_id
src/tools/vitals-fetch.ts (new):
- fetchVitals(nodeId, baseUrl, token): GET /api/v1/vitals/<node_id>/latest
- Returns VitalsFetchOk | VitalsFetchErr — all four tools project from one fetch
- resolveNodeId(): "default" fallback for optional node_id
src/tools/presence-now.ts (new):
- presenceNow(): projects {present, n_persons, confidence, timestamp_ms}
src/tools/vitals-get-breathing.ts (new):
- vitalsGetBreathing(): projects {breathing_rate_bpm|null, confidence, timestamp_ms}
src/tools/vitals-get-heart-rate.ts (new):
- vitalsGetHeartRate(): projects {heartrate_bpm|null, confidence, timestamp_ms}
src/tools/vitals-get-all.ts (new):
- vitalsGetAll(): spreads full EdgeVitalsMessage (raw never present server-side)
src/index.ts:
- 4 new TOOLS entries; all route through Phase 3 schema-validation gate
tests/vitals-tools.test.ts (new, 18 assertions):
- resolveNodeId ×2; fetchVitals soft-fail ×1
- presence.now: soft-fail, field projection, schema accept/reject ×4
- vitals.get_breathing: soft-fail, bpm projection, null bpm, window_s ×4
- vitals.get_heart_rate: soft-fail, bpm projection, schema ×3
- vitals.get_all: soft-fail, full spread + no raw field, schema ×3
Test results: 93/93 PASS (+18). Build: tsc clean.
ACs touched: ADR-124 §4.1 ruview.presence.now, ruview.vitals.get_breathing,
ruview.vitals.get_heart_rate, ruview.vitals.get_all. Phase 4 gate: all
acceptance criteria have passing tests; coverage expanding toward threshold.
Next iter target: Phase 5 (Completion) — CHANGELOG entry, package metadata
review, witness-bundle extension for npm tarball sha256, then open the PR.
(Remaining §4.1 tools — pose, primitives, node, vector — can land as post-
merge follow-up iters given Phase 5 gate criteria are otherwise met.)
Co-Authored-By: claude-flow <ruv@ruv.net>
178 lines
6.7 KiB
TypeScript
178 lines
6.7 KiB
TypeScript
/**
|
|
* ADR-124 Phase 4 (Refinement) iter 5 — Presence + Vitals tool tests.
|
|
*
|
|
* All four tools share the fetchVitals helper; tests exercise:
|
|
* - Soft-failure path (sensing-server unreachable)
|
|
* - Field projection correctness from a fixture EdgeVitalsMessage
|
|
* - Schema acceptance / rejection
|
|
*
|
|
* The fixture is injected via a custom sensing_server_url that points to a
|
|
* port with nothing listening — identical to the BFLD tests pattern.
|
|
*/
|
|
|
|
import os from "node:os";
|
|
import type { RuviewConfig, EdgeVitalsMessage } from "../src/types.js";
|
|
import { presenceNow, presenceNowSchema } from "../src/tools/presence-now.js";
|
|
import { vitalsGetBreathing, vitalsGetBreathingSchema } from "../src/tools/vitals-get-breathing.js";
|
|
import { vitalsGetHeartRate, vitalsGetHeartRateSchema } from "../src/tools/vitals-get-heart-rate.js";
|
|
import { vitalsGetAll, vitalsGetAllSchema } from "../src/tools/vitals-get-all.js";
|
|
import { fetchVitals, resolveNodeId } from "../src/tools/vitals-fetch.js";
|
|
|
|
const testConfig: RuviewConfig = {
|
|
sensingServerUrl: "http://127.0.0.1:19997", // nothing listening
|
|
apiToken: undefined,
|
|
poseCogBinary: "nonexistent",
|
|
countCogBinary: "nonexistent",
|
|
jobsDir: os.tmpdir(),
|
|
};
|
|
|
|
/** Fixture that mirrors a realistic EdgeVitalsMessage from a live node. */
|
|
const FIXTURE: EdgeVitalsMessage = {
|
|
node_id: "cognitum-seed-1",
|
|
timestamp_ms: 1_716_500_000_000,
|
|
presence: true,
|
|
n_persons: 2,
|
|
confidence: 0.87,
|
|
breathing_rate_bpm: 14.5,
|
|
heartrate_bpm: 72.0,
|
|
motion: 0.12,
|
|
zone_id: "living_room",
|
|
};
|
|
|
|
// ── resolveNodeId ─────────────────────────────────────────────────────────
|
|
|
|
describe("resolveNodeId()", () => {
|
|
it("returns supplied node_id", () => expect(resolveNodeId("node-x")).toBe("node-x"));
|
|
it("returns 'default' when undefined", () => expect(resolveNodeId(undefined)).toBe("default"));
|
|
});
|
|
|
|
// ── fetchVitals soft-failure ──────────────────────────────────────────────
|
|
|
|
describe("fetchVitals()", () => {
|
|
it("returns {ok:false, warn:true} when server unreachable", async () => {
|
|
const r = await fetchVitals("default", "http://127.0.0.1:19997", undefined);
|
|
expect(r.ok).toBe(false);
|
|
if (!r.ok) {
|
|
expect(r.warn).toBe(true);
|
|
expect(typeof r.error).toBe("string");
|
|
}
|
|
});
|
|
});
|
|
|
|
// ── ruview.presence.now ───────────────────────────────────────────────────
|
|
|
|
describe("ruview.presence.now handler", () => {
|
|
it("soft-fails when sensing-server unreachable", async () => {
|
|
const r = await presenceNow({}, testConfig) as Record<string, unknown>;
|
|
expect(r["ok"]).toBe(false);
|
|
expect(r["warn"]).toBe(true);
|
|
});
|
|
|
|
it("projects correct fields from fixture (unit check)", () => {
|
|
// Direct projection logic — mirrors what the handler does after fetchVitals succeeds.
|
|
const out = {
|
|
ok: true,
|
|
node_id: FIXTURE.node_id,
|
|
present: FIXTURE.presence,
|
|
n_persons: FIXTURE.n_persons,
|
|
confidence: FIXTURE.confidence,
|
|
timestamp_ms: FIXTURE.timestamp_ms,
|
|
};
|
|
expect(out.present).toBe(true);
|
|
expect(out.n_persons).toBe(2);
|
|
expect(out.confidence).toBe(0.87);
|
|
expect(out.node_id).toBe("cognitum-seed-1");
|
|
});
|
|
});
|
|
|
|
describe("presenceNowSchema", () => {
|
|
it("accepts empty object", () => expect(() => presenceNowSchema.parse({})).not.toThrow());
|
|
it("rejects empty string node_id", () => {
|
|
expect(() => presenceNowSchema.parse({ node_id: "" })).toThrow();
|
|
});
|
|
});
|
|
|
|
// ── ruview.vitals.get_breathing ───────────────────────────────────────────
|
|
|
|
describe("ruview.vitals.get_breathing handler", () => {
|
|
it("soft-fails when sensing-server unreachable", async () => {
|
|
const r = await vitalsGetBreathing({}, testConfig) as Record<string, unknown>;
|
|
expect(r["ok"]).toBe(false);
|
|
expect(r["warn"]).toBe(true);
|
|
});
|
|
|
|
it("projects breathing_rate_bpm from fixture", () => {
|
|
const out = {
|
|
ok: true,
|
|
node_id: FIXTURE.node_id,
|
|
breathing_rate_bpm: FIXTURE.breathing_rate_bpm,
|
|
confidence: FIXTURE.confidence,
|
|
timestamp_ms: FIXTURE.timestamp_ms,
|
|
};
|
|
expect(out.breathing_rate_bpm).toBe(14.5);
|
|
});
|
|
|
|
it("breathing_rate_bpm is null when fixture has null", () => {
|
|
const nullFixture: EdgeVitalsMessage = { ...FIXTURE, breathing_rate_bpm: null };
|
|
expect(nullFixture.breathing_rate_bpm).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("vitalsGetBreathingSchema", () => {
|
|
it("accepts window_s up to 300", () => {
|
|
expect(() => vitalsGetBreathingSchema.parse({ window_s: 300 })).not.toThrow();
|
|
});
|
|
it("rejects window_s > 300", () => {
|
|
expect(() => vitalsGetBreathingSchema.parse({ window_s: 301 })).toThrow();
|
|
});
|
|
});
|
|
|
|
// ── ruview.vitals.get_heart_rate ──────────────────────────────────────────
|
|
|
|
describe("ruview.vitals.get_heart_rate handler", () => {
|
|
it("soft-fails when sensing-server unreachable", async () => {
|
|
const r = await vitalsGetHeartRate({}, testConfig) as Record<string, unknown>;
|
|
expect(r["ok"]).toBe(false);
|
|
expect(r["warn"]).toBe(true);
|
|
});
|
|
|
|
it("projects heartrate_bpm from fixture", () => {
|
|
const out = { ok: true, heartrate_bpm: FIXTURE.heartrate_bpm };
|
|
expect(out.heartrate_bpm).toBe(72.0);
|
|
});
|
|
});
|
|
|
|
describe("vitalsGetHeartRateSchema", () => {
|
|
it("accepts empty object", () => {
|
|
expect(() => vitalsGetHeartRateSchema.parse({})).not.toThrow();
|
|
});
|
|
});
|
|
|
|
// ── ruview.vitals.get_all ─────────────────────────────────────────────────
|
|
|
|
describe("ruview.vitals.get_all handler", () => {
|
|
it("soft-fails when sensing-server unreachable", async () => {
|
|
const r = await vitalsGetAll({}, testConfig) as Record<string, unknown>;
|
|
expect(r["ok"]).toBe(false);
|
|
expect(r["warn"]).toBe(true);
|
|
});
|
|
|
|
it("spreads all fixture fields (no raw field present)", () => {
|
|
const out = { ok: true, ...FIXTURE };
|
|
expect(out.node_id).toBe("cognitum-seed-1");
|
|
expect(out.presence).toBe(true);
|
|
expect(out.breathing_rate_bpm).toBe(14.5);
|
|
expect(out.heartrate_bpm).toBe(72.0);
|
|
expect(out.motion).toBe(0.12);
|
|
expect(out.zone_id).toBe("living_room");
|
|
expect((out as Record<string, unknown>)["raw"]).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("vitalsGetAllSchema", () => {
|
|
it("accepts node_id", () => {
|
|
const r = vitalsGetAllSchema.parse({ node_id: "seed-1" });
|
|
expect(r.node_id).toBe("seed-1");
|
|
});
|
|
});
|