feat: Playtube Stream Dock Plugin 1.0.0 (Ajazz AKP153E) mit Icon-Pack
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
'use strict';
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const net = require('node:net');
|
||||
const { PlaytubeClient, PlaytubeError, pipePath } = require('../src/playtubeClient');
|
||||
|
||||
const uniqueName = () => `Test${process.pid}${Date.now()}${Math.floor(Math.random() * 1e6)}`;
|
||||
const until = async (condition, timeoutMs = 3000) => {
|
||||
const start = Date.now();
|
||||
while (!condition()) {
|
||||
if (Date.now() - start > timeoutMs) throw new Error('Zeitueberschreitung im Test');
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
};
|
||||
|
||||
/** Fake-Playtube: beantwortet zeilenweise JSON-Befehle wie playtube/remote_control.py. */
|
||||
function startServer(name, handler) {
|
||||
const sockets = new Set();
|
||||
const server = net.createServer((socket) => {
|
||||
sockets.add(socket);
|
||||
socket.setEncoding('utf8');
|
||||
let buffer = '';
|
||||
socket.on('data', (chunk) => {
|
||||
buffer += chunk;
|
||||
let index;
|
||||
while ((index = buffer.indexOf('\n')) >= 0) {
|
||||
const line = buffer.slice(0, index);
|
||||
buffer = buffer.slice(index + 1);
|
||||
const request = JSON.parse(line);
|
||||
const reply = handler(request);
|
||||
if (reply) socket.write(`${JSON.stringify({ id: request.id, ...reply })}\n`);
|
||||
}
|
||||
});
|
||||
socket.on('close', () => sockets.delete(socket));
|
||||
socket.on('error', () => {});
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
server.listen(pipePath(name), () => resolve({
|
||||
dropClients: () => sockets.forEach((socket) => socket.destroy()),
|
||||
close: () => new Promise((done) => { sockets.forEach((s) => s.destroy()); server.close(done); }),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
test('pipePath bildet den Windows-Pipe-Namen', () => {
|
||||
assert.equal(pipePath('Playtube', 'win32'), '\\\\.\\pipe\\Playtube.Remote.Playtube');
|
||||
});
|
||||
|
||||
test('verbindet sich, sendet Befehle und liefert Antworten', async () => {
|
||||
const name = uniqueName();
|
||||
const fake = await startServer(name, (request) => {
|
||||
if (request.cmd === 'status') return { ok: true, state: { activeTab: 'music' } };
|
||||
if (request.cmd === 'list_playlists') return { ok: true, playlists: [{ id: 'LM', title: 'Liked' }] };
|
||||
if (request.cmd === 'boom') return { ok: false, error: 'kaputt' };
|
||||
return { ok: true, echo: request };
|
||||
});
|
||||
const client = new PlaytubeClient({ pipeNames: [name] });
|
||||
client.start();
|
||||
try {
|
||||
await until(() => client.connected);
|
||||
assert.deepEqual(await client.status(), { activeTab: 'music' });
|
||||
assert.deepEqual(await client.listPlaylists(), [{ id: 'LM', title: 'Liked' }]);
|
||||
const echoed = await client.request('volume_change', { target: 'auto', value: 5, skip: undefined });
|
||||
assert.deepEqual(echoed.echo, { id: echoed.id, cmd: 'volume_change', target: 'auto', value: 5 });
|
||||
await assert.rejects(client.request('boom'),
|
||||
(error) => error instanceof PlaytubeError && error.code === 'remote' && /kaputt/.test(error.message));
|
||||
} finally {
|
||||
client.stop();
|
||||
await fake.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('ohne Verbindung wird sofort abgelehnt', async () => {
|
||||
const client = new PlaytubeClient({ pipeNames: [uniqueName()] });
|
||||
await assert.rejects(client.request('status'), (error) => error.code === 'not_connected');
|
||||
});
|
||||
|
||||
test('Timeout, wenn Playtube nicht antwortet', async () => {
|
||||
const name = uniqueName();
|
||||
const fake = await startServer(name, () => null);
|
||||
const client = new PlaytubeClient({ pipeNames: [name], requestTimeoutMs: 80 });
|
||||
client.start();
|
||||
try {
|
||||
await until(() => client.connected);
|
||||
await assert.rejects(client.request('status'), (error) => error.code === 'timeout');
|
||||
} finally {
|
||||
client.stop();
|
||||
await fake.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('Verbindungsabbruch: offene Anfragen scheitern, danach automatische Wiederverbindung', async () => {
|
||||
const name = uniqueName();
|
||||
const fake = await startServer(name, () => null);
|
||||
const client = new PlaytubeClient({ pipeNames: [name], backoffMs: [20] });
|
||||
const events = [];
|
||||
client.on('connected', () => events.push('connected'));
|
||||
client.on('disconnected', () => events.push('disconnected'));
|
||||
client.start();
|
||||
try {
|
||||
await until(() => client.connected);
|
||||
const pending = client.request('status');
|
||||
fake.dropClients();
|
||||
await assert.rejects(pending, (error) => error.code === 'disconnected');
|
||||
await until(() => events.filter((e) => e === 'connected').length === 2);
|
||||
assert.deepEqual(events, ['connected', 'disconnected', 'connected']);
|
||||
} finally {
|
||||
client.stop();
|
||||
await fake.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('probiert mehrere Pipe-Namen der Reihe nach und verbindet Playtube spaeter', async () => {
|
||||
const missing = uniqueName();
|
||||
const present = uniqueName();
|
||||
const client = new PlaytubeClient({ pipeNames: [missing, present], backoffMs: [20] });
|
||||
client.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 60));
|
||||
assert.equal(client.connected, false);
|
||||
const fake = await startServer(present, () => ({ ok: true }));
|
||||
try {
|
||||
await until(() => client.connected);
|
||||
assert.equal(client.pipeName, present);
|
||||
} finally {
|
||||
client.stop();
|
||||
await fake.close();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user