96 lines
5.5 KiB
TypeScript
96 lines
5.5 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import { DlnaDmsClient, DlnaDmsIntegration } from '../../ts/integrations/dlna_dms/index.js';
|
|
|
|
const descriptionXml = `<?xml version="1.0"?>
|
|
<root>
|
|
<device>
|
|
<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>
|
|
<friendlyName>Fixture Media Server</friendlyName>
|
|
<manufacturer>Example</manufacturer>
|
|
<modelName>DMS Fixture</modelName>
|
|
<UDN>uuid:fixture-server</UDN>
|
|
<serviceList>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
|
|
<serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId>
|
|
<controlURL>/ContentDirectory/control</controlURL>
|
|
</service>
|
|
<service>
|
|
<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
|
|
<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
|
|
<controlURL>/ConnectionManager/control</controlURL>
|
|
</service>
|
|
</serviceList>
|
|
</device>
|
|
</root>`;
|
|
|
|
const rootDidl = `<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"><container id="0" parentID="-1" childCount="2"><dc:title>Fixture Media Server</dc:title><upnp:class>object.container</upnp:class></container></DIDL-Lite>`;
|
|
const childrenDidl = `<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"><container id="music" parentID="0" childCount="12"><dc:title>Music</dc:title><upnp:class>object.container.album.musicAlbum</upnp:class></container><item id="track-1" parentID="0"><dc:title>Track One</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><res protocolInfo="http-get:*:audio/mpeg:*">/media/track.mp3</res></item></DIDL-Lite>`;
|
|
|
|
const soapEnvelope = (actionArg: string, bodyArg: string): string => `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><u:${actionArg}Response xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">${bodyArg}</u:${actionArg}Response></s:Body></s:Envelope>`;
|
|
|
|
tap.test('refreshes a live DLNA DMS snapshot from native UPnP responses', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const requests: string[] = [];
|
|
globalThis.fetch = (async (inputArg: RequestInfo | URL, initArg?: RequestInit) => {
|
|
const url = String(inputArg);
|
|
requests.push(`${initArg?.method || 'GET'} ${url}`);
|
|
if (url.endsWith('/rootDesc.xml')) {
|
|
return new Response(descriptionXml, { status: 200 });
|
|
}
|
|
const body = String(initArg?.body || '');
|
|
if (body.includes('GetSystemUpdateID')) {
|
|
return new Response(soapEnvelope('GetSystemUpdateID', '<SystemUpdateID>11</SystemUpdateID>'), { status: 200 });
|
|
}
|
|
if (body.includes('GetSearchCapabilities')) {
|
|
return new Response(soapEnvelope('GetSearchCapabilities', '<SearchCaps>dc:title</SearchCaps>'), { status: 200 });
|
|
}
|
|
if (body.includes('GetSortCapabilities')) {
|
|
return new Response(soapEnvelope('GetSortCapabilities', '<SortCaps>dc:title</SortCaps>'), { status: 200 });
|
|
}
|
|
if (body.includes('GetProtocolInfo')) {
|
|
return new Response(soapEnvelope('GetProtocolInfo', '<Source>http-get:*:audio/mpeg:*</Source><Sink></Sink>'), { status: 200 });
|
|
}
|
|
if (body.includes('GetCurrentConnectionIDs')) {
|
|
return new Response(soapEnvelope('GetCurrentConnectionIDs', '<ConnectionIDs>0</ConnectionIDs>'), { status: 200 });
|
|
}
|
|
if (body.includes('BrowseMetadata')) {
|
|
return new Response(soapEnvelope('Browse', `<Result>${escapeXml(rootDidl)}</Result><NumberReturned>1</NumberReturned><TotalMatches>1</TotalMatches><UpdateID>11</UpdateID>`), { status: 200 });
|
|
}
|
|
if (body.includes('BrowseDirectChildren')) {
|
|
return new Response(soapEnvelope('Browse', `<Result>${escapeXml(childrenDidl)}</Result><NumberReturned>2</NumberReturned><TotalMatches>2</TotalMatches><UpdateID>11</UpdateID>`), { status: 200 });
|
|
}
|
|
return new Response('', { status: 404 });
|
|
}) as typeof globalThis.fetch;
|
|
|
|
try {
|
|
const client = new DlnaDmsClient({ location: 'http://192.168.1.60:8200/rootDesc.xml' });
|
|
const snapshot = await client.refresh();
|
|
expect(snapshot.online).toBeTrue();
|
|
expect(snapshot.source).toEqual('upnp');
|
|
expect(snapshot.device.friendlyName).toEqual('Fixture Media Server');
|
|
expect(snapshot.status.systemUpdateId).toEqual(11);
|
|
expect(snapshot.libraries[0].title).toEqual('Music');
|
|
expect(snapshot.contentDirectory.children[1].canPlay).toBeTrue();
|
|
expect(snapshot.contentDirectory.children[1].resources[0].uri).toEqual('http://192.168.1.60:8200/media/track.mp3');
|
|
expect(requests.some((requestArg) => requestArg.includes('/ContentDirectory/control'))).toBeTrue();
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
tap.test('does not report live UPnP success without endpoint, snapshot, or client', async () => {
|
|
const runtime = await new DlnaDmsIntegration().setup({}, {});
|
|
const result = await runtime.callService!({ domain: 'dlna_dms', service: 'refresh', target: {} });
|
|
expect(result.success).toBeFalse();
|
|
const snapshot = result.data as { online: boolean; source: string; error?: string };
|
|
expect(snapshot.online).toBeFalse();
|
|
expect(snapshot.source).toEqual('runtime');
|
|
expect(snapshot.error).toContain('requires a device description URL');
|
|
await runtime.destroy();
|
|
});
|
|
|
|
const escapeXml = (valueArg: string): string => valueArg.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
|
|
export default tap.start();
|