mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
Fix mpv protocol/transport typing and test regressions
This commit is contained in:
@@ -172,19 +172,19 @@ test("dispatchMpvProtocolMessage pauses on sub-end when pendingPauseAtSubEnd is
|
||||
|
||||
test("splitMpvMessagesFromBuffer parses complete lines and preserves partial buffer", () => {
|
||||
const parsed = splitMpvMessagesFromBuffer(
|
||||
"{\"event\":\"shutdown\"}\\n{\"event\":\"property-change\",\"name\":\"media-title\",\"data\":\"x\"}\\n{\"partial\"",
|
||||
'{"event":"shutdown"}\n{"event":"property-change","name":"media-title","data":"x"}\n{"partial"',
|
||||
);
|
||||
|
||||
assert.equal(parsed.messages.length, 2);
|
||||
assert.equal(parsed.nextBuffer, "{\"partial\"");
|
||||
assert.equal(parsed.messages[0].event, "shutdown");
|
||||
assert.equal(parsed.messages[1].name, "property-change");
|
||||
assert.equal(parsed.messages[1].name, "media-title");
|
||||
});
|
||||
|
||||
test("splitMpvMessagesFromBuffer reports invalid JSON lines", () => {
|
||||
const errors: Array<{ line: string; error?: string }> = [];
|
||||
|
||||
splitMpvMessagesFromBuffer("{\"event\":\"x\"}\\n{invalid}\\n", undefined, (line, error) => {
|
||||
splitMpvMessagesFromBuffer('{"event":"x"}\n{invalid}\n', undefined, (line, error) => {
|
||||
errors.push({ line, error: String(error) });
|
||||
});
|
||||
|
||||
|
||||
@@ -138,9 +138,10 @@ export async function dispatchMpvProtocolMessage(
|
||||
end: deps.getCurrentSubEnd(),
|
||||
});
|
||||
} else if (msg.name === "sub-end") {
|
||||
deps.setCurrentSubEnd((msg.data as number) || 0);
|
||||
if (deps.getPendingPauseAtSubEnd() && deps.getCurrentSubEnd() > 0) {
|
||||
deps.setPauseAtTime(deps.getCurrentSubEnd());
|
||||
const subEnd = (msg.data as number) || 0;
|
||||
deps.setCurrentSubEnd(subEnd);
|
||||
if (deps.getPendingPauseAtSubEnd() && subEnd > 0) {
|
||||
deps.setPauseAtTime(subEnd);
|
||||
deps.setPendingPauseAtSubEnd(false);
|
||||
deps.sendCommand({ command: ["set_property", "pause", false] });
|
||||
}
|
||||
|
||||
@@ -32,16 +32,6 @@ class FakeSocket extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
function withSocketMock<T>(fn: () => T): T {
|
||||
const OriginalSocket = net.Socket;
|
||||
(net as any).Socket = FakeSocket as any;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
(net as any).Socket = OriginalSocket;
|
||||
}
|
||||
}
|
||||
|
||||
const wait = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
test("getMpvReconnectDelay follows existing reconnect ramp", () => {
|
||||
@@ -104,140 +94,134 @@ test("scheduleMpvReconnect clears existing timer and increments attempt", () =>
|
||||
|
||||
test("MpvSocketTransport connects and sends payloads over a live socket", async () => {
|
||||
const events: string[] = [];
|
||||
await withSocketMock(async () => {
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
events.push("connect");
|
||||
},
|
||||
onData: () => {
|
||||
events.push("data");
|
||||
},
|
||||
onError: () => {
|
||||
events.push("error");
|
||||
},
|
||||
onClose: () => {
|
||||
events.push("close");
|
||||
},
|
||||
});
|
||||
|
||||
const payload: MpvSocketMessagePayload = {
|
||||
command: ["sub-seek", 1],
|
||||
request_id: 1,
|
||||
};
|
||||
|
||||
assert.equal(transport.send(payload), false);
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.includes("connect"), true);
|
||||
assert.equal(transport.send(payload), true);
|
||||
|
||||
const fakeSocket = transport.getSocket() as unknown as FakeSocket;
|
||||
assert.equal(fakeSocket.connectedPaths.at(0), "/tmp/mpv.sock");
|
||||
assert.equal(fakeSocket.writePayloads.length, 1);
|
||||
assert.equal(fakeSocket.writePayloads.at(0), `${JSON.stringify(payload)}\n`);
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
events.push("connect");
|
||||
},
|
||||
onData: () => {
|
||||
events.push("data");
|
||||
},
|
||||
onError: () => {
|
||||
events.push("error");
|
||||
},
|
||||
onClose: () => {
|
||||
events.push("close");
|
||||
},
|
||||
socketFactory: () => new FakeSocket() as unknown as net.Socket,
|
||||
});
|
||||
|
||||
const payload: MpvSocketMessagePayload = {
|
||||
command: ["sub-seek", 1],
|
||||
request_id: 1,
|
||||
};
|
||||
|
||||
assert.equal(transport.send(payload), false);
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.includes("connect"), true);
|
||||
assert.equal(transport.send(payload), true);
|
||||
|
||||
const fakeSocket = transport.getSocket() as unknown as FakeSocket;
|
||||
assert.equal(fakeSocket.connectedPaths.at(0), "/tmp/mpv.sock");
|
||||
assert.equal(fakeSocket.writePayloads.length, 1);
|
||||
assert.equal(fakeSocket.writePayloads.at(0), `${JSON.stringify(payload)}\n`);
|
||||
});
|
||||
|
||||
test("MpvSocketTransport reports lifecycle transitions and callback order", async () => {
|
||||
const events: string[] = [];
|
||||
const fakeError = new Error("boom");
|
||||
|
||||
await withSocketMock(async () => {
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
events.push("connect");
|
||||
},
|
||||
onData: () => {
|
||||
events.push("data");
|
||||
},
|
||||
onError: () => {
|
||||
events.push("error");
|
||||
},
|
||||
onClose: () => {
|
||||
events.push("close");
|
||||
},
|
||||
});
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
const socket = transport.getSocket() as unknown as FakeSocket;
|
||||
socket.emit("error", fakeError);
|
||||
socket.emit("data", Buffer.from("{}"));
|
||||
socket.destroy();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.includes("connect"), true);
|
||||
assert.equal(events.includes("data"), true);
|
||||
assert.equal(events.includes("error"), true);
|
||||
assert.equal(events.includes("close"), true);
|
||||
assert.equal(transport.isConnected, false);
|
||||
assert.equal(transport.isConnecting, false);
|
||||
assert.equal(socket.destroyed, true);
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
events.push("connect");
|
||||
},
|
||||
onData: () => {
|
||||
events.push("data");
|
||||
},
|
||||
onError: () => {
|
||||
events.push("error");
|
||||
},
|
||||
onClose: () => {
|
||||
events.push("close");
|
||||
},
|
||||
socketFactory: () => new FakeSocket() as unknown as net.Socket,
|
||||
});
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
const socket = transport.getSocket() as unknown as FakeSocket;
|
||||
socket.emit("error", fakeError);
|
||||
socket.emit("data", Buffer.from("{}"));
|
||||
socket.destroy();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.includes("connect"), true);
|
||||
assert.equal(events.includes("data"), true);
|
||||
assert.equal(events.includes("error"), true);
|
||||
assert.equal(events.includes("close"), true);
|
||||
assert.equal(transport.isConnected, false);
|
||||
assert.equal(transport.isConnecting, false);
|
||||
assert.equal(socket.destroyed, true);
|
||||
});
|
||||
|
||||
test("MpvSocketTransport ignores connect requests while already connecting or connected", async () => {
|
||||
const events: string[] = [];
|
||||
|
||||
await withSocketMock(async () => {
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
events.push("connect");
|
||||
},
|
||||
onData: () => {
|
||||
events.push("data");
|
||||
},
|
||||
onError: () => {
|
||||
events.push("error");
|
||||
},
|
||||
onClose: () => {
|
||||
events.push("close");
|
||||
},
|
||||
});
|
||||
|
||||
transport.connect();
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.includes("connect"), true);
|
||||
const socket = transport.getSocket() as unknown as FakeSocket;
|
||||
socket.emit("close");
|
||||
await wait();
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.filter((entry) => entry === "connect").length, 2);
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
events.push("connect");
|
||||
},
|
||||
onData: () => {
|
||||
events.push("data");
|
||||
},
|
||||
onError: () => {
|
||||
events.push("error");
|
||||
},
|
||||
onClose: () => {
|
||||
events.push("close");
|
||||
},
|
||||
socketFactory: () => new FakeSocket() as unknown as net.Socket,
|
||||
});
|
||||
|
||||
transport.connect();
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.includes("connect"), true);
|
||||
const socket = transport.getSocket() as unknown as FakeSocket;
|
||||
socket.emit("close");
|
||||
await wait();
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
|
||||
assert.equal(events.filter((entry) => entry === "connect").length, 2);
|
||||
});
|
||||
|
||||
test("MpvSocketTransport.shutdown clears socket and lifecycle flags", async () => {
|
||||
await withSocketMock(async () => {
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
},
|
||||
onData: () => {
|
||||
},
|
||||
onError: () => {
|
||||
},
|
||||
onClose: () => {
|
||||
},
|
||||
});
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
assert.equal(transport.isConnected, true);
|
||||
|
||||
transport.shutdown();
|
||||
assert.equal(transport.isConnected, false);
|
||||
assert.equal(transport.isConnecting, false);
|
||||
assert.equal(transport.getSocket(), null);
|
||||
const transport = new MpvSocketTransport({
|
||||
socketPath: "/tmp/mpv.sock",
|
||||
onConnect: () => {
|
||||
},
|
||||
onData: () => {
|
||||
},
|
||||
onError: () => {
|
||||
},
|
||||
onClose: () => {
|
||||
},
|
||||
socketFactory: () => new FakeSocket() as unknown as net.Socket,
|
||||
});
|
||||
|
||||
transport.connect();
|
||||
await wait();
|
||||
assert.equal(transport.isConnected, true);
|
||||
|
||||
transport.shutdown();
|
||||
assert.equal(transport.isConnected, false);
|
||||
assert.equal(transport.isConnecting, false);
|
||||
assert.equal(transport.getSocket(), null);
|
||||
});
|
||||
|
||||
@@ -73,11 +73,13 @@ export interface MpvSocketTransportOptions {
|
||||
onData: (data: Buffer) => void;
|
||||
onError: (error: Error) => void;
|
||||
onClose: () => void;
|
||||
socketFactory?: () => net.Socket;
|
||||
}
|
||||
|
||||
export class MpvSocketTransport {
|
||||
private socketPath: string;
|
||||
private readonly callbacks: MpvSocketTransportEvents;
|
||||
private readonly socketFactory: () => net.Socket;
|
||||
private socketRef: net.Socket | null = null;
|
||||
public socket: net.Socket | null = null;
|
||||
public connected = false;
|
||||
@@ -85,6 +87,7 @@ export class MpvSocketTransport {
|
||||
|
||||
constructor(options: MpvSocketTransportOptions) {
|
||||
this.socketPath = options.socketPath;
|
||||
this.socketFactory = options.socketFactory ?? (() => new net.Socket());
|
||||
this.callbacks = {
|
||||
onConnect: options.onConnect,
|
||||
onData: options.onData,
|
||||
@@ -107,7 +110,7 @@ export class MpvSocketTransport {
|
||||
}
|
||||
|
||||
this.connecting = true;
|
||||
this.socketRef = new net.Socket();
|
||||
this.socketRef = this.socketFactory();
|
||||
this.socket = this.socketRef;
|
||||
|
||||
this.socketRef.on("connect", () => {
|
||||
|
||||
Reference in New Issue
Block a user