test: hand-rolled, explicit Fetcher mock

This commit is contained in:
WebStreamr 2025-06-13 21:53:11 +02:00
parent 4bc25692a8
commit 3a4ff73953
No known key found for this signature in database
27 changed files with 65 additions and 157 deletions

View file

@ -1,6 +1,7 @@
import type { Config } from 'jest';
const config: Config = {
automock: false,
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: [

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { DoodStream } from './DoodStream';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new DoodStream(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new DoodStream(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { Dropload } from './Dropload';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new Dropload(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new Dropload(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { ExternalUrl } from './ExternalUrl';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new ExternalUrl(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new ExternalUrl(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { ExtractorRegistry } from './ExtractorRegistry';
import { Context, CountryCode } from '../types';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { createExtractors } from './index';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, createExtractors(fetcher));
const extractorRegistry = new ExtractorRegistry(logger, createExtractors(new FetcherMock()));
describe('ExtractorRegistry', () => {
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { Fsst } from './Fsst';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new Fsst(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new Fsst(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { KinoGer } from './KinoGer';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new KinoGer(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new KinoGer(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { Soaper } from './Soaper';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new Soaper(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new Soaper(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { SuperVideo } from './SuperVideo';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new SuperVideo(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new SuperVideo(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,14 +1,11 @@
import winston from 'winston';
import { Fetcher } from '../utils';
import { FetcherMock } from '../utils';
import { Context, CountryCode } from '../types';
import { ExtractorRegistry } from './ExtractorRegistry';
import { VidSrc } from './VidSrc';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const extractorRegistry = new ExtractorRegistry(logger, [new VidSrc(fetcher)]);
const extractorRegistry = new ExtractorRegistry(logger, [new VidSrc(new FetcherMock())]);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: {} };

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { CineHDPlus } from './CineHDPlus';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { es: 'on', mx: 'on' } };
describe('CineHDPlus', () => {
let handler: CineHDPlus;
beforeEach(() => {
handler = new CineHDPlus(fetcher);
handler = new CineHDPlus(new FetcherMock());
});
test('handles non-existent series gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { Eurostreaming } from './Eurostreaming';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { it: 'on' } };
describe('Eurostreaming', () => {
let handler: Eurostreaming;
beforeEach(() => {
handler = new Eurostreaming(fetcher);
handler = new Eurostreaming(new FetcherMock());
});
test('handles non-existent series gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { Frembed } from './Frembed';
import { Fetcher, ImdbId, TmdbId } from '../utils';
import { FetcherMock, ImdbId, TmdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { fr: 'on' } };
describe('Frembed', () => {
let handler: Frembed;
beforeEach(() => {
handler = new Frembed(fetcher);
handler = new Frembed(new FetcherMock());
});
test('handle imdb black mirror s4e2', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { FrenchCloud } from './FrenchCloud';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { fr: 'on' } };
describe('FrenchCloud', () => {
let handler: FrenchCloud;
beforeEach(() => {
handler = new FrenchCloud(fetcher);
handler = new FrenchCloud(new FetcherMock());
});
test('handles non-existent movies gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { KinoGer } from './KinoGer';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } };
describe('KinoGer', () => {
let handler: KinoGer;
beforeEach(() => {
handler = new KinoGer(fetcher);
handler = new KinoGer(new FetcherMock());
});
test('handles non-existent movies gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { MeineCloud } from './MeineCloud';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } };
describe('MeineCloud', () => {
let handler: MeineCloud;
beforeEach(() => {
handler = new MeineCloud(fetcher);
handler = new MeineCloud(new FetcherMock());
});
test('handles non-existent movies gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { MostraGuarda } from './MostraGuarda';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { it: 'on' } };
describe('MostraGuarda', () => {
let handler: MostraGuarda;
beforeEach(() => {
handler = new MostraGuarda(fetcher);
handler = new MostraGuarda(new FetcherMock());
});
test('handles non-existent movies gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { Soaper } from './Soaper';
import { Fetcher, ImdbId, TmdbId } from '../utils';
import { FetcherMock, ImdbId, TmdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { en: 'on' } };
describe('Soaper', () => {
let handler: Soaper;
beforeEach(() => {
handler = new Soaper(fetcher);
handler = new Soaper(new FetcherMock());
});
test('handles non-existent movies gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { StreamKiste } from './StreamKiste';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } };
describe('StreamKiste', () => {
let handler: StreamKiste;
beforeEach(() => {
handler = new StreamKiste(fetcher);
handler = new StreamKiste(new FetcherMock());
});
test('handles non-existent series gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { VerHdLink } from './VerHdLink';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { es: 'on', mx: 'on' } };
describe('VerHdLink', () => {
let handler: VerHdLink;
beforeEach(() => {
handler = new VerHdLink(fetcher);
handler = new VerHdLink(new FetcherMock());
});
test('handles non-existent movies gracefully', async () => {

View file

@ -1,19 +1,14 @@
import winston from 'winston';
import { VidSrc } from './VidSrc';
import { Fetcher, ImdbId } from '../utils';
import { FetcherMock, ImdbId } from '../utils';
import { Context } from '../types';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { en: 'on' } };
describe('VidSrc', () => {
let handler: VidSrc;
beforeEach(() => {
handler = new VidSrc(fetcher);
handler = new VidSrc(new FetcherMock());
});
test('handle imdb black mirror s4e2', async () => {

View file

@ -76,7 +76,7 @@ export class Fetcher {
return (await this.cachedFetch(ctx, url, { ...init, method: 'HEAD' })).policy.responseHeaders();
};
public getInit(ctx: Context, url: URL, init?: CustomRequestInit): CustomRequestInit {
private getInit(ctx: Context, url: URL, init?: CustomRequestInit): CustomRequestInit {
const cookieString = this.cookieJar.getCookieStringSync(url.href);
const noReferer = init?.noReferer ?? false;
@ -206,7 +206,7 @@ export class Fetcher {
return this.handleHttpCacheItem(ctx, httpCacheItem, url, init);
};
private async fetchWithTimeout(ctx: Context, url: URL, init?: CustomRequestInit): Promise<Response> {
protected async fetchWithTimeout(ctx: Context, url: URL, init?: CustomRequestInit): Promise<Response> {
this.logger.info(`Fetch ${init?.method ?? 'GET'} ${url}`, ctx);
const controller = new AbortController();

View file

@ -1,32 +1,32 @@
/* istanbul ignore file */
import fs from 'node:fs';
import slugify from 'slugify';
import winston from 'winston';
import crypto from 'crypto';
import { Context } from '../../types';
import { envGet } from '../env';
const { Fetcher } = jest.requireActual('../Fetcher');
import CachePolicy from 'http-cache-semantics';
import { Context } from '../types';
import { envGet } from './env';
import { Fetcher } from './Fetcher';
class MockedFetcher {
private readonly fetcher: typeof Fetcher;
public constructor(logger: winston.Logger) {
this.fetcher = new Fetcher(logger);
export class FetcherMock extends Fetcher {
public constructor() {
super(winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }));
}
public readonly text = async (ctx: Context, url: URL, init?: RequestInit): Promise<string> => {
const path = `${__dirname}/../__fixtures__/Fetcher/${this.slugifyUrl(url)}`;
public override async text(ctx: Context, url: URL, init?: RequestInit): Promise<string> {
const path = `${__dirname}/__fixtures__/Fetcher/${this.slugifyUrl(url)}`;
return this.fetch(path, ctx, url, init);
};
public readonly textPost = async (ctx: Context, url: URL, body: string, init?: RequestInit): Promise<string> => {
const path = `${__dirname}/../__fixtures__/Fetcher/post-${this.slugifyUrl(url)}-${slugify(body)}`;
public override async textPost(ctx: Context, url: URL, body: string, init?: RequestInit): Promise<string> {
const path = `${__dirname}/__fixtures__/Fetcher/post-${this.slugifyUrl(url)}-${slugify(body)}`;
return this.fetch(path, ctx, url, { ...init, method: 'POST', body });
};
public readonly head = async (ctx: Context, url: URL, init?: RequestInit): Promise<unknown> => {
const path = `${__dirname}/../__fixtures__/Fetcher/head-${this.slugifyUrl(url)}`;
public override async head(ctx: Context, url: URL, init?: RequestInit): Promise<CachePolicy.Headers> {
const path = `${__dirname}/__fixtures__/Fetcher/head-${this.slugifyUrl(url)}`;
return JSON.parse(await this.fetch(path, ctx, url, { ...init, method: 'HEAD' }));
};
@ -50,7 +50,7 @@ class MockedFetcher {
let response;
try {
if (envGet('TEST_UPDATE_FIXTURES')) {
response = await fetch(url, this.fetcher.getInit(ctx, url, init));
response = await super.fetchWithTimeout(ctx, url, init);
} else {
console.error(`No fixture found at "${path}".`);
process.exit(1);
@ -79,5 +79,3 @@ class MockedFetcher {
}
};
}
export { MockedFetcher as Fetcher };

View file

@ -3,15 +3,13 @@ import winston from 'winston';
import { createExtractors, Extractor, ExtractorRegistry } from '../extractor';
import { StreamResolver } from './StreamResolver';
import { Source, SourceResult, MeineCloud, MostraGuarda } from '../source';
import { Fetcher } from './Fetcher';
import { BlockedReason, Context, CountryCode, TIMEOUT, UrlResult } from '../types';
import { BlockedError, HttpError, NotFoundError, QueueIsFullError } from '../error';
import { ImdbId } from './id';
jest.mock('../utils/Fetcher');
import { FetcherMock } from './FetcherMock';
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const fetcher = new FetcherMock();
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on', it: 'on' } };
const meineCloud = new MeineCloud(fetcher);

View file

@ -1,4 +1,5 @@
export * from './Fetcher';
export * from './FetcherMock';
export * from './StreamResolver';
export * from './config';
export * from './embed';

View file

@ -1,12 +1,8 @@
import winston from 'winston';
import { buildManifest } from './manifest';
import { StreamKiste, MeineCloud, VerHdLink } from '../source';
import { Fetcher } from './Fetcher';
import { FetcherMock } from './FetcherMock';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const fetcher = new FetcherMock();
describe('buildManifest', () => {
test('has unchecked source without a config', () => {

View file

@ -1,13 +1,9 @@
import { getImdbIdFromTmdbId, getTmdbIdFromImdbId } from './tmdb';
import { Fetcher } from './Fetcher';
import { Context } from '../types';
import { ImdbId, TmdbId } from './id';
import winston from 'winston';
import { FetcherMock } from './FetcherMock';
jest.mock('../utils/Fetcher');
const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] });
const fetcher = new Fetcher(logger);
const fetcher = new FetcherMock();
const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } };
describe('getTmdbIdFromImdbId', () => {