import { ContentType } from 'stremio-addon-sdk'; import winston from 'winston'; import { createExtractors, Extractor, ExtractorRegistry } from '../extractor'; import { StreamResolver } from './StreamResolver'; import { Source, SourceResult, MeineCloud, MostraGuarda } from '../source'; import { BlockedReason, Context, CountryCode, TIMEOUT, UrlResult } from '../types'; import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TooManyRequestsError } from '../error'; import { ImdbId } from './id'; import { FetcherMock } from './FetcherMock'; const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }); const fetcher = new FetcherMock(`${__dirname}/__fixtures__/StreamResolver`); const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on', it: 'on' } }; const meineCloud = new MeineCloud(fetcher); const mostraGuarda = new MostraGuarda(fetcher); describe('resolve', () => { test('returns info as stream if no sources were configured', async () => { const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher))); const streams = await streamResolver.resolve(ctx, [], 'movie', new ImdbId('tt123456789', undefined, undefined)); expect(streams).toMatchSnapshot(); }); test('returns source errors as stream', async () => { const fetcherSpy = jest.spyOn(fetcher, 'text').mockRejectedValue('ups, an error occurred.'); const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher))); const streams = await streamResolver.resolve(ctx, [meineCloud], 'movie', new ImdbId('tt123456789', undefined, undefined)); expect(streams).toMatchSnapshot(); fetcherSpy.mockRestore(); }); test('returns empty array if no source found anything', async () => { const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher))); const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toMatchSnapshot(); }); test('returns empty array if no source supported the type', async () => { const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher))); const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'series', new ImdbId('tt12345678', 1, 1)); expect(streams).toMatchSnapshot(); }); test('returns sorted results', async () => { const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher))); const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'movie', new ImdbId('tt29141112', undefined, undefined)); expect(streams).toMatchSnapshot(); }); test('adds error info', async () => { class MockHandler implements Source { public readonly id = 'mockhandler'; public readonly label = 'MockHandler'; public readonly contentTypes: ContentType[] = ['movie']; public readonly countryCodes: CountryCode[] = [CountryCode.de]; public readonly handle = async (): Promise => { return [{ countryCode: CountryCode.de, url: new URL('https://example.com') }]; }; } class MockExtractor extends Extractor { public readonly id = 'mockextractor'; public readonly label = 'MockExtractor'; public override readonly ttl = 1; public readonly supports = (): boolean => true; protected readonly extractInternal = async (): Promise => [ { url: new URL('https://example.com'), isExternal: true, error: new BlockedError(BlockedReason.cloudflare_challenge, {}), label: 'hoster.com', sourceId: '', meta: { countryCode: CountryCode.de, }, }, { url: new URL('https://example.com'), isExternal: true, error: new BlockedError(BlockedReason.unknown, {}), label: 'hoster.com', sourceId: '', meta: { countryCode: CountryCode.de, }, }, { url: new URL('https://example.com'), isExternal: true, error: new TooManyRequestsError(10), label: 'hoster.com', sourceId: '', meta: { countryCode: CountryCode.de, }, }, { url: new URL('https://example2.com'), isExternal: true, error: new TypeError(), label: 'hoster.com', sourceId: '', meta: { countryCode: CountryCode.de, }, }, { url: new URL('https://example2.com'), isExternal: true, error: TIMEOUT, label: 'hoster.com', sourceId: '', meta: { countryCode: CountryCode.de, }, }, { url: new URL('https://example3.com'), isExternal: true, error: new QueueIsFullError(), label: 'hoster.com', sourceId: '', meta: { countryCode: CountryCode.de, }, }, { url: new URL('https://example4.com'), isExternal: true, error: new HttpError(500, 'Internal Server Error', { 'x-foo': 'bar' }), label: 'hoster.com', sourceId: '', meta: { countryCode: CountryCode.de, }, }, ]; } const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, [new MockExtractor()])); const streams = await streamResolver.resolve(ctx, [new MockHandler()], 'movie', new ImdbId('tt11655566', undefined, undefined)); expect(streams).toMatchSnapshot(); const streamsWithoutExternalUrls = await streamResolver.resolve({ ...ctx, config: { ...ctx.config, excludeExternalUrls: 'on' } }, [new MockHandler()], 'movie', new ImdbId('tt11655566', undefined, undefined)); expect(streamsWithoutExternalUrls).toMatchSnapshot(); }); test('ignores not found errors', async () => { const mockHandler: Source = { id: 'mockhandler', label: 'MockHandler', contentTypes: ['movie'], countryCodes: [CountryCode.de], handle: jest.fn().mockRejectedValue(new NotFoundError()), }; const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher))); const streams = await streamResolver.resolve(ctx, [mockHandler], 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toMatchSnapshot(); }); });