rx-player
Version:
Canal+ HTML5 Video Player
167 lines (146 loc) • 6.51 kB
text/typescript
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import CdnPrioritizer from "../cdn_prioritizer";
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
const mockGetCurrent = vi.hoisted(() => {
return vi.fn(() => ({ DEFAULT_CDN_DOWNGRADE_TIME: 60000 }));
});
vi.mock("../../../config", () => ({
default: { getCurrent: mockGetCurrent },
}));
vi.mock("../../../utils/array_find_index", () => ({
// eslint-disable-next-line no-restricted-properties
default: (arr: unknown[], fn: (elt: unknown) => boolean) => arr.findIndex(fn),
}));
vi.mock("../../../utils/event_emitter", () => {
class MockEventEmitter {
trigger = vi.fn();
addEventListener = vi.fn();
removeEventListener = vi.fn();
}
return { default: MockEventEmitter };
});
function makeCancellationSignal(): any {
let _cb: (() => void) | null = null;
return {
signal: {
register: (cb: () => void) => {
_cb = cb;
},
},
cancel: () => _cb?.(),
};
}
describe("CdnPrioritizer", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("returns single CDN as-is without prioritization", () => {
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdns = [{ id: "cdn1", baseUrl: "https://cdn1.example.com" }];
expect(prioritizer.getCdnPreferenceForResource(cdns)).toEqual(cdns);
});
it("returns multiple CDNs in original order when none are downgraded", () => {
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdns = [
{ id: "cdn1", baseUrl: "https://cdn1.example.com" },
{ id: "cdn2", baseUrl: "https://cdn2.example.com" },
];
expect(prioritizer.getCdnPreferenceForResource(cdns)).toEqual(cdns);
});
it("puts downgraded CDN last", () => {
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
prioritizer.downgradeCdn(cdn1);
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
expect(result).toEqual([cdn2, cdn1]);
});
it("triggers priorityChange when downgrading a CDN", () => {
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
prioritizer.downgradeCdn(cdn);
expect((prioritizer as any).trigger).toHaveBeenCalledWith("priorityChange", null);
});
it("triggers priorityChange when downgrade expires", () => {
mockGetCurrent.mockReturnValue({ DEFAULT_CDN_DOWNGRADE_TIME: 5000 });
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
prioritizer.downgradeCdn(cdn);
const triggerMock = (prioritizer as any).trigger;
const callCountBefore = triggerMock.mock.calls.length;
vi.advanceTimersByTime(5000);
expect(triggerMock.mock.calls.length).toBeGreaterThan(callCountBefore);
});
it("CDN is no longer downgraded after timeout expires", () => {
mockGetCurrent.mockReturnValue({ DEFAULT_CDN_DOWNGRADE_TIME: 5000 });
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
prioritizer.downgradeCdn(cdn1);
vi.advanceTimersByTime(5000);
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
expect(result[0]).toEqual(cdn1);
});
it("re-downgrading a CDN resets its downgrade timer", () => {
mockGetCurrent.mockReturnValue({ DEFAULT_CDN_DOWNGRADE_TIME: 5000 });
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
prioritizer.downgradeCdn(cdn1);
vi.advanceTimersByTime(3000);
prioritizer.downgradeCdn(cdn1); // reset
vi.advanceTimersByTime(3000); // only 3s since reset, should still be downgraded
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
expect(result[0]).toEqual(cdn2);
});
it("matches CDN by baseUrl when id is undefined", () => {
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn1 = { baseUrl: "https://cdn1.example.com" } as any;
const cdn2 = { baseUrl: "https://cdn2.example.com" } as any;
prioritizer.downgradeCdn(cdn1);
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2]);
expect(result).toEqual([cdn2, cdn1]);
});
it("clears timeouts and resets on destroy signal", () => {
const { signal, cancel } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
prioritizer.downgradeCdn(cdn);
cancel();
// After destroy, downgraded list should be empty
expect((prioritizer as any)._downgradedCdnList.metadata).toHaveLength(0);
expect((prioritizer as any)._downgradedCdnList.timeouts).toHaveLength(0);
});
it("returns empty array when given empty array", () => {
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
expect(prioritizer.getCdnPreferenceForResource([])).toEqual([]);
});
it("handles multiple downgraded CDNs correctly", () => {
const { signal } = makeCancellationSignal();
const prioritizer = new CdnPrioritizer(signal);
const cdn1 = { id: "cdn1", baseUrl: "https://cdn1.example.com" };
const cdn2 = { id: "cdn2", baseUrl: "https://cdn2.example.com" };
const cdn3 = { id: "cdn3", baseUrl: "https://cdn3.example.com" };
prioritizer.downgradeCdn(cdn1);
prioritizer.downgradeCdn(cdn2);
const result = prioritizer.getCdnPreferenceForResource([cdn1, cdn2, cdn3]);
expect(result[0]).toEqual(cdn3);
expect(result).toContain(cdn1);
expect(result).toContain(cdn2);
});
});