scrape.use-case.spec.ts 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { Test, TestingModule } from '@nestjs/testing';
  2. import { ScrapeUseCase } from '../src//application/use-cases/scrape.use-case';
  3. import { DirectFetchAdapter } from '../src//infrastructure/adapters/direct-fetch.adapter';
  4. import { BrowserlessAdapter } from '../src//infrastructure/adapters/browserless.adapter';
  5. import { ReadabilityMarkdownConverter } from '../src//infrastructure/adapters/readability-markdown.converter';
  6. describe('ScrapeUseCase', () => {
  7. let useCase: ScrapeUseCase;
  8. let directMock: Mock<DirectFetchAdapter>;
  9. let browserlessMock: Mock<BrowserlessAdapter>;
  10. let converterMock: Mock<ReadabilityMarkdownConverter>;
  11. const sampleHtml = (len: number) => '<html><body>' + 'x'.repeat(len) + '</body></html>';
  12. beforeEach(async () => {
  13. directMock = { fetch: vi.fn() } as any;
  14. browserlessMock = { fetch: vi.fn() } as any;
  15. converterMock = { convert: vi.fn() } as any;
  16. const module: TestingModule = await Test.createTestingModule({
  17. providers: [
  18. ScrapeUseCase,
  19. { provide: DirectFetchAdapter, useValue: directMock },
  20. { provide: BrowserlessAdapter, useValue: browserlessMock },
  21. { provide: ReadabilityMarkdownConverter, useValue: converterMock },
  22. ],
  23. }).compile();
  24. useCase = module.get(ScrapeUseCase);
  25. });
  26. it('uses direct fetch when content is sufficient', async () => {
  27. const html = sampleHtml(2000);
  28. directMock.fetch.mockResolvedValue(html);
  29. converterMock.convert.mockResolvedValue({ title: 'T', markdown: 'M' });
  30. const result = await useCase.execute({ url: 'https://example.com' });
  31. expect(directMock.fetch).toHaveBeenCalled();
  32. expect(browserlessMock.fetch).not.toHaveBeenCalled();
  33. expect(result.metadata.source).toBe('direct');
  34. });
  35. it('falls back to browserless when direct content is insufficient', async () => {
  36. const shortHtml = '<html><body>short</body></html>';
  37. directMock.fetch.mockResolvedValue(shortHtml);
  38. browserlessMock.fetch.mockResolvedValue(sampleHtml(2000));
  39. converterMock.convert.mockResolvedValue({ title: 'T', markdown: 'M' });
  40. const result = await useCase.execute({ url: 'https://example.com' });
  41. expect(browserlessMock.fetch).toHaveBeenCalled();
  42. expect(result.metadata.source).toBe('browserless');
  43. });
  44. it('uses browserless directly when render=true', async () => {
  45. browserlessMock.fetch.mockResolvedValue(sampleHtml(2000));
  46. converterMock.convert.mockResolvedValue({ title: 'T', markdown: 'M' });
  47. const result = await useCase.execute({ url: 'https://example.com', render: true });
  48. expect(directMock.fetch).not.toHaveBeenCalled();
  49. expect(browserlessMock.fetch).toHaveBeenCalled();
  50. expect(result.metadata.source).toBe('browserless');
  51. });
  52. it('returns markdown and title from converter', async () => {
  53. directMock.fetch.mockResolvedValue(sampleHtml(2000));
  54. converterMock.convert.mockResolvedValue({ title: 'Page Title', markdown: '# Hello' });
  55. const result = await useCase.execute({ url: 'https://example.com' });
  56. expect(result.title).toBe('Page Title');
  57. expect(result.markdown).toBe('# Hello');
  58. });
  59. });