Add explicit HEAD handler for track stream that doesnt scrobble

This commit is contained in:
simojenki
2021-06-19 12:26:49 +10:00
parent 79c8c99c1b
commit e6378de25d
2 changed files with 430 additions and 287 deletions

View File

@@ -26,7 +26,7 @@ function server(
musicService: MusicService, musicService: MusicService,
linkCodes: LinkCodes = new InMemoryLinkCodes(), linkCodes: LinkCodes = new InMemoryLinkCodes(),
accessTokens: AccessTokens = new AccessTokenPerAuthToken(), accessTokens: AccessTokens = new AccessTokenPerAuthToken(),
clock: Clock = SystemClock clock: Clock = SystemClock
): Express { ): Express {
const app = express(); const app = express();
@@ -139,6 +139,29 @@ function server(
</Presentation>`); </Presentation>`);
}); });
app.head("/stream/track/:id", async (req, res) => {
const id = req.params["id"]!;
const accessToken = req.headers[BONOB_ACCESS_TOKEN_HEADER] as string;
const authToken = accessTokens.authTokenFor(accessToken);
if (!authToken) {
return res.status(401).send();
} else {
return musicService
.login(authToken)
.then((it) =>
it.stream({ trackId: id, range: req.headers["range"] || undefined })
)
.then((trackStream) => {
res.status(trackStream.status);
Object.entries(trackStream.headers)
.filter(([_, v]) => v !== undefined)
.forEach(([header, value]) => res.setHeader(header, value));
res.send();
});
}
});
app.get("/stream/track/:id", async (req, res) => { app.get("/stream/track/:id", async (req, res) => {
const id = req.params["id"]!; const id = req.params["id"]!;
const accessToken = req.headers[BONOB_ACCESS_TOKEN_HEADER] as string; const accessToken = req.headers[BONOB_ACCESS_TOKEN_HEADER] as string;

View File

@@ -1,7 +1,7 @@
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import dayjs from "dayjs"; import dayjs from "dayjs";
import request from "supertest"; import request from "supertest";
import { MusicService } from "../src/music_service"; import { MusicService } from "../src/music_service";
import makeServer, { BONOB_ACCESS_TOKEN_HEADER } from "../src/server"; import makeServer, { BONOB_ACCESS_TOKEN_HEADER } from "../src/server";
import { SONOS_DISABLED, Sonos, Device } from "../src/sonos"; import { SONOS_DISABLED, Sonos, Device } from "../src/sonos";
@@ -158,7 +158,7 @@ describe("server", () => {
sid: 999, sid: 999,
}); });
const server = makeServer( const server = makeServer(
(sonos as unknown) as Sonos, sonos as unknown as Sonos,
theService, theService,
"http://localhost:1234", "http://localhost:1234",
new InMemoryMusicService() new InMemoryMusicService()
@@ -199,16 +199,16 @@ describe("server", () => {
}; };
const musicLibrary = { const musicLibrary = {
stream: jest.fn(), stream: jest.fn(),
scrobble: jest.fn() scrobble: jest.fn(),
}; };
let now = dayjs(); let now = dayjs();
const accessTokens = new ExpiringAccessTokens({ now: () => now }); const accessTokens = new ExpiringAccessTokens({ now: () => now });
const server = makeServer( const server = makeServer(
(jest.fn() as unknown) as Sonos, jest.fn() as unknown as Sonos,
aService(), aService(),
"http://localhost:1234", "http://localhost:1234",
(musicService as unknown) as MusicService, musicService as unknown as MusicService,
new InMemoryLinkCodes(), new InMemoryLinkCodes(),
accessTokens accessTokens
); );
@@ -221,301 +221,414 @@ describe("server", () => {
accessToken = accessTokens.mint(authToken); accessToken = accessTokens.mint(authToken);
}); });
describe("when there is no access-token", () => { describe("HEAD requests", () => {
it("should return a 401", async () => { describe("when there is no access-token", () => {
const res = await request(server).get(`/stream/track/${trackId}`); it("should return a 401", async () => {
const res = await request(server).head(`/stream/track/${trackId}`);
expect(res.status).toEqual(401); expect(res.status).toEqual(401);
});
});
describe("when the access-token has expired", () => {
it("should return a 401", async () => {
now = now.add(1, "day");
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(401);
});
});
describe("scrobbling", () => {
describe("when scrobbling succeeds", () => {
it("should scrobble the track", async () => {
const trackStream = {
status: 200,
headers: {
"content-type": "audio/mp3",
},
stream: {
pipe: (res: Response) => res.send("")
}
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status);
expect(musicLibrary.scrobble).toHaveBeenCalledWith(trackId);
}); });
}); });
describe("when scrobbling succeeds", () => { describe("when the access-token has expired", () => {
it("should still return the track", async () => { it("should return a 401", async () => {
now = now.add(1, "day");
const res = await request(server)
.head(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(401);
});
});
describe("when the access-token is valid", () => {
describe("and the track exists", () => {
it("should return a 200", async () => {
const trackStream = {
status: 200,
headers: {
"content-type": "audio/mp3; charset=utf-8",
"content-length": "123",
},
stream: {
pipe: (res: Response) => res.send(""),
},
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
const res = await request(server)
.head(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status);
expect(res.headers["content-type"]).toEqual(
"audio/mp3; charset=utf-8"
);
expect(res.headers["content-length"]).toEqual(
"123"
);
expect(res.body).toEqual({});
});
});
describe("and the track doesnt exist", () => {
it("should return a 404", async () => {
const trackStream = {
status: 404,
headers: {},
stream: {
pipe: (res: Response) => res.send(""),
},
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
const res = await request(server)
.head(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(404);
expect(res.body).toEqual({});
});
});
});
});
describe("GET requests", () => {
describe("when there is no access-token", () => {
it("should return a 401", async () => {
const res = await request(server).get(`/stream/track/${trackId}`);
expect(res.status).toEqual(401);
});
});
describe("when the access-token has expired", () => {
it("should return a 401", async () => {
now = now.add(1, "day");
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(401);
});
});
describe("scrobbling", () => {
describe("when scrobbling succeeds", () => {
it("should scrobble the track", async () => {
const trackStream = {
status: 200,
headers: {
"content-type": "audio/mp3",
},
stream: {
pipe: (res: Response) => res.send(""),
},
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status);
expect(musicLibrary.scrobble).toHaveBeenCalledWith(trackId);
});
});
describe("when scrobbling succeeds", () => {
it("should still return the track", async () => {
const trackStream = {
status: 200,
headers: {
"content-type": "audio/mp3",
},
stream: {
pipe: (res: Response) => res.send(""),
},
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(false);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status);
expect(musicLibrary.scrobble).toHaveBeenCalledWith(trackId);
});
});
});
describe("when the track doesnt exist", () => {
it("should return a 404", async () => {
const trackStream = { const trackStream = {
status: 200, status: 404,
headers: { headers: {
"content-type": "audio/mp3",
}, },
stream: { stream: {
pipe: (res: Response) => res.send("") pipe: (res: Response) => res.send(""),
} },
}; };
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream); musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(false); musicLibrary.scrobble.mockResolvedValue(false);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status);
expect(musicLibrary.scrobble).toHaveBeenCalledWith(trackId);
});
});
});
describe("when sonos does not ask for a range", () => {
describe("when the music service does not return a content-range, content-length or accept-ranges", () => {
it("should return a 200 with the data, without adding the undefined headers", async () => {
const trackStream = {
status: 200,
headers: {
"content-type": "audio/mp3",
},
stream: {
pipe: (res: Response) => res.send("")
}
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server) const res = await request(server)
.get(`/stream/track/${trackId}`) .get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status); expect(res.status).toEqual(404);
expect(res.headers["content-type"]).toEqual("audio/mp3; charset=utf-8")
expect(Object.keys(res.headers)).not.toContain("content-range")
expect(Object.keys(res.headers)).not.toContain("accept-ranges")
}); });
}); });
describe("when the music service returns undefined values for content-range, content-length or accept-ranges", () => { describe("when sonos does not ask for a range", () => {
it("should return a 200 with the data, without adding the undefined headers", async () => { describe("when the music service does not return a content-range, content-length or accept-ranges", () => {
const trackStream = { it("should return a 200 with the data, without adding the undefined headers", async () => {
status: 200, const content = "some-track";
headers: {
"content-type": "audio/mp3",
"content-length": undefined,
"accept-ranges": undefined,
"content-range": undefined,
},
stream: {
pipe: (res: Response) => res.send("")
}
};
musicService.login.mockResolvedValue(musicLibrary); const trackStream = {
musicLibrary.stream.mockResolvedValue(trackStream); status: 200,
musicLibrary.scrobble.mockResolvedValue(true); headers: {
"content-type": "audio/mp3",
// this content-length seems to be ignored for GET requests, stream.pipe must set its' own
"content-length": "666"
},
stream: {
pipe: (res: Response) => res.send(content),
},
};
const res = await request(server) musicService.login.mockResolvedValue(musicLibrary);
.get(`/stream/track/${trackId}`) musicLibrary.stream.mockResolvedValue(trackStream);
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status); expect(res.status).toEqual(trackStream.status);
expect(res.headers["content-type"]).toEqual("audio/mp3; charset=utf-8") expect(res.headers["content-type"]).toEqual(
expect(Object.keys(res.headers)).not.toContain("content-range") "audio/mp3; charset=utf-8"
expect(Object.keys(res.headers)).not.toContain("accept-ranges") );
expect(res.headers["content-length"]).toEqual(
`${content.length}`
);
expect(Object.keys(res.headers)).not.toContain("content-range");
expect(Object.keys(res.headers)).not.toContain("accept-ranges");
}); });
});
describe("when the music service returns a 200", () => {
it("should return a 200 with the data", async () => {
const trackStream = {
status: 200,
headers: {
"content-type": "audio/mp3",
"content-length": "222",
"accept-ranges": "bytes",
"content-range": "-100",
},
stream: {
pipe: (res: Response) => {
console.log("calling send on response")
res.send("")
}
}
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status);
expect(res.header["content-type"]).toEqual(
`${trackStream.headers["content-type"]}; charset=utf-8`
);
expect(res.header["accept-ranges"]).toEqual(
trackStream.headers["accept-ranges"]
);
expect(res.header["content-range"]).toEqual(
trackStream.headers["content-range"]
);
expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.stream).toHaveBeenCalledWith({ trackId });
}); });
});
describe("when the music service returns a 206", () => { describe("when the music service returns undefined values for content-range, content-length or accept-ranges", () => {
it("should return a 206 with the data", async () => { it("should return a 200 with the data, without adding the undefined headers", async () => {
const trackStream = { const trackStream = {
status: 206, status: 200,
headers: { headers: {
"content-type": "audio/ogg", "content-type": "audio/mp3",
"content-length": "333", "content-length": undefined,
"accept-ranges": "bytez", "accept-ranges": undefined,
"content-range": "100-200", "content-range": undefined,
}, },
stream: { stream: {
pipe: (res: Response) => res.send("") pipe: (res: Response) => res.send(""),
} },
}; };
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream); musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true); musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server) const res = await request(server)
.get(`/stream/track/${trackId}`) .get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status); expect(res.status).toEqual(trackStream.status);
expect(res.header["content-type"]).toEqual( expect(res.headers["content-type"]).toEqual(
`${trackStream.headers["content-type"]}; charset=utf-8` "audio/mp3; charset=utf-8"
); );
expect(res.header["accept-ranges"]).toEqual( expect(Object.keys(res.headers)).not.toContain("content-range");
trackStream.headers["accept-ranges"] expect(Object.keys(res.headers)).not.toContain("accept-ranges");
); });
expect(res.header["content-range"]).toEqual(
trackStream.headers["content-range"]
);
expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.stream).toHaveBeenCalledWith({ trackId });
}); });
});
});
describe("when sonos does ask for a range", () => { describe("when the music service returns a 200", () => {
describe("when the music service returns a 200", () => { it("should return a 200 with the data", async () => {
it("should return a 200 with the data", async () => { const trackStream = {
const trackStream = { status: 200,
status: 200, headers: {
headers: { "content-type": "audio/mp3",
"content-type": "audio/mp3", "content-length": "222",
"content-length": "222", "accept-ranges": "bytes",
"accept-ranges": "bytes", "content-range": "-100",
"content-range": "-100", },
}, stream: {
stream: { pipe: (res: Response) => {
pipe: (res: Response) => res.send("") console.log("calling send on response");
} res.send("");
}; },
},
};
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream); musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true); musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server) const res = await request(server)
.get(`/stream/track/${trackId}`) .get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken) .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
.set("Range", "3000-4000");
expect(res.status).toEqual(trackStream.status); expect(res.status).toEqual(trackStream.status);
expect(res.header["content-type"]).toEqual( expect(res.header["content-type"]).toEqual(
`${trackStream.headers["content-type"]}; charset=utf-8` `${trackStream.headers["content-type"]}; charset=utf-8`
); );
expect(res.header["accept-ranges"]).toEqual( expect(res.header["accept-ranges"]).toEqual(
trackStream.headers["accept-ranges"] trackStream.headers["accept-ranges"]
); );
expect(res.header["content-range"]).toEqual( expect(res.header["content-range"]).toEqual(
trackStream.headers["content-range"] trackStream.headers["content-range"]
); );
expect(musicService.login).toHaveBeenCalledWith(authToken); expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.stream).toHaveBeenCalledWith({ expect(musicLibrary.stream).toHaveBeenCalledWith({ trackId });
trackId, });
range: "3000-4000", });
describe("when the music service returns a 206", () => {
it("should return a 206 with the data", async () => {
const trackStream = {
status: 206,
headers: {
"content-type": "audio/ogg",
"content-length": "333",
"accept-ranges": "bytez",
"content-range": "100-200",
},
stream: {
pipe: (res: Response) => res.send(""),
},
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(trackStream.status);
expect(res.header["content-type"]).toEqual(
`${trackStream.headers["content-type"]}; charset=utf-8`
);
expect(res.header["accept-ranges"]).toEqual(
trackStream.headers["accept-ranges"]
);
expect(res.header["content-range"]).toEqual(
trackStream.headers["content-range"]
);
expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.stream).toHaveBeenCalledWith({ trackId });
}); });
}); });
}); });
describe("when the music service returns a 206", () => { describe("when sonos does ask for a range", () => {
it("should return a 206 with the data", async () => { describe("when the music service returns a 200", () => {
const trackStream = { it("should return a 200 with the data", async () => {
status: 206, const trackStream = {
headers: { status: 200,
"content-type": "audio/ogg", headers: {
"content-length": "333", "content-type": "audio/mp3",
"accept-ranges": "bytez", "content-length": "222",
"content-range": "100-200", "accept-ranges": "bytes",
}, "content-range": "-100",
stream: { },
pipe: (res: Response) => res.send("") stream: {
} pipe: (res: Response) => res.send(""),
}; },
};
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream); musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true); musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server) const res = await request(server)
.get(`/stream/track/${trackId}`) .get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken) .set(BONOB_ACCESS_TOKEN_HEADER, accessToken)
.set("Range", "4000-5000"); .set("Range", "3000-4000");
expect(res.status).toEqual(trackStream.status); expect(res.status).toEqual(trackStream.status);
expect(res.header["content-type"]).toEqual( expect(res.header["content-type"]).toEqual(
`${trackStream.headers["content-type"]}; charset=utf-8` `${trackStream.headers["content-type"]}; charset=utf-8`
); );
expect(res.header["accept-ranges"]).toEqual( expect(res.header["accept-ranges"]).toEqual(
trackStream.headers["accept-ranges"] trackStream.headers["accept-ranges"]
); );
expect(res.header["content-range"]).toEqual( expect(res.header["content-range"]).toEqual(
trackStream.headers["content-range"] trackStream.headers["content-range"]
); );
expect(musicService.login).toHaveBeenCalledWith(authToken); expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.stream).toHaveBeenCalledWith({ expect(musicLibrary.stream).toHaveBeenCalledWith({
trackId, trackId,
range: "4000-5000", range: "3000-4000",
});
});
});
describe("when the music service returns a 206", () => {
it("should return a 206 with the data", async () => {
const trackStream = {
status: 206,
headers: {
"content-type": "audio/ogg",
"content-length": "333",
"accept-ranges": "bytez",
"content-range": "100-200",
},
stream: {
pipe: (res: Response) => res.send(""),
},
};
musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.stream.mockResolvedValue(trackStream);
musicLibrary.scrobble.mockResolvedValue(true);
const res = await request(server)
.get(`/stream/track/${trackId}`)
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken)
.set("Range", "4000-5000");
expect(res.status).toEqual(trackStream.status);
expect(res.header["content-type"]).toEqual(
`${trackStream.headers["content-type"]}; charset=utf-8`
);
expect(res.header["accept-ranges"]).toEqual(
trackStream.headers["accept-ranges"]
);
expect(res.header["content-range"]).toEqual(
trackStream.headers["content-range"]
);
expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.stream).toHaveBeenCalledWith({
trackId,
range: "4000-5000",
});
}); });
}); });
}); });
@@ -533,10 +646,10 @@ describe("server", () => {
const accessTokens = new ExpiringAccessTokens({ now: () => now }); const accessTokens = new ExpiringAccessTokens({ now: () => now });
const server = makeServer( const server = makeServer(
(jest.fn() as unknown) as Sonos, jest.fn() as unknown as Sonos,
aService(), aService(),
"http://localhost:1234", "http://localhost:1234",
(musicService as unknown) as MusicService, musicService as unknown as MusicService,
new InMemoryLinkCodes(), new InMemoryLinkCodes(),
accessTokens accessTokens
); );
@@ -590,58 +703,62 @@ describe("server", () => {
contentType: "image/jpeg", contentType: "image/jpeg",
data: Buffer.from("some image", "ascii"), data: Buffer.from("some image", "ascii"),
}; };
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.coverArt.mockResolvedValue(coverArt); musicLibrary.coverArt.mockResolvedValue(coverArt);
const res = await request(server) const res = await request(server)
.get( .get(
`/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(coverArt.status); expect(res.status).toEqual(coverArt.status);
expect(res.header["content-type"]).toEqual(coverArt.contentType); expect(res.header["content-type"]).toEqual(coverArt.contentType);
expect(musicService.login).toHaveBeenCalledWith(authToken); expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.coverArt).toHaveBeenCalledWith(albumId, "artist", 180); expect(musicLibrary.coverArt).toHaveBeenCalledWith(
albumId,
"artist",
180
);
}); });
}); });
describe("when there isn't one", () => { describe("when there isn't one", () => {
it("should return a 404", async () => { it("should return a 404", async () => {
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.coverArt.mockResolvedValue(undefined); musicLibrary.coverArt.mockResolvedValue(undefined);
const res = await request(server) const res = await request(server)
.get( .get(
`/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(404); expect(res.status).toEqual(404);
}); });
}); });
describe("when there is an error", () => { describe("when there is an error", () => {
it("should return a 500", async () => { it("should return a 500", async () => {
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.coverArt.mockRejectedValue("Boom") musicLibrary.coverArt.mockRejectedValue("Boom");
const res = await request(server) const res = await request(server)
.get( .get(
`/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/artist/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(500); expect(res.status).toEqual(500);
}); });
}); });
}); });
describe("album art", () => { describe("album art", () => {
describe("when there is some", () => { describe("when there is some", () => {
it("should return the image and a 200", async () => { it("should return the image and a 200", async () => {
@@ -650,21 +767,25 @@ describe("server", () => {
contentType: "image/jpeg", contentType: "image/jpeg",
data: Buffer.from("some image", "ascii"), data: Buffer.from("some image", "ascii"),
}; };
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.coverArt.mockResolvedValue(coverArt); musicLibrary.coverArt.mockResolvedValue(coverArt);
const res = await request(server) const res = await request(server)
.get( .get(
`/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(coverArt.status); expect(res.status).toEqual(coverArt.status);
expect(res.header["content-type"]).toEqual(coverArt.contentType); expect(res.header["content-type"]).toEqual(coverArt.contentType);
expect(musicService.login).toHaveBeenCalledWith(authToken); expect(musicService.login).toHaveBeenCalledWith(authToken);
expect(musicLibrary.coverArt).toHaveBeenCalledWith(albumId, "album", 180); expect(musicLibrary.coverArt).toHaveBeenCalledWith(
albumId,
"album",
180
);
}); });
}); });
@@ -672,13 +793,13 @@ describe("server", () => {
it("should return a 404", async () => { it("should return a 404", async () => {
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.coverArt.mockResolvedValue(undefined); musicLibrary.coverArt.mockResolvedValue(undefined);
const res = await request(server) const res = await request(server)
.get( .get(
`/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(404); expect(res.status).toEqual(404);
}); });
}); });
@@ -686,19 +807,18 @@ describe("server", () => {
describe("when there is an error", () => { describe("when there is an error", () => {
it("should return a 500", async () => { it("should return a 500", async () => {
musicService.login.mockResolvedValue(musicLibrary); musicService.login.mockResolvedValue(musicLibrary);
musicLibrary.coverArt.mockRejectedValue("Boooooom") musicLibrary.coverArt.mockRejectedValue("Boooooom");
const res = await request(server) const res = await request(server)
.get( .get(
`/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}` `/album/${albumId}/art/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
) )
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken); .set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
expect(res.status).toEqual(500); expect(res.status).toEqual(500);
}); });
}); });
}); });
}); });
}); });
}); });