|
@@ -0,0 +1,76 @@
|
|
|
|
|
+import { Test, TestingModule } from '@nestjs/testing';
|
|
|
|
|
+import { ScrapeUseCase } from '../src//application/use-cases/scrape.use-case';
|
|
|
|
|
+import { DirectFetchAdapter } from '../src//infrastructure/adapters/direct-fetch.adapter';
|
|
|
|
|
+import { BrowserlessAdapter } from '../src//infrastructure/adapters/browserless.adapter';
|
|
|
|
|
+import { ReadabilityMarkdownConverter } from '../src//infrastructure/adapters/readability-markdown.converter';
|
|
|
|
|
+
|
|
|
|
|
+describe('ScrapeUseCase', () => {
|
|
|
|
|
+ let useCase: ScrapeUseCase;
|
|
|
|
|
+ let directMock: Mock<DirectFetchAdapter>;
|
|
|
|
|
+ let browserlessMock: Mock<BrowserlessAdapter>;
|
|
|
|
|
+ let converterMock: Mock<ReadabilityMarkdownConverter>;
|
|
|
|
|
+
|
|
|
|
|
+ const sampleHtml = (len: number) => '<html><body>' + 'x'.repeat(len) + '</body></html>';
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async () => {
|
|
|
|
|
+ directMock = { fetch: vi.fn() } as any;
|
|
|
|
|
+ browserlessMock = { fetch: vi.fn() } as any;
|
|
|
|
|
+ converterMock = { convert: vi.fn() } as any;
|
|
|
|
|
+
|
|
|
|
|
+ const module: TestingModule = await Test.createTestingModule({
|
|
|
|
|
+ providers: [
|
|
|
|
|
+ ScrapeUseCase,
|
|
|
|
|
+ { provide: DirectFetchAdapter, useValue: directMock },
|
|
|
|
|
+ { provide: BrowserlessAdapter, useValue: browserlessMock },
|
|
|
|
|
+ { provide: ReadabilityMarkdownConverter, useValue: converterMock },
|
|
|
|
|
+ ],
|
|
|
|
|
+ }).compile();
|
|
|
|
|
+
|
|
|
|
|
+ useCase = module.get(ScrapeUseCase);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('uses direct fetch when content is sufficient', async () => {
|
|
|
|
|
+ const html = sampleHtml(2000);
|
|
|
|
|
+ directMock.fetch.mockResolvedValue(html);
|
|
|
|
|
+ converterMock.convert.mockResolvedValue({ title: 'T', markdown: 'M' });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await useCase.execute({ url: 'https://example.com' });
|
|
|
|
|
+
|
|
|
|
|
+ expect(directMock.fetch).toHaveBeenCalled();
|
|
|
|
|
+ expect(browserlessMock.fetch).not.toHaveBeenCalled();
|
|
|
|
|
+ expect(result.metadata.source).toBe('direct');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('falls back to browserless when direct content is insufficient', async () => {
|
|
|
|
|
+ const shortHtml = '<html><body>short</body></html>';
|
|
|
|
|
+ directMock.fetch.mockResolvedValue(shortHtml);
|
|
|
|
|
+ browserlessMock.fetch.mockResolvedValue(sampleHtml(2000));
|
|
|
|
|
+ converterMock.convert.mockResolvedValue({ title: 'T', markdown: 'M' });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await useCase.execute({ url: 'https://example.com' });
|
|
|
|
|
+
|
|
|
|
|
+ expect(browserlessMock.fetch).toHaveBeenCalled();
|
|
|
|
|
+ expect(result.metadata.source).toBe('browserless');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('uses browserless directly when render=true', async () => {
|
|
|
|
|
+ browserlessMock.fetch.mockResolvedValue(sampleHtml(2000));
|
|
|
|
|
+ converterMock.convert.mockResolvedValue({ title: 'T', markdown: 'M' });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await useCase.execute({ url: 'https://example.com', render: true });
|
|
|
|
|
+
|
|
|
|
|
+ expect(directMock.fetch).not.toHaveBeenCalled();
|
|
|
|
|
+ expect(browserlessMock.fetch).toHaveBeenCalled();
|
|
|
|
|
+ expect(result.metadata.source).toBe('browserless');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('returns markdown and title from converter', async () => {
|
|
|
|
|
+ directMock.fetch.mockResolvedValue(sampleHtml(2000));
|
|
|
|
|
+ converterMock.convert.mockResolvedValue({ title: 'Page Title', markdown: '# Hello' });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await useCase.execute({ url: 'https://example.com' });
|
|
|
|
|
+
|
|
|
|
|
+ expect(result.title).toBe('Page Title');
|
|
|
|
|
+ expect(result.markdown).toBe('# Hello');
|
|
|
|
|
+ });
|
|
|
|
|
+});
|