unit tests for provider checks, provider listings, utils and provider meta

This commit is contained in:
mrjvs 2023-09-05 20:57:10 +02:00
parent 391432c1ba
commit bcf312d1b3
11 changed files with 476 additions and 61 deletions

View File

@ -1,7 +0,0 @@
import { describe, it, expect } from "vitest";
describe('oof.ts', () => {
it('should contain hello', () => {
expect('hello').toContain('hello');
});
});

View File

@ -0,0 +1,122 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { vi } from 'vitest';
import { gatherAllEmbeds, gatherAllSources } from '@/providers/all';
import { Embed, Sourcerer } from '@/providers/base';
export function makeProviderMocks() {
const embedsMock = vi.fn<Parameters<typeof gatherAllEmbeds>, ReturnType<typeof gatherAllEmbeds>>();
const sourcesMock = vi.fn<Parameters<typeof gatherAllSources>, ReturnType<typeof gatherAllSources>>();
return {
gatherAllEmbeds: embedsMock,
gatherAllSources: sourcesMock,
};
}
const sourceA = {
id: 'a',
rank: 1,
disabled: false,
} as Sourcerer;
const sourceB = {
id: 'b',
rank: 2,
disabled: false,
} as Sourcerer;
const sourceCDisabled = {
id: 'c',
rank: 3,
disabled: true,
} as Sourcerer;
const sourceAHigherRank = {
id: 'a',
rank: 100,
disabled: false,
} as Sourcerer;
const sourceGSameRankAsA = {
id: 'g',
rank: 1,
disabled: false,
} as Sourcerer;
const fullSourceYMovie = {
id: 'y',
name: 'Y',
rank: 105,
scrapeMovie: vi.fn(),
} as Sourcerer;
const fullSourceYShow = {
id: 'y',
name: 'Y',
rank: 105,
scrapeShow: vi.fn(),
} as Sourcerer;
const fullSourceZBoth = {
id: 'z',
name: 'Z',
rank: 106,
scrapeMovie: vi.fn(),
scrapeShow: vi.fn(),
} as Sourcerer;
const embedD = {
id: 'd',
rank: 4,
disabled: false,
} as Embed;
const embedA = {
id: 'a',
rank: 5,
disabled: false,
} as Embed;
const embedEDisabled = {
id: 'e',
rank: 6,
disabled: true,
} as Embed;
const embedDHigherRank = {
id: 'd',
rank: 4000,
disabled: false,
} as Embed;
const embedFSameRankAsA = {
id: 'f',
rank: 5,
disabled: false,
} as Embed;
const embedHSameRankAsSourceA = {
id: 'h',
rank: 1,
disabled: false,
} as Embed;
const fullEmbedX = {
id: 'x',
name: 'X',
rank: 104,
} as Embed;
const fullEmbedZ = {
id: 'z',
name: 'Z',
rank: 109,
} as Embed;
export const mockSources = {
sourceA,
sourceB,
sourceCDisabled,
sourceAHigherRank,
sourceGSameRankAsA,
fullSourceYMovie,
fullSourceYShow,
fullSourceZBoth,
};
export const mockEmbeds = {
embedA,
embedD,
embedDHigherRank,
embedEDisabled,
embedFSameRankAsA,
embedHSameRankAsSourceA,
fullEmbedX,
fullEmbedZ,
};

View File

@ -0,0 +1,63 @@
import { mockEmbeds, mockSources } from '@/__test__/providerTests';
import { getProviders } from '@/providers/get';
import { vi, describe, it, expect, afterEach } from 'vitest';
const mocks = await vi.hoisted(async () => (await import('@/__test__/providerTests')).makeProviderMocks());
vi.mock('@/providers/all', () => mocks);
describe('getProviders()', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('should return providers', () => {
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.embedD]);
mocks.gatherAllSources.mockReturnValue([mockSources.sourceA, mockSources.sourceB]);
expect(getProviders()).toEqual({
sources: [mockSources.sourceA, mockSources.sourceB],
embeds: [mockEmbeds.embedD],
});
});
it('should filter out disabled providers', () => {
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.embedD, mockEmbeds.embedEDisabled]);
mocks.gatherAllSources.mockReturnValue([mockSources.sourceA, mockSources.sourceCDisabled, mockSources.sourceB]);
expect(getProviders()).toEqual({
sources: [mockSources.sourceA, mockSources.sourceB],
embeds: [mockEmbeds.embedD],
});
});
it('should throw on duplicate ids in sources', () => {
mocks.gatherAllEmbeds.mockReturnValue([]);
mocks.gatherAllSources.mockReturnValue([mockSources.sourceAHigherRank, mockSources.sourceA, mockSources.sourceB]);
expect(() => getProviders()).toThrowError();
});
it('should throw on duplicate ids in embeds', () => {
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.embedD, mockEmbeds.embedDHigherRank, mockEmbeds.embedA]);
mocks.gatherAllSources.mockReturnValue([]);
expect(() => getProviders()).toThrowError();
});
it('should throw on duplicate ids between sources and embeds', () => {
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.embedD, mockEmbeds.embedA]);
mocks.gatherAllSources.mockReturnValue([mockSources.sourceA, mockSources.sourceB]);
expect(() => getProviders()).toThrowError();
});
it('should throw on duplicate rank between sources and embeds', () => {
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.embedD, mockEmbeds.embedA]);
mocks.gatherAllSources.mockReturnValue([mockSources.sourceA, mockSources.sourceB]);
expect(() => getProviders()).toThrowError();
});
it('should not throw with same rank between sources and embeds', () => {
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.embedD, mockEmbeds.embedHSameRankAsSourceA]);
mocks.gatherAllSources.mockReturnValue([mockSources.sourceA, mockSources.sourceB]);
expect(getProviders()).toEqual({
sources: [mockSources.sourceA, mockSources.sourceB],
embeds: [mockEmbeds.embedD, mockEmbeds.embedHSameRankAsSourceA],
});
});
});

View File

@ -0,0 +1,121 @@
import { mockEmbeds, mockSources } from '@/__test__/providerTests';
import { makeProviders } from '@/main/builder';
import { afterEach, describe, expect, it, vi } from 'vitest';
const mocks = await vi.hoisted(async () => (await import('@/__test__/providerTests')).makeProviderMocks());
vi.mock('@/providers/all', () => mocks);
describe('ProviderControls.listSources()', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('should return the source with movie type', () => {
mocks.gatherAllSources.mockReturnValue([mockSources.fullSourceYMovie]);
mocks.gatherAllEmbeds.mockReturnValue([]);
const p = makeProviders({
fetcher: null as any,
});
expect(p.listSources()).toEqual([
{
type: 'source',
id: 'y',
rank: mockSources.fullSourceYMovie.rank,
name: 'Y',
mediaTypes: ['movie'],
},
]);
});
it('should return the source with show type', () => {
mocks.gatherAllSources.mockReturnValue([mockSources.fullSourceYShow]);
mocks.gatherAllEmbeds.mockReturnValue([]);
const p = makeProviders({
fetcher: null as any,
});
expect(p.listSources()).toEqual([
{
type: 'source',
id: 'y',
rank: mockSources.fullSourceYShow.rank,
name: 'Y',
mediaTypes: ['show'],
},
]);
});
it('should return the source with both types', () => {
mocks.gatherAllSources.mockReturnValue([mockSources.fullSourceZBoth]);
mocks.gatherAllEmbeds.mockReturnValue([]);
const p = makeProviders({
fetcher: null as any,
});
expect(p.listSources()).toEqual([
{
type: 'source',
id: 'z',
rank: mockSources.fullSourceZBoth.rank,
name: 'Z',
mediaTypes: ['movie', 'show'],
},
]);
});
it('should return the sources in correct order', () => {
mocks.gatherAllSources.mockReturnValue([mockSources.fullSourceYMovie, mockSources.fullSourceZBoth]);
mocks.gatherAllEmbeds.mockReturnValue([]);
const p1 = makeProviders({
fetcher: null as any,
});
const l1 = p1.listSources();
expect(l1.map((v) => v.id).join(',')).toEqual('z,y');
mocks.gatherAllSources.mockReturnValue([mockSources.fullSourceZBoth, mockSources.fullSourceYMovie]);
mocks.gatherAllEmbeds.mockReturnValue([]);
const p2 = makeProviders({
fetcher: null as any,
});
const l2 = p2.listSources();
expect(l2.map((v) => v.id).join(',')).toEqual('z,y');
});
});
describe('ProviderControls.getAllEmbedMetaSorted()', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('should return the correct embed format', () => {
mocks.gatherAllSources.mockReturnValue([]);
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.fullEmbedX]);
const p = makeProviders({
fetcher: null as any,
});
expect(p.listEmbeds()).toEqual([
{
type: 'embed',
id: 'x',
rank: mockEmbeds.fullEmbedX.rank,
name: 'X',
},
]);
});
it('should return the embeds in correct order', () => {
mocks.gatherAllSources.mockReturnValue([]);
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.fullEmbedX, mockEmbeds.fullEmbedZ]);
const p1 = makeProviders({
fetcher: null as any,
});
const l1 = p1.listEmbeds();
expect(l1.map((v) => v.id).join(',')).toEqual('z,x');
mocks.gatherAllSources.mockReturnValue([]);
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.fullEmbedZ, mockEmbeds.fullEmbedX]);
const p2 = makeProviders({
fetcher: null as any,
});
const l2 = p2.listEmbeds();
expect(l2.map((v) => v.id).join(',')).toEqual('z,x');
});
});

View File

@ -0,0 +1,50 @@
import { mockEmbeds, mockSources } from '@/__test__/providerTests';
import { makeProviders } from '@/main/builder';
import { afterEach, describe, expect, it, vi } from 'vitest';
const mocks = await vi.hoisted(async () => (await import('@/__test__/providerTests')).makeProviderMocks());
vi.mock('@/providers/all', () => mocks);
describe('ProviderControls.getMetadata()', () => {
afterEach(() => {
vi.clearAllMocks();
});
it('should return null if not found', () => {
mocks.gatherAllSources.mockReturnValue([]);
mocks.gatherAllEmbeds.mockReturnValue([]);
const p = makeProviders({
fetcher: null as any,
});
expect(p.getMetadata(':)')).toEqual(null);
});
it('should return correct source meta', () => {
mocks.gatherAllSources.mockReturnValue([mockSources.fullSourceZBoth]);
mocks.gatherAllEmbeds.mockReturnValue([]);
const p = makeProviders({
fetcher: null as any,
});
expect(p.getMetadata(mockSources.fullSourceZBoth.id)).toEqual({
type: 'source',
id: 'z',
name: 'Z',
rank: mockSources.fullSourceZBoth.rank,
mediaTypes: ['movie', 'show'],
});
});
it('should return correct embed meta', () => {
mocks.gatherAllSources.mockReturnValue([]);
mocks.gatherAllEmbeds.mockReturnValue([mockEmbeds.fullEmbedX]);
const p = makeProviders({
fetcher: null as any,
});
expect(p.getMetadata(mockEmbeds.fullEmbedX.id)).toEqual({
type: 'embed',
id: 'x',
name: 'X',
rank: mockEmbeds.fullEmbedX.rank,
});
});
});

View File

@ -0,0 +1,54 @@
import { reorderOnIdList } from "@/utils/list";
import { describe, it, expect } from "vitest";
function list(def: string) {
return def.split(",").map(v=>({
rank: parseInt(v),
id: v,
}))
}
function expectListToEqual(l1: ReturnType<typeof list>, l2: ReturnType<typeof list>) {
function flatten(l: ReturnType<typeof list>) {
return l.map(v=>v.id).join(",");
}
expect(flatten(l1)).toEqual(flatten(l2));
}
describe('reorderOnIdList()', () => {
it('should reorder based on rank', () => {
const l = list('2,1,4,3');
const sortedList = list('4,3,2,1')
expectListToEqual(reorderOnIdList([], l), sortedList);
});
it('should work with empty input', () => {
expectListToEqual(reorderOnIdList([], []), []);
});
it('should reorder based on id list', () => {
const l = list('4,2,1,3');
const sortedList = list('4,3,2,1')
expectListToEqual(reorderOnIdList(["4","3","2","1"], l), sortedList);
});
it('should reorder based on id list and rank second', () => {
const l = list('4,2,1,3');
const sortedList = list('4,3,2,1')
expectListToEqual(reorderOnIdList(["4","3"], l), sortedList);
});
it('should work with only one item', () => {
const l = list('1');
const sortedList = list('1')
expectListToEqual(reorderOnIdList(["1"], l), sortedList);
expectListToEqual(reorderOnIdList([], l), sortedList);
});
it('should not affect original list', () => {
const l = list('4,3,2,1');
const unsortedList = list('4,3,2,1')
reorderOnIdList([], l);
expectListToEqual(l, unsortedList);
});
});

View File

@ -4,7 +4,7 @@ import { FullScraperEvents } from '@/main/events';
import { ScrapeMedia } from '@/main/media'; import { ScrapeMedia } from '@/main/media';
import { MetaOutput, getAllEmbedMetaSorted, getAllSourceMetaSorted, getSpecificId } from '@/main/meta'; import { MetaOutput, getAllEmbedMetaSorted, getAllSourceMetaSorted, getSpecificId } from '@/main/meta';
import { RunOutput, runAllProviders } from '@/main/runner'; import { RunOutput, runAllProviders } from '@/main/runner';
import { getProviders } from '@/providers/all'; import { getProviders } from '@/providers/get';
export interface ProviderBuilderOptions { export interface ProviderBuilderOptions {
// fetcher, every web request gets called through here // fetcher, every web request gets called through here

View File

@ -1,5 +1,6 @@
import { MediaTypes } from '@/main/media'; import { MediaTypes } from '@/main/media';
import { ProviderList } from '@/providers/all'; import { Embed, Sourcerer } from '@/providers/base';
import { ProviderList } from '@/providers/get';
export type MetaOutput = { export type MetaOutput = {
type: 'embed' | 'source'; type: 'embed' | 'source';
@ -9,36 +10,45 @@ export type MetaOutput = {
mediaTypes?: Array<MediaTypes>; mediaTypes?: Array<MediaTypes>;
}; };
export function getAllSourceMetaSorted(list: ProviderList): MetaOutput[] { function formatSourceMeta(v: Sourcerer): MetaOutput {
return list.sources const types: Array<MediaTypes> = [];
.sort((a, b) => b.rank - a.rank) if (v.scrapeMovie) types.push('movie');
.map((v) => { if (v.scrapeShow) types.push('show');
const types: Array<MediaTypes> = []; return {
if (v.scrapeMovie) types.push('movie'); type: 'source',
if (v.scrapeShow) types.push('show'); id: v.id,
return { rank: v.rank,
type: 'source', name: v.name,
id: v.id, mediaTypes: types,
rank: v.rank, };
name: v.name,
mediaTypes: types,
};
});
} }
export function getAllEmbedMetaSorted(_list: ProviderList): MetaOutput[] { function formatEmbedMeta(v: Embed): MetaOutput {
return []; return {
type: 'embed',
id: v.id,
rank: v.rank,
name: v.name,
};
}
export function getAllSourceMetaSorted(list: ProviderList): MetaOutput[] {
return list.sources.sort((a, b) => b.rank - a.rank).map(formatSourceMeta);
}
export function getAllEmbedMetaSorted(list: ProviderList): MetaOutput[] {
return list.embeds.sort((a, b) => b.rank - a.rank).map(formatEmbedMeta);
} }
export function getSpecificId(list: ProviderList, id: string): MetaOutput | null { export function getSpecificId(list: ProviderList, id: string): MetaOutput | null {
const foundSource = list.sources.find((v) => v.id === id); const foundSource = list.sources.find((v) => v.id === id);
if (foundSource) { if (foundSource) {
return { return formatSourceMeta(foundSource);
type: 'source', }
id: foundSource.id,
name: foundSource.name, const foundEmbed = list.embeds.find((v) => v.id === id);
rank: foundSource.rank, if (foundEmbed) {
}; return formatEmbedMeta(foundEmbed);
} }
return null; return null;

View File

@ -1,38 +1,13 @@
import { Embed, Sourcerer } from '@/providers/base'; import { Embed, Sourcerer } from '@/providers/base';
import { upcloudScraper } from '@/providers/embeds/upcloud'; import { upcloudScraper } from '@/providers/embeds/upcloud';
import { flixhqScraper } from '@/providers/sources/flixhq/index'; import { flixhqScraper } from '@/providers/sources/flixhq/index';
import { hasDuplicates } from '@/utils/predicates';
function gatherAllSources(): Array<Sourcerer> { export function gatherAllSources(): Array<Sourcerer> {
// all sources are gathered here // all sources are gathered here
return [flixhqScraper]; return [flixhqScraper];
} }
function gatherAllEmbeds(): Array<Embed> { export function gatherAllEmbeds(): Array<Embed> {
// all embeds are gathered here // all embeds are gathered here
return [upcloudScraper]; return [upcloudScraper];
} }
export interface ProviderList {
sources: Sourcerer[];
embeds: Embed[];
}
export function getProviders(): ProviderList {
const sources = gatherAllSources().filter((v) => !v?.disabled);
const embeds = gatherAllEmbeds().filter((v) => !v?.disabled);
const combined = [...sources, ...embeds];
const anyDuplicateId = hasDuplicates(combined.map((v) => v.id));
const anyDuplicateSourceRank = hasDuplicates(sources.map((v) => v.rank));
const anyDuplicateEmbedRank = hasDuplicates(embeds.map((v) => v.rank));
if (anyDuplicateId) throw new Error('Duplicate id found in sources/embeds');
if (anyDuplicateSourceRank) throw new Error('Duplicate rank found in sources');
if (anyDuplicateEmbedRank) throw new Error('Duplicate rank found in embeds');
return {
sources,
embeds,
};
}

27
src/providers/get.ts Normal file
View File

@ -0,0 +1,27 @@
import { gatherAllEmbeds, gatherAllSources } from '@/providers/all';
import { Embed, Sourcerer } from '@/providers/base';
import { hasDuplicates } from '@/utils/predicates';
export interface ProviderList {
sources: Sourcerer[];
embeds: Embed[];
}
export function getProviders(): ProviderList {
const sources = gatherAllSources().filter((v) => !v?.disabled);
const embeds = gatherAllEmbeds().filter((v) => !v?.disabled);
const combined = [...sources, ...embeds];
const anyDuplicateId = hasDuplicates(combined.map((v) => v.id));
const anyDuplicateSourceRank = hasDuplicates(sources.map((v) => v.rank));
const anyDuplicateEmbedRank = hasDuplicates(embeds.map((v) => v.rank));
if (anyDuplicateId) throw new Error('Duplicate id found in sources/embeds');
if (anyDuplicateSourceRank) throw new Error('Duplicate rank found in sources');
if (anyDuplicateEmbedRank) throw new Error('Duplicate rank found in embeds');
return {
sources,
embeds,
};
}

View File

@ -10,8 +10,8 @@ export function reorderOnIdList<T extends { rank: number; id: string }[]>(order:
// only one in order list // only one in order list
// negative means order [a,b] // negative means order [a,b]
// positive means order [b,a] // positive means order [b,a]
if (aIndex < 0) return 1; // A isnt in list, so A goes later on the list if (bIndex >= 0) return 1; // A isnt in list but B is, so A goes later on the list
if (bIndex < 0) return -1; // B isnt in list, so B goes later on the list if (aIndex >= 0) return -1; // B isnt in list but A is, so B goes later on the list
// both not in list, sort on rank // both not in list, sort on rank
return b.rank - a.rank; return b.rank - a.rank;