Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LB-530: Refactor the LastFm Modal code #1214

Merged
merged 6 commits into from Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
261 changes: 260 additions & 1 deletion listenbrainz/webserver/static/js/src/LastFMImporter.test.tsx
@@ -1,8 +1,14 @@
import * as React from "react";
import { mount, shallow } from "enzyme";

import LastFmImporter from "./LastFMImporter";
// Mock data to test functions
import * as page from "./__mocks__/page.json";
import * as getInfo from "./__mocks__/getInfo.json";
import * as getInfoNoPlayCount from "./__mocks__/getInfoNoPlayCount.json";
// Output for the mock data
import * as encodeScrobbleOutput from "./__mocks__/encodeScrobbleOutput.json";

jest.useFakeTimers();
const props = {
user: {
id: "id",
Expand All @@ -15,6 +21,259 @@ const props = {
lastfmApiKey: "foobar",
};

describe("encodeScrobbles", () => {
it("encodes the given scrobbles correctly", () => {
expect(LastFmImporter.encodeScrobbles(page)).toEqual(encodeScrobbleOutput);
});
});

let instance: LastFmImporter;

describe("getNumberOfPages", () => {
beforeEach(() => {
const wrapper = shallow<LastFmImporter>(<LastFmImporter {...props} />);
instance = wrapper.instance();
instance.setState({ lastfmUsername: "dummyUser" });
// Mock function for fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(page),
});
});
});

it("should call with the correct url", () => {
instance.getNumberOfPages();

expect(window.fetch).toHaveBeenCalledWith(
`${props.lastfmApiUrl}?method=user.getrecenttracks&user=${instance.state.lastfmUsername}&api_key=${props.lastfmApiKey}&from=1&format=json`
);
});

it("should return number of pages", async () => {
const num = await instance.getNumberOfPages();
expect(num).toBe(1);
});

it("should return -1 if there is an error", async () => {
// Mock function for failed fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: false,
});
});

const num = await instance.getNumberOfPages();
expect(num).toBe(-1);
});
});

describe("getTotalNumberOfScrobbles", () => {
beforeEach(() => {
const wrapper = shallow<LastFmImporter>(<LastFmImporter {...props} />);
instance = wrapper.instance();
instance.setState({ lastfmUsername: "dummyUser" });
// Mock function for fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(getInfo),
});
});
});

it("should call with the correct url", () => {
instance.getTotalNumberOfScrobbles();

expect(window.fetch).toHaveBeenCalledWith(
`${props.lastfmApiUrl}?method=user.getinfo&user=${instance.state.lastfmUsername}&api_key=${props.lastfmApiKey}&format=json`
);
});

it("should return number of pages", async () => {
const num = await instance.getTotalNumberOfScrobbles();
expect(num).toBe(1026);
});

it("should return -1 if playcount is not available", async () => {
// Mock function for fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(getInfoNoPlayCount),
});
});

const num = await instance.getTotalNumberOfScrobbles();
expect(num).toBe(-1);
});

it("should throw an error when fetch fails", async () => {
// Mock function for failed fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: false,
});
});
await expect(instance.getTotalNumberOfScrobbles()).rejects.toThrowError();
});
});

describe("getPage", () => {
beforeEach(() => {
const wrapper = shallow<LastFmImporter>(<LastFmImporter {...props} />);
instance = wrapper.instance();
instance.setState({ lastfmUsername: "dummyUser" });
// Mock function for fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(page),
});
});
});

it("should call with the correct url", () => {
instance.getPage(1);

expect(window.fetch).toHaveBeenCalledWith(
`${props.lastfmApiUrl}?method=user.getrecenttracks&user=${instance.state.lastfmUsername}&api_key=${props.lastfmApiKey}&from=1&page=1&format=json`
);
});

it("should call encodeScrobbles", async () => {
// Mock function for encodeScrobbles
LastFmImporter.encodeScrobbles = jest.fn(() => ["foo", "bar"]);

const data = await instance.getPage(1);
expect(LastFmImporter.encodeScrobbles).toHaveBeenCalledTimes(1);
expect(data).toEqual(["foo", "bar"]);
});

it("should retry if 50x error is recieved", async () => {
// Mock function for fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: false,
status: 503,
});
});

await instance.getPage(1);
// There is no direct way to check if retry has been called
expect(setTimeout).toHaveBeenCalledTimes(1);

jest.runAllTimers();
});

it("should skip the page if 40x is recieved", async () => {
// Mock function for failed fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: false,
status: 404,
});
});

await instance.getPage(1);
expect(setTimeout).not.toHaveBeenCalled();
});

it("should retry if there is any other error", async () => {
// Mock function for fetch
window.fetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.reject(),
});
});

await instance.getPage(1);
// There is no direct way to check if retry has been called
expect(setTimeout).toHaveBeenCalledTimes(1);
jest.runAllTimers();
});
});

describe("submitPage", () => {
beforeEach(() => {
const wrapper = shallow<LastFmImporter>(<LastFmImporter {...props} />);
instance = wrapper.instance();
instance.setState({ lastfmUsername: "dummyUser" });
instance.getRateLimitDelay = jest.fn().mockImplementation(() => 0);
instance.updateRateLimitParameters = jest.fn();
});

it("calls submitListens once", async () => {
// window.fetch = jest.fn().mockImplementation(() => {
// return Promise.resolve({
// ok: true,
// json: () => Promise.resolve({ status: 200 }),
// });
// });
instance.APIService.submitListens = jest.fn().mockImplementation(() => {
return Promise.resolve({ status: 200 });
});
instance.submitPage([
{
listened_at: 1000,
track_metadata: {
artist_name: "foobar",
track_name: "bazfoo",
},
},
]);

jest.runAllTimers();

// Flush all promises
// https://stackoverflow.com/questions/51126786/jest-fake-timers-with-promises
await new Promise((resolve) => setImmediate(resolve));

expect(instance.APIService.submitListens).toHaveBeenCalledTimes(1);
});

it("calls updateRateLimitParameters once", async () => {
instance.APIService.submitListens = jest.fn().mockImplementation(() => {
return Promise.resolve({ status: 200 });
});
instance.submitPage([
{
listened_at: 1000,
track_metadata: {
artist_name: "foobar",
track_name: "bazfoo",
},
},
]);

jest.runAllTimers();

// Flush all promises
// https://stackoverflow.com/questions/51126786/jest-fake-timers-with-promises
await new Promise((resolve) => setImmediate(resolve));

expect(instance.updateRateLimitParameters).toHaveBeenCalledTimes(1);
expect(instance.updateRateLimitParameters).toHaveBeenCalledWith({
status: 200,
});
});

it("calls getRateLimitDelay once", async () => {
instance.submitPage([
{
listened_at: 1000,
track_metadata: {
artist_name: "foobar",
track_name: "bazfoo",
},
},
]);
expect(instance.getRateLimitDelay).toHaveBeenCalledTimes(1);
});
});

describe("LastFmImporter Page", () => {
it("renders", () => {
const wrapper = mount(<LastFmImporter {...props} />);
Expand Down