/* eslint-env mocha */ /* eslint no-unused-vars: "off" */ const assert = require('assert') const SONOS = require('../') const Sonos = SONOS.Sonos const nock = require('nock') const Helpers = require('../lib/helpers') const generateResponse = function (responseTag, serviceName, responseBody) { const soapBody = `${(responseBody || null)}` return Helpers.CreateSoapEnvelop(soapBody) } const mockRequest = function (endpoint, action, requestBody, responseTag, serviceName, responseBody) { return nock('http://localhost:1400', { reqheaders: { soapaction: action } }) .post(endpoint, Helpers.CreateSoapEnvelop(requestBody)) .reply(200, generateResponse(responseTag, serviceName, responseBody)) } describe('Sonos - Mock', function () { describe('play()', function () { it('should generate play command', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Play"', '01', 'PlayResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) return sonos.play() }) it('should accept a uri add => Zone Info => Select queue => seek => play', function () { this.skip() mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp301', 'AddURIToQueueResponse', 'AVTransport', '111' ) mockRequest('/DeviceProperties/Control', '"urn:schemas-upnp-org:service:DeviceProperties:1#GetZoneInfo"', '', 'GetZoneInfoResponse', 'DeviceProperties', 'xx:xx:xx:xx:xx' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"', '0x-rincon-queue:RINCON_xxxxxxxxxx01400#0', 'SetAVTransportURIResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Seek"', '0TRACK_NR1', 'SeekResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Play"', '01', 'PlayResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) return sonos.play('http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3') }) it('should be able to accept an object instead of uri', function () { this.skip() mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3test01', 'AddURIToQueueResponse', 'AVTransport', '111' ) mockRequest('/DeviceProperties/Control', '"urn:schemas-upnp-org:service:DeviceProperties:1#GetZoneInfo"', '', 'GetZoneInfoResponse', 'DeviceProperties', 'xx:xx:xx:xx:xx' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"', '0x-rincon-queue:RINCON_xxxxxxxxxx01400#0', 'SetAVTransportURIResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Seek"', '0TRACK_NR1', 'SeekResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Play"', '01', 'PlayResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.play({ uri: 'http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3', metadata: 'test' }) }) }) describe('queue()', function () { it('should generate queue command', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp301', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) return sonos.queue('http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3') }) it('should encode html characters in uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-sonos-spotify:spotify%3atrack%3a01Bz4Mijhe7m7qRvq2Ujpn?sid=12&flags=8224&sn=201', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) return sonos.queue('x-sonos-spotify:spotify%3atrack%3a01Bz4Mijhe7m7qRvq2Ujpn?sid=12&flags=8224&sn=2') }) it('should accept object in place of uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3<test>"hello"</test>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) return sonos.queue({ uri: 'http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3', metadata: '"hello"' }) }) it('should accept a Spotify track uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-sonos-spotify:spotify%3atrack%3a1AhDOtG9vPSOmsWgNW0BEY<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="00032020spotify%3atrack%3a1AhDOtG9vPSOmsWgNW0BEY" restricted="true"><dc:title></dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON3079_X_#Svc3079-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) return sonos.queue('spotify:track:1AhDOtG9vPSOmsWgNW0BEY') }) it('should accept a Spotify EU track uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-sonos-spotify:spotify%3atrack%3a1AhDOtG9vPSOmsWgNW0BEY<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="00032020spotify%3atrack%3a1AhDOtG9vPSOmsWgNW0BEY" restricted="true"><dc:title></dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON2311_X_#Svc2311-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.setSpotifyRegion(SONOS.SpotifyRegion.EU) return sonos.queue('spotify:track:1AhDOtG9vPSOmsWgNW0BEY') }) it('should accept a Spotify album uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-rincon-cpcontainer:0004206cspotify%3aalbum%3a1TSZDcvlPtAnekTaItI3qO<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="0004206cspotify%3aalbum%3a1TSZDcvlPtAnekTaItI3qO" restricted="true"><dc:title></dc:title><upnp:class>object.container.album.musicAlbum</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON3079_X_#Svc3079-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.queue('spotify:album:1TSZDcvlPtAnekTaItI3qO') }) it('should accept a Spotify artist top tracks uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-rincon-cpcontainer:000e206cspotify%3aartistTopTracks%3a1dfeR4HaWDbWqFHLkxsg1d<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="000e206cspotify%3aartistTopTracks%3a1dfeR4HaWDbWqFHLkxsg1d" restricted="true"><dc:title></dc:title><upnp:class>object.container.playlistContainer</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON3079_X_#Svc3079-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.queue('spotify:artistTopTracks:1dfeR4HaWDbWqFHLkxsg1d') }) it('should accept a Spotify user public playlist uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-rincon-cpcontainer:10062a6cspotify%3auser%3a26iFraqozskd5POrzg68pr?sid=9&flags=10860&sn=7<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="10062a6cspotify%3auser%3a26iFraqozskd5POrzg68pr" parentID="10082664playlists" restricted="true"><dc:title>User playlist</dc:title><upnp:class>object.container.playlistContainer</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON3079_X_#Svc3079-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.queue('spotify:user:26iFraqozskd5POrzg68pr') }) it('should accept a Spotify EU user public playlist uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-rincon-cpcontainer:10062a6cspotify%3auser%3a26iFraqozskd5POrzg68pr?sid=9&flags=10860&sn=7<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="10062a6cspotify%3auser%3a26iFraqozskd5POrzg68pr" parentID="10082664playlists" restricted="true"><dc:title>User playlist</dc:title><upnp:class>object.container.playlistContainer</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON2311_X_#Svc2311-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.setSpotifyRegion(SONOS.SpotifyRegion.EU) sonos.queue('spotify:user:26iFraqozskd5POrzg68pr') }) }) describe('playWithoutQueue()', function () { it('should accept a Spotify artist radio uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-sonosapi-radio:spotify%3aartistRadio%3a1dfeR4HaWDbWqFHLkxsg1d?sid=12&flags=8300&sn=5<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="100c206cspotify%3aartistRadio%3a1dfeR4HaWDbWqFHLkxsg1d" parentID="10052064spotify%3aartist%3a1dfeR4HaWDbWqFHLkxsg1d" restricted="true"><dc:title>Artist Radio</dc:title><upnp:class>object.item.audioItem.audioBroadcast.#artistRadio</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON3079_X_#Svc3079-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.queue('spotify:artistRadio:1dfeR4HaWDbWqFHLkxsg1d') }) it('should accept a Spotify EU artist radio uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"', '0x-sonosapi-radio:spotify%3aartistRadio%3a1dfeR4HaWDbWqFHLkxsg1d?sid=12&flags=8300&sn=5<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="100c206cspotify%3aartistRadio%3a1dfeR4HaWDbWqFHLkxsg1d" parentID="10052064spotify%3aartist%3a1dfeR4HaWDbWqFHLkxsg1d" restricted="true"><dc:title>Artist Radio</dc:title><upnp:class>object.item.audioItem.audioBroadcast.#artistRadio</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON2311_X_#Svc2311-0-Token</desc></item></DIDL-Lite>01', 'AddURIToQueueResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.setSpotifyRegion(SONOS.SpotifyRegion.EU) sonos.queue('spotify:artistRadio:1dfeR4HaWDbWqFHLkxsg1d') }) }) describe('playTuneinRadio()', function () { it('should generate play command', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"', '0x-sonosapi-stream:34682?sid=254&flags=8224&sn=0<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="F0009202034682" parentID="L" restricted="true"><dc:title>88.5 | Jazz24 (Jazz)</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON65031_</desc></item></DIDL-Lite>', 'SetAVTransportURIResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Play"', '01', 'PlayResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.playTuneinRadio('34682', '88.5 | Jazz24 (Jazz)') }) }) describe('playSpotifyRadio()', function () { it('should generate play command', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"', '0x-sonosapi-radio:spotify%3aartistRadio%3a1dfeR4HaWDbWqFHLkxsg1d?sid=12&flags=8300&sn=5<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="100c206cspotify%3aartistRadio%3a1dfeR4HaWDbWqFHLkxsg1d" parentID="10052064spotify%3aartist%3a1dfeR4HaWDbWqFHLkxsg1d" restricted="true"><dc:title>Queen</dc:title><upnp:class>object.item.audioItem.audioBroadcast.#artistRadio</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON3079_X_#Svc3079-0-Token</desc></item></DIDL-Lite>', 'SetAVTransportURIResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Play"', '01', 'PlayResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.playSpotifyRadio('1dfeR4HaWDbWqFHLkxsg1d', 'Queen') }) }) describe('setAVTransportURI()', function () { it('should generate queue command', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"', '0http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3', 'SetAVTransportURIResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Play"', '01', 'PlayResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.setAVTransportURI('http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3') }) it('should accept object in place of uri', function () { mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"', '0http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3<test>"hello"</test>', 'SetAVTransportURIResponse', 'AVTransport' ) mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#Play"', '01', 'PlayResponse', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) sonos.setAVTransportURI({ uri: 'http://livingears.com/music/SceneNotHeard/091909/Do You Mind Kyla.mp3', metadata: '"hello"' }) }) }) describe('createPlaylist()', function () { it('should create new playlist', async function () { const scope = mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#CreateSavedQueue"', '0Test', 'CreateSavedQueue', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) await sonos.createPlaylist('Test') scope.done() }) }) describe('deletePlaylist()', function () { it('should remove existing playlist', async function () { const scope = mockRequest('/MediaServer/ContentDirectory/Control', '"urn:schemas-upnp-org:service:ContentDirectory:1#DestroyObject"', 'SQ:5', 'DestroyObject', 'ContentDirectory' ) const sonos = new Sonos('localhost', 1400) await sonos.deletePlaylist(5) scope.done() }) }) describe('addToPlaylist()', function () { it('should add track to playlist', async function () { mockRequest('/MediaServer/ContentDirectory/Control', '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"', 'BrowseDirectChildren*0100SQ:1', 'BrowseResponse', 'ContentDirectory', '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="x-file-cifs://localhost/Music/Song.mp4" parentID="SQ:1" restricted="true"><res protocolInfo="x-file-cifs:*:audio/mp4:*">x-file-cifs://localhost/Music/Song.mp3</res><upnp:albumArtURI></upnp:albumArtURI><dc:title>Song</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>MyInterpret</dc:creator><upnp:album>MyAlbum</upnp:album><upnp:originalTrackNumber>1</upnp:originalTrackNumber></item></DIDL-Lite>000' ) const scope = mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToSavedQueue"', '0SQ:10x-file-cifs://localhost/Music/Song.mp3<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="S://localhost/Music/Song.mp3" parentID="A:TRACKS" restricted="true"><dc:title>Song</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><upnp:albumArtURI></upnp:albumArtURI><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">RINCON_AssociatedZPUDN</desc></item></DIDL-Lite>4294967295', 'AddURIToSavedQueue', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) await sonos.addToPlaylist(1, 'x-file-cifs://localhost/Music/Song.mp3') scope.done() }) it('should add album to playlist', async function () { mockRequest('/MediaServer/ContentDirectory/Control', '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"', 'BrowseDirectChildren*0100SQ:1', 'BrowseResponse', 'ContentDirectory', '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="x-file-cifs://localhost/Music/Song.mp4" parentID="SQ:1" restricted="true"><res protocolInfo="x-file-cifs:*:audio/mp4:*">x-file-cifs://localhost/Music/Song.mp3</res><upnp:albumArtURI></upnp:albumArtURI><dc:title>Song</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>MyInterpret</dc:creator><upnp:album>MyAlbum</upnp:album><upnp:originalTrackNumber>1</upnp:originalTrackNumber></item></DIDL-Lite>000' ) const scope = mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToSavedQueue"', '0SQ:10x-rincon-playlist://localhost/Music/Album#A:ALBUMS/MyAlbum<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="A:ALBUMS/MyAlbum" parentID="A:ALBUMS" restricted="true"><dc:title>MyAlbum</dc:title><upnp:class>object.item.audioItem.musicAlbum</upnp:class><upnp:albumArtURI></upnp:albumArtURI><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">RINCON_AssociatedZPUDN</desc></item></DIDL-Lite>4294967295', 'AddURIToSavedQueue', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) await sonos.addToPlaylist(1, 'x-rincon-playlist://localhost/Music/Album#A:ALBUMS/MyAlbum') scope.done() }) it('should add albumartist to playlist', async function () { mockRequest('/MediaServer/ContentDirectory/Control', '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"', 'BrowseDirectChildren*0100SQ:1', 'BrowseResponse', 'ContentDirectory', '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="x-file-cifs://localhost/Music/Song.mp4" parentID="SQ:1" restricted="true"><res protocolInfo="x-file-cifs:*:audio/mp4:*">x-file-cifs://localhost/Music/Song.mp3</res><upnp:albumArtURI></upnp:albumArtURI><dc:title>Song</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>MyInterpret</dc:creator><upnp:album>MyAlbum</upnp:album><upnp:originalTrackNumber>1</upnp:originalTrackNumber></item></DIDL-Lite>000' ) const scope = mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToSavedQueue"', '0SQ:10x-rincon-playlist://localhost/Music/AlbumArtist#A:ALBUMARTIST/My Album Artist<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="A:ALBUMARTIST/My%20Album%20Artist" parentID="A:ALBUMARTIST" restricted="true"><dc:title>My Album Artist</dc:title><upnp:class>object.item.audioItem.musicArtist</upnp:class><upnp:albumArtURI></upnp:albumArtURI><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">RINCON_AssociatedZPUDN</desc></item></DIDL-Lite>4294967295', 'AddURIToSavedQueue', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) await sonos.addToPlaylist(1, 'x-rincon-playlist://localhost/Music/AlbumArtist#A:ALBUMARTIST/My Album Artist') scope.done() }) it('should add genre to playlist', async function () { mockRequest('/MediaServer/ContentDirectory/Control', '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"', 'BrowseDirectChildren*0100SQ:1', 'BrowseResponse', 'ContentDirectory', '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="x-file-cifs://localhost/Music/Song.mp4" parentID="SQ:1" restricted="true"><res protocolInfo="x-file-cifs:*:audio/mp4:*">x-file-cifs://localhost/Music/Song.mp3</res><upnp:albumArtURI></upnp:albumArtURI><dc:title>Song</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>MyInterpret</dc:creator><upnp:album>MyAlbum</upnp:album><upnp:originalTrackNumber>1</upnp:originalTrackNumber></item></DIDL-Lite>000' ) const scope = mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#AddURIToSavedQueue"', '0SQ:10x-rincon-playlist://localhost/Music/Genre#A:GENRE/MyGenre<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="A:GENRE/MyGenre" parentID="A:GENRE" restricted="true"><dc:title>MyGenre</dc:title><upnp:class>object.container.genre.musicGenre</upnp:class><upnp:albumArtURI></upnp:albumArtURI><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">RINCON_AssociatedZPUDN</desc></item></DIDL-Lite>4294967295', 'AddURIToSavedQueue', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) await sonos.addToPlaylist(1, 'x-rincon-playlist://localhost/Music/Genre#A:GENRE/MyGenre') scope.done() }) }) describe('removeFromPlaylist()', function () { it('should remove track from playlist', async function () { mockRequest('/MediaServer/ContentDirectory/Control', '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"', 'BrowseDirectChildren*0100SQ:1', 'BrowseResponse', 'ContentDirectory', '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="x-file-cifs://localhost/Music/Song.mp4" parentID="SQ:1" restricted="true"><res protocolInfo="x-file-cifs:*:audio/mp4:*">x-file-cifs://localhost/Music/Song.mp3</res><upnp:albumArtURI></upnp:albumArtURI><dc:title>Song</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>MyInterpret</dc:creator><upnp:album>MyAlbum</upnp:album><upnp:originalTrackNumber>1</upnp:originalTrackNumber></item></DIDL-Lite>000' ) const scope = mockRequest('/MediaRenderer/AVTransport/Control', '"urn:schemas-upnp-org:service:AVTransport:1#ReorderTracksInSavedQueue"', '0SQ:102', 'ReorderTracksInSavedQueue', 'AVTransport' ) const sonos = new Sonos('localhost', 1400) await sonos.removeFromPlaylist(1, 2) scope.done() }) }) }) describe('DeviceDiscovery', function () { it('should emit a timeout event when timeout is hit', function (done) { const failTimeout = setTimeout(function () { assert(false, 'Event never fired') done() }, 100) const search = SONOS.DeviceDiscovery({ timeout: 10 }, function (device, model) {}) search.on('timeout', function () { clearTimeout(failTimeout) assert(true) done() }) }) it('should not emit a timeout event when no timeout option is passed in', function (done) { setTimeout(function () { assert(true) done() }, 10) const search = SONOS.DeviceDiscovery(function (device, model) {}) search.on('timeout', function () { assert(false, 'Timeout event should never fire') done() }) }) it('should not emit a timeout event after search is stopped', function (done) { const search = SONOS.DeviceDiscovery({ timeout: 10 }, function (device, model) {}) search.on('timeout', function () { assert(false, 'Timeout event should never fire') done() }) search.destroy(function () { assert(true) done() }) }) }) describe('Async DeviceDiscovery', function () { it('should reject after timeout without devices found', function (done) { const failTimeout = setTimeout(function () { assert(false, 'Event never fired') done() }, 100) const deviceDiscovery = new SONOS.AsyncDeviceDiscovery() deviceDiscovery.discover({ timeout: 10 }).then((device, model) => { // Also fine if we found a device.... clearTimeout(failTimeout) assert(true) done() }).catch(err => { // eslint-disable-line handle-callback-err assert(err instanceof Error) clearTimeout(failTimeout) assert(true) done() }) }) }) describe('Sonos - Device', function () { let sonos before(function () { if (!process.env.SONOS_HOST) { this.skip() } else { sonos = new Sonos(process.env.SONOS_HOST, 1400) } }) it('should getMuted()', function () { return sonos.getMuted().then(muted => { assert(typeof muted === 'boolean', 'muted is a boolean') }) }) it('should getCurrentState()', function () { return sonos.getCurrentState().then(state => { assert(typeof state === 'string', 'state is a string') const values = ['stopped', 'playing', 'paused', 'transitioning', 'no_media'] assert(values.indexOf(state) > -1, 'state is one of the allowed values') }) }) it('should getVolume()', function () { return sonos.getVolume().then(volume => { assert(typeof volume === 'number', 'volume is a number') assert((volume >= 0 && volume <= 100), 'volume is between 0 and 100') }) }) it('should getPlayMode()', function () { return sonos.getPlayMode().then(playmode => { assert(typeof playmode === 'string', 'playmode is a string') const values = ['NORMAL', 'REPEAT_ONE', 'REPEAT_ALL', 'SHUFFLE', 'SHUFFLE_NOREPEAT', 'SHUFFLE_REPEAT_ONE'] assert(values.indexOf(playmode) > -1, 'playmode is one of the allowed values') }) }) it('should getFavorites()', function () { return sonos.getFavorites().then(function (favs) { assert(favs.items, 'should have items') }) }) it('should getMusicLibrary("sonos_playlists")', function () { return sonos.getMusicLibrary('sonos_playlists').then(function (playlists) { assert(playlists.items, 'should have items') if (playlists.items.length > 0) { assert('id' in playlists.items[0], 'item should have id in object') assert('parentID' in playlists.items[0], 'item should have parentID in object') assert('uri' in playlists.items[0], 'item should have uri in object') assert('title' in playlists.items[0], 'item should have title in object') } }) }) it('should getPlaylist()', function () { return sonos.getPlaylist('1').then(function (playlist) { assert(playlist.items, 'should have items') }) }) it('should getQueue()', function () { return sonos.getQueue().then(function (queue) { assert(queue.items, 'should have items') }) }) // There seem to be some kind of error here.... Needs attention. it('should getFavoritesRadioStations()', function () { return sonos.getFavoritesRadioStations().then(function (radio) { assert(radio.items, 'should have items') }) }) it('should getAllGroups()', function () { return sonos.getAllGroups().then(function (groups) { assert(Array.isArray(groups), 'should return an array') }) }) it('playNotification()', function () { return sonos.playNotification({ uri: 'https://www.zapsplat.com/wp-content/uploads/2015/sound-effects-the-sound-pack-tree/tspt_pull_bell_02_065.mp3?_=1', volume: 10 }).then(result => { assert(result, 'Played notification') }) }) it('playNotification() -> No notification when not playing', async function () { const state = await sonos.getCurrentState() if ((state === 'playing' || state === 'transitioning')) { this.skip('Is playing cannot test') } return sonos.playNotification({ uri: 'https://www.zapsplat.com/wp-content/uploads/2015/sound-effects-the-sound-pack-tree/tspt_pull_bell_02_065.mp3?_=1', volume: 10, onlyWhenPlaying: true }).then(result => { assert(result === false, 'Shouldn\'t start when not playing') }) }) it('should return Spotify Conenct Info', function () { return sonos.getSpotifyConnectInfo() .then(data => { assert(true) }) }) describe('AlarmClockService()', function () { it('should list alarms', function () { return sonos.alarmClockService() .ListAlarms().then(function (result) { assert(Array.isArray(result.Alarms), '.Alarms should be an array') assert(typeof (result.CurrentAlarmListVersion) === 'string', '.CurrentAlarmListVersion should be a string') }) }) it('should throw an error on update with not string', function () { return sonos.alarmClockService().SetAlarm(1, false).catch(err => { assert(err, 'Got error') }) }) it('should toggle the first alarm', function () { return sonos.alarmClockService() .ListAlarms().then(function (result) { if (result.Alarms.length === 0) { assert(true) } else { const alarm = result.Alarms[0] const toggleTo = alarm.Enabled === '0' return sonos.alarmClockService().SetAlarm(alarm.ID, toggleTo).then(function (result) { // Revert back the change, or you'll get a bad morning after testing this library.... return sonos.alarmClockService().SetAlarm(alarm.ID, !toggleTo).then(function (result) { assert(true) }) }) } }) }) }) })