roon-mcp 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { ZoneSubscription } from "./ZoneSubscription.js";
4
+ /** Build a zone with sensible defaults so tests can focus on the bits that
5
+ * matter (state, now_playing). */
6
+ function zone(partial) {
7
+ return {
8
+ display_name: partial.zone_id,
9
+ state: "stopped",
10
+ outputs: [{ output_id: `o:${partial.zone_id}`, zone_id: partial.zone_id, display_name: partial.zone_id }],
11
+ ...partial,
12
+ };
13
+ }
14
+ /**
15
+ * Builds a transport stub whose `subscribe_zones` callback can be invoked
16
+ * manually to simulate Roon's `Subscribed` / `Changed` / `Unsubscribed`
17
+ * events. Returns the subscription plus a `push` helper to drive it.
18
+ */
19
+ function buildSubscription(initial = []) {
20
+ let cb;
21
+ const transport = {
22
+ subscribe_zones: (callback) => {
23
+ cb = callback;
24
+ },
25
+ };
26
+ const sub = new ZoneSubscription(transport, "core-1");
27
+ sub.start();
28
+ // Pre-load the cache so reads don't fall through to a one-shot RPC.
29
+ if (initial.length)
30
+ cb?.("Subscribed", { zones: initial });
31
+ return { sub, push: (response, body) => cb?.(response, body) };
32
+ }
33
+ test("Subscribed populates the cache from the full snapshot", () => {
34
+ const { sub } = buildSubscription([
35
+ zone({ zone_id: "z1", state: "playing", now_playing: { two_line: { line1: "A" } } }),
36
+ ]);
37
+ // getSnapshot should return the cached body without invoking a fallback.
38
+ void sub
39
+ .getSnapshot(() => {
40
+ throw new Error("fallback should not be called when the cache is fresh");
41
+ })
42
+ .then((body) => {
43
+ assert.equal(body.zones?.length, 1);
44
+ assert.equal(body.zones?.[0]?.zone_id, "z1");
45
+ });
46
+ });
47
+ test("getSnapshot falls back to a one-shot RPC when the cache is empty", async () => {
48
+ const transport = {
49
+ subscribe_zones: () => { },
50
+ };
51
+ const sub = new ZoneSubscription(transport, "core-1");
52
+ sub.start();
53
+ const fallback = await sub.getSnapshot(async () => ({ zones: [zone({ zone_id: "lazy" })] }));
54
+ assert.equal(fallback.zones?.[0]?.zone_id, "lazy");
55
+ });
56
+ test("Changed merges zones_added, zones_changed, and zones_removed into the cache", () => {
57
+ const { sub, push } = buildSubscription([
58
+ zone({ zone_id: "z1" }),
59
+ zone({ zone_id: "z2" }),
60
+ ]);
61
+ push("Changed", {
62
+ zones_removed: ["z1"],
63
+ zones_added: [zone({ zone_id: "z3" })],
64
+ zones_changed: [zone({ zone_id: "z2", state: "playing" })],
65
+ });
66
+ void sub
67
+ .getSnapshot(() => Promise.resolve({ zones: [] }))
68
+ .then((body) => {
69
+ const ids = (body.zones ?? []).map((z) => z.zone_id).sort();
70
+ assert.deepEqual(ids, ["z2", "z3"]);
71
+ const z2 = body.zones?.find((z) => z.zone_id === "z2");
72
+ assert.equal(z2?.state, "playing");
73
+ });
74
+ });
75
+ test("waitForZoneChange resolves on a Changed event with a different fingerprint", async () => {
76
+ const { sub, push } = buildSubscription([
77
+ zone({
78
+ zone_id: "z1",
79
+ state: "playing",
80
+ now_playing: { two_line: { line1: "Old Track", line2: "Old Artist" } },
81
+ }),
82
+ ]);
83
+ // Capture the pre-action fingerprint manually.
84
+ const before = (await sub.getSnapshot(() => Promise.resolve({ zones: [] }))).zones
85
+ ? { state: "playing", title: "Old Track", artist: "Old Artist" }
86
+ : undefined;
87
+ assert.ok(before);
88
+ // Simulate the new track landing.
89
+ const waiter = sub.waitForZoneChange("z1", before);
90
+ push("Changed", {
91
+ zones_changed: [
92
+ zone({
93
+ zone_id: "z1",
94
+ state: "playing",
95
+ now_playing: { two_line: { line1: "New Track", line2: "New Artist" } },
96
+ }),
97
+ ],
98
+ });
99
+ const after = await waiter;
100
+ const z1 = after.zones?.find((z) => z.zone_id === "z1");
101
+ assert.equal(z1?.now_playing?.two_line?.line1, "New Track");
102
+ });
103
+ test("waitForZoneChange resolves immediately when the change already landed", async () => {
104
+ const { sub, push } = buildSubscription([
105
+ zone({ zone_id: "z1", state: "stopped" }),
106
+ ]);
107
+ // Push the new state BEFORE the wait — the synchronous check in
108
+ // waitForZoneChange should catch it.
109
+ push("Changed", {
110
+ zones_changed: [
111
+ zone({
112
+ zone_id: "z1",
113
+ state: "playing",
114
+ now_playing: { two_line: { line1: "New Track" } },
115
+ }),
116
+ ],
117
+ });
118
+ const before = { state: "stopped", title: undefined, artist: undefined, album: undefined };
119
+ const after = await sub.waitForZoneChange("z1", before);
120
+ const z1 = after.zones?.find((z) => z.zone_id === "z1");
121
+ assert.equal(z1?.state, "playing");
122
+ });
123
+ test("waitForZoneChange times out and returns the latest snapshot", async () => {
124
+ const { sub } = buildSubscription([
125
+ zone({ zone_id: "z1", state: "stopped" }),
126
+ ]);
127
+ const before = { state: "stopped", title: undefined, artist: undefined, album: undefined };
128
+ const after = await sub.waitForZoneChange("z1", before, 5);
129
+ // No change event ever fires; the waiter returns the current snapshot
130
+ // so the caller can still report *something* useful.
131
+ assert.equal(after.zones?.length, 1);
132
+ });
133
+ test("waitForZoneChange does not match a seek-position-only update", async () => {
134
+ // Issue: a high-frequency `seek_position` tick should not satisfy a
135
+ // wait — the agent is waiting for the *track* to change, not for the
136
+ // playback position to advance. node-roon-api-transport's internal cache
137
+ // already updates `seek_position` on `Changed`/`zones_seek_changed`; we
138
+ // only consume `zones_changed` here, so a seek-only tick doesn't move
139
+ // the fingerprint. This test pins that behavior: a Changed event with
140
+ // the same track must not resolve the waiter before the timeout.
141
+ const { sub, push } = buildSubscription([
142
+ zone({
143
+ zone_id: "z1",
144
+ state: "playing",
145
+ now_playing: { two_line: { line1: "A Walk", line2: "Tycho" }, seek_position: 10 },
146
+ }),
147
+ ]);
148
+ const before = { state: "playing", title: "A Walk", artist: "Tycho", album: undefined };
149
+ const waiter = sub.waitForZoneChange("z1", before, 5);
150
+ // Same track, just a position tick — should not satisfy the wait.
151
+ push("Changed", {
152
+ zones_changed: [
153
+ zone({
154
+ zone_id: "z1",
155
+ state: "playing",
156
+ now_playing: { two_line: { line1: "A Walk", line2: "Tycho" }, seek_position: 20 },
157
+ }),
158
+ ],
159
+ });
160
+ const after = await waiter;
161
+ const z1 = after.zones?.find((z) => z.zone_id === "z1");
162
+ // The cache is updated (the snapshot reflects the new position), but the
163
+ // wait resolved via timeout — the caller's fingerprint would not match.
164
+ assert.equal(z1?.now_playing?.seek_position, 20);
165
+ });
166
+ test("Unsubscribed drops the cache and resolves pending waiters", async () => {
167
+ const { sub, push } = buildSubscription([
168
+ zone({ zone_id: "z1", state: "stopped" }),
169
+ ]);
170
+ const before = { state: "stopped", title: undefined, artist: undefined, album: undefined };
171
+ const waiter = sub.waitForZoneChange("z1", before, 5_000);
172
+ push("Unsubscribed", {});
173
+ const after = await waiter;
174
+ // Cache cleared; waiter resolves with an empty snapshot.
175
+ assert.equal(after.zones?.length ?? 0, 0);
176
+ });
177
+ test("stop() resolves any pending waiters with the last known snapshot", async () => {
178
+ const { sub } = buildSubscription([zone({ zone_id: "z1", state: "stopped" })]);
179
+ const before = { state: "stopped", title: undefined, artist: undefined, album: undefined };
180
+ const waiter = sub.waitForZoneChange("z1", before, 5_000);
181
+ sub.stop();
182
+ const after = await waiter;
183
+ assert.equal(after.zones?.length ?? 0, 0);
184
+ });
185
+ //# sourceMappingURL=ZoneSubscription.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZoneSubscription.test.js","sourceRoot":"","sources":["../src/ZoneSubscription.test.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -77,7 +77,8 @@ export interface PlaybackResult {
77
77
  itemKey: string;
78
78
  reason: string;
79
79
  }>;
80
- /** Best-effort "now playing" line read just after the action; may be stale. */
80
+ /** Current track line read after the action; waits for Roon's next zone
81
+ * event so it reflects the new track rather than the previous one. */
81
82
  nowPlaying?: string;
82
83
  message?: string;
83
84
  }
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,qEAAqE;AA6JrE,MAAM,OAAO,YAAa,SAAQ,KAAK;IAEnB;IAEA;IAHlB,YACkB,IAAsB,EACtC,OAAe,EACC,OAAiB;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAkB;QAEtB,YAAO,GAAP,OAAO,CAAU;QAGjC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,qEAAqE;AA8JrE,MAAM,OAAO,YAAa,SAAQ,KAAK;IAEnB;IAEA;IAHlB,YACkB,IAAsB,EACtC,OAAe,EACC,OAAiB;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAkB;QAEtB,YAAO,GAAP,OAAO,CAAU;QAGjC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roon-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "An MCP server to control the Roon music player.",
5
5
  "license": "MIT",
6
6
  "author": "Christoph Halang",