Skip to content

Commit

Permalink
Add tests for operations that look for playlists containing specific …
Browse files Browse the repository at this point in the history
…media (#676)

* Add a test for /api/playlists?contains=:media_id

* no playlist leakage

* Test playlists.getPlaylistsContainingAnyMedia

* lint fix
  • Loading branch information
goto-bus-stop authored Nov 28, 2024
1 parent 888ef62 commit 07405b7
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 8 deletions.
4 changes: 4 additions & 0 deletions src/controllers/playlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ async function getPlaylists(req) {
playlists = await uw.playlists.getUserPlaylists(user);
}

playlists.sort((a, b) => {
return a.name.localeCompare(b.name);
});

return toListResponse(
playlists.map(serializePlaylist),
{ url: req.fullUrl },
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/playlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ class PlaylistsRepository {

/**
* Get playlists that contain any of the given medias. If multiple medias are in a single
* playlist, that playlist will be returned multiple times, keyed on the media's unique ObjectId.
* playlist, that playlist will be returned multiple times, keyed on the media ID.
*
* @param {MediaID[]} mediaIDs
* @param {{ author?: UserID }} options
Expand Down
47 changes: 47 additions & 0 deletions test/playlists.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,53 @@ describe('Playlists', () => {
sinon.match({ name: 'Playlist B' }),
]);
});

it('shows all playlists containing a specific media', async () => {
const token = await uw.test.createTestSessionToken(user);
const otherUser = await uw.test.createUser();

const { playlist: playlistA } = await uw.playlists.createPlaylist(user, { name: 'Playlist A' });
const { playlist: playlistB } = await uw.playlists.createPlaylist(user, { name: 'Playlist B' });
const { playlist: playlistC } = await uw.playlists.createPlaylist(otherUser, {
name: "Other user's playlist should not be included",
});

const [onlyA, onlyB, both] = await uw.source('test-source').get(user, ['ONLY_A', 'ONLY_B', 'BOTH']);
const a = await uw.playlists.addPlaylistItems(playlistA, [onlyA, both]);
const b = await uw.playlists.addPlaylistItems(playlistB, [onlyB, both]);
// All media are in playlist C, but that playlist is owned by a different user,
// so we do not expect it to show up in the assertions below.
await uw.playlists.addPlaylistItems(playlistC, [onlyA, onlyB, both]);

const mediaIDOnlyA = a.added[0].media.id;
const mediaIDOnlyB = b.added[0].media.id;
const mediaIDBoth = a.added[1].media.id;

const resOnlyA = await supertest(uw.server)
.get(`/api/playlists?contains=${mediaIDOnlyA}`)
.set('Cookie', `uwsession=${token}`)
.expect(200);
sinon.assert.match(resOnlyA.body.data, [
sinon.match({ name: 'Playlist A' }),
]);

const resOnlyB = await supertest(uw.server)
.get(`/api/playlists?contains=${mediaIDOnlyB}`)
.set('Cookie', `uwsession=${token}`)
.expect(200);
sinon.assert.match(resOnlyB.body.data, [
sinon.match({ name: 'Playlist B' }),
]);

const resBoth = await supertest(uw.server)
.get(`/api/playlists?contains=${mediaIDBoth}`)
.set('Cookie', `uwsession=${token}`)
.expect(200);
sinon.assert.match(resBoth.body.data, [
sinon.match({ name: 'Playlist A' }),
sinon.match({ name: 'Playlist B' }),
]);
});
});

describe('POST /playlists', () => {
Expand Down
77 changes: 70 additions & 7 deletions test/sources.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('Media Sources', () => {
artist: `artist ${sourceID}`,
title: `title ${sourceID}`,
thumbnail: 'https://placedog.net/280',
duration: 60,
};
}

Expand All @@ -34,7 +35,7 @@ describe('Media Sources', () => {
};

function testSource() {
const search = async (query) => [{ sourceID: query }];
const search = async (query) => [makeTestMedia(query)];
const get = async (ids) => ids.map((sourceID) => makeTestMedia(sourceID));
return {
name: 'test-source',
Expand All @@ -48,7 +49,7 @@ describe('Media Sources', () => {
name: 'test-source-with-play',
async search() { throw new Error('unimplemented'); },
async get() { throw new Error('unimplemented'); },
async play(context, media) {
async play(_context, media) {
return {
urn: `${media.sourceType}:${media.sourceID}`,
};
Expand All @@ -70,8 +71,8 @@ describe('Media Sources', () => {
uw.source(testSource);
const query = 'search-query';
const results = await uw.source('test-source').search(null, query);
assert.deepStrictEqual(results, [
{ sourceType: 'test-source', sourceID: query },
sinon.assert.match(results, [
sinon.match({ sourceType: 'test-source', sourceID: query }),
]);
});

Expand All @@ -80,10 +81,20 @@ describe('Media Sources', () => {
const results = await uw.source('test-source').get(null, ['one', 'two']);
assert.deepStrictEqual(results, [
{
sourceType: 'test-source', sourceID: 'one', artist: 'artist one', title: 'title one', thumbnail: 'https://placedog.net/280',
sourceType: 'test-source',
sourceID: 'one',
artist: 'artist one',
title: 'title one',
thumbnail: 'https://placedog.net/280',
duration: 60,
},
{
sourceType: 'test-source', sourceID: 'two', artist: 'artist two', title: 'title two', thumbnail: 'https://placedog.net/280',
sourceType: 'test-source',
sourceID: 'two',
artist: 'artist two',
title: 'title two',
thumbnail: 'https://placedog.net/280',
duration: 60,
},
]);
});
Expand Down Expand Up @@ -146,7 +157,7 @@ describe('Media Sources', () => {
.expect(200);
sinon.assert.match(results.body, {
data: [
{ sourceType: 'test-source', sourceID: query },
sinon.match({ sourceType: 'test-source', sourceID: query }),
],
});
});
Expand Down Expand Up @@ -192,5 +203,57 @@ describe('Media Sources', () => {
code: 'validation-error',
});
});

it('should include the playlists a media is already in', async () => {
uw.source(testSource);

const user = await uw.test.createUser();
const otherUser = await uw.test.createUser();
const token = await uw.test.createTestSessionToken(user);

const { playlist: playlistA } = await uw.playlists.createPlaylist(user, { name: 'Playlist A' });
const { playlist: playlistB } = await uw.playlists.createPlaylist(user, { name: 'Playlist B' });
const { playlist: playlistC } = await uw.playlists.createPlaylist(otherUser, {
name: "Other user's playlist should not be included",
});

const [onlyA, onlyB, both] = await uw.source('test-source').get(user, ['ONLY_A', 'ONLY_B', 'BOTH']);
await uw.playlists.addPlaylistItems(playlistA, [onlyA, both]);
await uw.playlists.addPlaylistItems(playlistB, [onlyB, both]);
// All media are in playlist C, but that playlist is owned by a different user,
// so we do not expect it to show up in the assertions below.
await uw.playlists.addPlaylistItems(playlistC, [onlyA, onlyB, both]);

const resNone = await supertest(uw.server)
.get('/api/search/test-source?include=playlists')
.query({ query: 'NONE' })
.set('Cookie', `uwsession=${token}`)
.send()
.expect(200);
sinon.assert.match(resNone.body.data, [
makeTestMedia('NONE'),
]);

const resOnlyA = await supertest(uw.server)
.get('/api/search/test-source?include=playlists')
.query({ query: 'ONLY_A' })
.set('Cookie', `uwsession=${token}`)
.send()
.expect(200);
sinon.assert.match(resOnlyA.body.data, [{
...makeTestMedia('ONLY_A'),
inPlaylists: [playlistA.id],
}]);
const resBoth = await supertest(uw.server)
.get('/api/search/test-source?include=playlists')
.query({ query: 'BOTH' })
.set('Cookie', `uwsession=${token}`)
.send()
.expect(200);
sinon.assert.match(resBoth.body.data, [{
...makeTestMedia('BOTH'),
inPlaylists: [playlistA.id, playlistB.id],
}]);
});
});
});

0 comments on commit 07405b7

Please sign in to comment.