From bcf312d1b3e06bc34ab2d3447771dceedceb84a2 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 5 Sep 2023 20:57:10 +0200 Subject: [PATCH] unit tests for provider checks, provider listings, utils and provider meta --- src/__test__/oof.test.ts | 7 -- src/__test__/providerTests.ts | 122 ++++++++++++++++++++++++++ src/__test__/providers/checks.test.ts | 63 +++++++++++++ src/__test__/runner/list.test.ts | 121 +++++++++++++++++++++++++ src/__test__/runner/meta.test.ts | 50 +++++++++++ src/__test__/utils/list.test.ts | 54 ++++++++++++ src/main/builder.ts | 2 +- src/main/meta.ts | 58 +++++++----- src/providers/all.ts | 29 +----- src/providers/get.ts | 27 ++++++ src/utils/list.ts | 4 +- 11 files changed, 476 insertions(+), 61 deletions(-) delete mode 100644 src/__test__/oof.test.ts create mode 100644 src/__test__/providerTests.ts create mode 100644 src/__test__/providers/checks.test.ts create mode 100644 src/__test__/runner/list.test.ts create mode 100644 src/__test__/runner/meta.test.ts create mode 100644 src/__test__/utils/list.test.ts create mode 100644 src/providers/get.ts diff --git a/src/__test__/oof.test.ts b/src/__test__/oof.test.ts deleted file mode 100644 index f17bde3..0000000 --- a/src/__test__/oof.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe('oof.ts', () => { - it('should contain hello', () => { - expect('hello').toContain('hello'); - }); -}); diff --git a/src/__test__/providerTests.ts b/src/__test__/providerTests.ts new file mode 100644 index 0000000..a8c0fa8 --- /dev/null +++ b/src/__test__/providerTests.ts @@ -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, ReturnType>(); + const sourcesMock = vi.fn, ReturnType>(); + 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, +}; diff --git a/src/__test__/providers/checks.test.ts b/src/__test__/providers/checks.test.ts new file mode 100644 index 0000000..71bef7c --- /dev/null +++ b/src/__test__/providers/checks.test.ts @@ -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], + }); + }); +}); diff --git a/src/__test__/runner/list.test.ts b/src/__test__/runner/list.test.ts new file mode 100644 index 0000000..7c46648 --- /dev/null +++ b/src/__test__/runner/list.test.ts @@ -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'); + }); +}); diff --git a/src/__test__/runner/meta.test.ts b/src/__test__/runner/meta.test.ts new file mode 100644 index 0000000..4db1fdc --- /dev/null +++ b/src/__test__/runner/meta.test.ts @@ -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, + }); + }); +}); diff --git a/src/__test__/utils/list.test.ts b/src/__test__/utils/list.test.ts new file mode 100644 index 0000000..9ebf93e --- /dev/null +++ b/src/__test__/utils/list.test.ts @@ -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, l2: ReturnType) { + function flatten(l: ReturnType) { + 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); + }); +}); diff --git a/src/main/builder.ts b/src/main/builder.ts index 2cf50fe..7035e6d 100644 --- a/src/main/builder.ts +++ b/src/main/builder.ts @@ -4,7 +4,7 @@ import { FullScraperEvents } from '@/main/events'; import { ScrapeMedia } from '@/main/media'; import { MetaOutput, getAllEmbedMetaSorted, getAllSourceMetaSorted, getSpecificId } from '@/main/meta'; import { RunOutput, runAllProviders } from '@/main/runner'; -import { getProviders } from '@/providers/all'; +import { getProviders } from '@/providers/get'; export interface ProviderBuilderOptions { // fetcher, every web request gets called through here diff --git a/src/main/meta.ts b/src/main/meta.ts index cbd8297..5696183 100644 --- a/src/main/meta.ts +++ b/src/main/meta.ts @@ -1,5 +1,6 @@ import { MediaTypes } from '@/main/media'; -import { ProviderList } from '@/providers/all'; +import { Embed, Sourcerer } from '@/providers/base'; +import { ProviderList } from '@/providers/get'; export type MetaOutput = { type: 'embed' | 'source'; @@ -9,36 +10,45 @@ export type MetaOutput = { mediaTypes?: Array; }; -export function getAllSourceMetaSorted(list: ProviderList): MetaOutput[] { - return list.sources - .sort((a, b) => b.rank - a.rank) - .map((v) => { - const types: Array = []; - if (v.scrapeMovie) types.push('movie'); - if (v.scrapeShow) types.push('show'); - return { - type: 'source', - id: v.id, - rank: v.rank, - name: v.name, - mediaTypes: types, - }; - }); +function formatSourceMeta(v: Sourcerer): MetaOutput { + const types: Array = []; + if (v.scrapeMovie) types.push('movie'); + if (v.scrapeShow) types.push('show'); + return { + type: 'source', + id: v.id, + rank: v.rank, + name: v.name, + mediaTypes: types, + }; } -export function getAllEmbedMetaSorted(_list: ProviderList): MetaOutput[] { - return []; +function formatEmbedMeta(v: Embed): MetaOutput { + 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 { const foundSource = list.sources.find((v) => v.id === id); if (foundSource) { - return { - type: 'source', - id: foundSource.id, - name: foundSource.name, - rank: foundSource.rank, - }; + return formatSourceMeta(foundSource); + } + + const foundEmbed = list.embeds.find((v) => v.id === id); + if (foundEmbed) { + return formatEmbedMeta(foundEmbed); } return null; diff --git a/src/providers/all.ts b/src/providers/all.ts index c2c924d..e9f7c1d 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -1,38 +1,13 @@ import { Embed, Sourcerer } from '@/providers/base'; import { upcloudScraper } from '@/providers/embeds/upcloud'; import { flixhqScraper } from '@/providers/sources/flixhq/index'; -import { hasDuplicates } from '@/utils/predicates'; -function gatherAllSources(): Array { +export function gatherAllSources(): Array { // all sources are gathered here return [flixhqScraper]; } -function gatherAllEmbeds(): Array { +export function gatherAllEmbeds(): Array { // all embeds are gathered here 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, - }; -} diff --git a/src/providers/get.ts b/src/providers/get.ts new file mode 100644 index 0000000..689617f --- /dev/null +++ b/src/providers/get.ts @@ -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, + }; +} diff --git a/src/utils/list.ts b/src/utils/list.ts index 75479c4..8d619fb 100644 --- a/src/utils/list.ts +++ b/src/utils/list.ts @@ -10,8 +10,8 @@ export function reorderOnIdList(order: // only one in order list // negative means order [a,b] // 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; // B isnt in list, so B 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 (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 return b.rank - a.rank;