Skip to content

Commit

Permalink
Fixed some errors that Github didn't handle the same way as local dev
Browse files Browse the repository at this point in the history
  • Loading branch information
Oceanity committed Jun 16, 2024
1 parent a05b934 commit d69f7c8
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 78 deletions.
5 changes: 3 additions & 2 deletions src/spotifyIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { AllSpotifyReplaceVariables } from "./firebot/variables";
import { AllSpotifyWebhooks } from "./firebot/webhooks";
import { SpotifyEventSource } from "./firebot/events/spotifyEventSource";
import { Effects } from "@crowbartools/firebot-custom-scripts-types/types/effects";
import { now } from "@utils/time";

const spotifyScopes = [
"app-remote-control",
Expand Down Expand Up @@ -82,7 +83,7 @@ export class SpotifyIntegration extends EventEmitter {
if (
currentAuth.access_token &&
this.expiresAt &&
this.expiresAt - performance.now() > 5000
this.expiresAt - now() > 5000
) {
return currentAuth;
}
Expand Down Expand Up @@ -125,7 +126,7 @@ export class SpotifyIntegration extends EventEmitter {
const data = (await response.json()) as SpotifyRefreshTokenResponse;
data.refresh_token = auth.refresh_token;

this.expiresAt = performance.now() + data.expires_in * 1000;
this.expiresAt = now() + data.expires_in * 1000;
logger.info(
`New token expires at ${new Date(this.expiresAt).toUTCString()}`
);
Expand Down
6 changes: 3 additions & 3 deletions src/utils/spotify/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { integrationId } from "@/main";
import { integration } from "@/spotifyIntegration";
import { SpotifyService } from "@utils/spotify";
import { getErrorMessage } from "@/utils/string";
import { now } from "@utils/time";

export default class SpotifyAuthService {
private spotify: SpotifyService;
Expand Down Expand Up @@ -56,7 +57,7 @@ export default class SpotifyAuthService {
throw new Error("Failed to refresh token");
}

this.expiresAt = performance.now() + refreshResponse.expires_in * 1000;
this.expiresAt = now() + refreshResponse.expires_in * 1000;

logger.info(
`Refreshed Spotify Token. New Token will expire at ${new Date(
Expand All @@ -77,8 +78,7 @@ export default class SpotifyAuthService {
private async tokenExpiredAsync(accessToken: string | undefined) {
if (!accessToken) return true;

if (this.expiresAt && this.expiresAt - performance.now() > 5000)
return false;
if (this.expiresAt && this.expiresAt - now() > 5000) return false;

// Check against API just in case of config issue
return !(await this.spotifyIsConnectedAsync(accessToken));
Expand Down
8 changes: 4 additions & 4 deletions src/utils/spotify/player/lyrics.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DbService from "@/utils/db";
import { logger } from "@/utils/firebot";
import { getErrorMessage } from "@/utils/string";
import { delay } from "@/utils/timing";
import DbService from "@utils/db";
import { logger } from "@utils/firebot";
import { getErrorMessage } from "@utils/string";
import { delay } from "@utils/time";
import { SpotifyService } from "@utils/spotify";
import { EventEmitter } from "events";
import { ensureDir, pathExists, readFile } from "fs-extra";
Expand Down
8 changes: 4 additions & 4 deletions src/utils/spotify/player/state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { eventManager, logger } from "@utils/firebot";
import { delay } from "@utils/timing";
import { logger } from "@utils/firebot";
import { delay, now } from "@utils/time";
import { SpotifyService } from "@utils/spotify";
import { EventEmitter } from "events";

Expand All @@ -19,7 +19,7 @@ export class SpotifyPlayerStateService extends EventEmitter {
}

private async updatePlaybackStateAsync(): Promise<void> {
const startTime = performance.now();
const startTime = now();

try {
if (!this.spotify.auth.isLinked) {
Expand Down Expand Up @@ -83,7 +83,7 @@ export class SpotifyPlayerStateService extends EventEmitter {
}

private async tick(delayMs: number, startTime: number): Promise<void> {
const diffedMs = performance.now() - startTime + this._progressMs;
const diffedMs = now() - startTime + this._progressMs;
this.spotify.events.trigger("tick", { progressMs: diffedMs });
this.emit("tick", this._progressMs);
await delay(delayMs, startTime);
Expand Down
120 changes: 64 additions & 56 deletions src/utils/spotify/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,73 +28,81 @@ describe("SpotifyProfileService", () => {
jest.clearAllMocks();
});

it("fetches user profile on first call", async () => {
const response = await user.getProfileAsync();
//#region getProfileAsync Unit Tests
describe("getProfileAsync", () => {
it("fetches user profile on first call", async () => {
const response = await user.getProfileAsync();

expect(spotify.api.fetch).toHaveBeenCalledTimes(1);
expect(response).toBe(testUser);
});

it("uses cached data on subsequent calls", async () => {
const responses: SpotifyUserProfile[] = [];

for (let i = 0; i < 10; i++) {
responses.push(await user.getProfileAsync());
}

// Should only fetch again after an hour
expect(spotify.api.fetch).toHaveBeenCalledTimes(1);

for (const response of responses) {
expect(spotify.api.fetch).toHaveBeenCalledTimes(1);
expect(response).toBe(testUser);
}
});
});

it("fetches again when user profile expires", async () => {
jest.spyOn(performance, "now").mockReturnValueOnce(-3660000); // Force expiry to pass
it("uses cached profile on subsequent calls", async () => {
const responses: SpotifyUserProfile[] = [];

const firstResponse = await user.getProfileAsync();
expect(firstResponse).toBe(testUser);
for (let i = 0; i < 10; i++) {
responses.push(await user.getProfileAsync());
}

jest.spyOn(performance, "now").mockReturnValueOnce(0);
// Should only fetch again after an hour
expect(spotify.api.fetch).toHaveBeenCalledTimes(1);

const secondResponse = await user.getProfileAsync();
expect(secondResponse).toBe(testUser);
for (const response of responses) {
expect(response).toBe(testUser);
}
});

expect(spotify.api.fetch).toHaveBeenCalledTimes(2);
});
it("fetches user profile when cached profile expires", async () => {
jest.spyOn(require("@utils/time"), "now").mockReturnValue(-3660000);
const firstResponse = await user.getProfileAsync();
expect(firstResponse).toBe(testUser);

it("throws error if fetch fails", async () => {
jest
.spyOn(spotify.api, "fetch")
.mockReturnValue(Promise.resolve({ ok: false, status: 500, data: null }));
jest.spyOn(require("@utils/time"), "now").mockReturnValue(0);
const secondResponse = await user.getProfileAsync();
expect(secondResponse).toBe(testUser);

await expect(user.getProfileAsync()).rejects.toThrow();
});

it("returns true if user is premium", async () => {
const premiumUser = { ...testUser, product: "premium" };
expect(spotify.api.fetch).toHaveBeenCalledTimes(2);
});

jest
.spyOn(spotify.api, "fetch")
.mockReturnValue(
Promise.resolve({ ok: true, status: 200, data: premiumUser })
);
it("throws error if fetch fails", async () => {
jest
.spyOn(spotify.api, "fetch")
.mockReturnValue(
Promise.resolve({ ok: false, status: 500, data: null })
);

const response = await user.isPremiumAsync();
expect(response).toBe(true);
await expect(user.getProfileAsync()).rejects.toThrow();
});
});

it("returns false if user not premium", async () => {
const notPremiumUser = { ...testUser, product: "free" };

jest
.spyOn(spotify.api, "fetch")
.mockReturnValue(
Promise.resolve({ ok: true, status: 200, data: notPremiumUser })
);

const response = await user.isPremiumAsync();
expect(response).toBe(false);
//#endregion

//#region isPremiumAsync Unit Tests
describe("isPremiumAsync", () => {
it("returns true if user is premium", async () => {
const premiumUser = { ...testUser, product: "premium" };

jest
.spyOn(spotify.api, "fetch")
.mockReturnValue(
Promise.resolve({ ok: true, status: 200, data: premiumUser })
);

const response = await user.isPremiumAsync();
expect(response).toBe(true);
});

it("returns false if user not premium", async () => {
const notPremiumUser = { ...testUser, product: "free" };

jest
.spyOn(spotify.api, "fetch")
.mockReturnValue(
Promise.resolve({ ok: true, status: 200, data: notPremiumUser })
);

const response = await user.isPremiumAsync();
expect(response).toBe(false);
});
});
//#endregion
});
7 changes: 3 additions & 4 deletions src/utils/spotify/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SpotifyService } from "@utils/spotify";
import { getErrorMessage } from "@utils/string";
import { now } from "@utils/time";
import { logger } from "@utils/firebot";

export default class SpotifyProfileService {
Expand All @@ -15,9 +16,7 @@ export default class SpotifyProfileService {

public async getProfileAsync(): Promise<SpotifyUserProfile> {
try {
const now = performance.now();

if (this._userProfile && this._pollUserAt > now) {
if (this._userProfile && this._pollUserAt > now()) {
return this._userProfile;
}

Expand All @@ -28,7 +27,7 @@ export default class SpotifyProfileService {
}

this._userProfile = response.data;
this._pollUserAt = now + 1000 * 60 * this.minutesToRefresh;
this._pollUserAt = now() + 1000 * 60 * this.minutesToRefresh;

return this._userProfile;
} catch (error) {
Expand Down
8 changes: 4 additions & 4 deletions src/utils/timing.test.ts → src/utils/time.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { delay } from "@utils/timing";
import { delay, now } from "@utils/time";
import { logger } from "@utils/firebot";
import { jest } from "@jest/globals";

Expand All @@ -10,13 +10,13 @@ jest.mock("@utils/firebot", () => ({

describe("delay", () => {
it("resolves after ms", async () => {
const startTime = performance.now();
const startTime = now();
await delay(100);
expect(performance.now() - startTime).toBeGreaterThanOrEqual(100);
expect(now() - startTime).toBeGreaterThanOrEqual(100);
});

it("logs warning if method took longer than expected", async () => {
const startTime = performance.now() - 5000;
const startTime = now() - 5000;

await delay(1000, startTime);

Expand Down
5 changes: 4 additions & 1 deletion src/utils/timing.ts → src/utils/time.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { logger } from "./firebot";

// Redeclaring because performance.now() is readonly in github environment, can't mock
export const now = (): number => performance.now();

export async function delay(ms: number, startTime?: number): Promise<void> {
const offset = startTime ? performance.now() - startTime : 0;
const offset = startTime ? now() - startTime : 0;

if (offset > ms) {
logger.warn(
Expand Down

0 comments on commit d69f7c8

Please sign in to comment.