236 lines
9.2 KiB
TypeScript
236 lines
9.2 KiB
TypeScript
import { log, Request, RequestList, RequestManagerTandem, RequestQueue } from '@crawlee/core';
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
import { MemoryStorageEmulator } from '../shared/MemoryStorageEmulator';
|
|
|
|
describe('RequestManagerTandem', () => {
|
|
let logLevel: number;
|
|
const emulator = new MemoryStorageEmulator();
|
|
|
|
beforeAll(() => {
|
|
logLevel = log.getLevel();
|
|
log.setLevel(log.LEVELS.ERROR);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await emulator.init();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
log.setLevel(logLevel);
|
|
await emulator.destroy();
|
|
});
|
|
|
|
test('fetchNextRequest transfers from list to queue when queue is empty', async () => {
|
|
// Create sources with 3 URLs
|
|
const requestList = await RequestList.open(null, [
|
|
{ url: 'https://example.com/1' },
|
|
{ url: 'https://example.com/2' },
|
|
{ url: 'https://example.com/3' },
|
|
]);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
// Mock the addRequest function of requestQueue to verify it's called
|
|
const addRequestSpy = vi.spyOn(requestQueue, 'addRequest');
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// First fetch should trigger transfer from list to queue
|
|
const request1 = await tandem.fetchNextRequest();
|
|
|
|
// Verify the request was transferred from list to queue
|
|
expect(addRequestSpy).toHaveBeenCalled();
|
|
expect(request1).not.toBeNull();
|
|
expect(request1?.url).toBe('https://example.com/1');
|
|
|
|
// Fetch more requests to ensure they all come from the queue
|
|
const request2 = await tandem.fetchNextRequest();
|
|
expect(request2).not.toBeNull();
|
|
expect(request2?.url).toBe('https://example.com/2');
|
|
|
|
const request3 = await tandem.fetchNextRequest();
|
|
expect(request3).not.toBeNull();
|
|
expect(request3?.url).toBe('https://example.com/3');
|
|
|
|
// No more requests should be available
|
|
const request4 = await tandem.fetchNextRequest();
|
|
expect(request4).toBeNull();
|
|
});
|
|
|
|
test('markRequestHandled properly marks request as handled in the queue', async () => {
|
|
const requestList = await RequestList.open(null, [{ url: 'https://example.com/1' }]);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Mock markRequestHandled in requestQueue
|
|
const markHandledSpy = vi.spyOn(requestQueue, 'markRequestHandled');
|
|
|
|
// First fetch a request
|
|
const request = await tandem.fetchNextRequest();
|
|
expect(request).not.toBeNull();
|
|
|
|
// Mark it as handled
|
|
await tandem.markRequestHandled(request!);
|
|
|
|
// Verify the queue's markRequestHandled was called
|
|
expect(markHandledSpy).toHaveBeenCalledWith(request);
|
|
});
|
|
|
|
test('reclaimRequest properly reclaims request in the queue', async () => {
|
|
const requestList = await RequestList.open(null, [{ url: 'https://example.com/1' }]);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Mock reclaimRequest in requestQueue
|
|
const reclaimSpy = vi.spyOn(requestQueue, 'reclaimRequest');
|
|
|
|
// First fetch a request
|
|
const request = await tandem.fetchNextRequest();
|
|
expect(request).not.toBeNull();
|
|
|
|
// Reclaim the request
|
|
await tandem.reclaimRequest(request!);
|
|
|
|
// Verify the queue's reclaimRequest was called
|
|
expect(reclaimSpy).toHaveBeenCalledWith(request, undefined);
|
|
});
|
|
|
|
test('handledCount returns the queue handledCount', async () => {
|
|
const requestList = await RequestList.open(null, [
|
|
{ url: 'https://example.com/1' },
|
|
{ url: 'https://example.com/2' },
|
|
]);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
// Mock handledCount methods to return fixed values
|
|
vi.spyOn(requestList, 'handledCount').mockReturnValue(3);
|
|
vi.spyOn(requestQueue, 'handledCount').mockResolvedValue(2);
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Only the request queue counts
|
|
await expect(tandem.handledCount()).resolves.toBe(2);
|
|
});
|
|
|
|
test('isFinished returns true only when both list and queue are finished', async () => {
|
|
const requestList = await RequestList.open(null, [{ url: 'https://example.com/1' }]);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Mock the isFinished methods
|
|
vi.spyOn(requestList, 'isFinished').mockResolvedValue(false);
|
|
vi.spyOn(requestQueue, 'isFinished').mockResolvedValue(false);
|
|
|
|
// Neither is finished, so tandem should not be finished
|
|
expect(await tandem.isFinished()).toBe(false);
|
|
|
|
// Only list is finished
|
|
vi.spyOn(requestList, 'isFinished').mockResolvedValue(true);
|
|
vi.spyOn(requestQueue, 'isFinished').mockResolvedValue(false);
|
|
expect(await tandem.isFinished()).toBe(false);
|
|
|
|
// Only queue is finished
|
|
vi.spyOn(requestList, 'isFinished').mockResolvedValue(false);
|
|
vi.spyOn(requestQueue, 'isFinished').mockResolvedValue(true);
|
|
expect(await tandem.isFinished()).toBe(false);
|
|
|
|
// Both are finished
|
|
vi.spyOn(requestList, 'isFinished').mockResolvedValue(true);
|
|
vi.spyOn(requestQueue, 'isFinished').mockResolvedValue(true);
|
|
expect(await tandem.isFinished()).toBe(true);
|
|
});
|
|
|
|
test('isEmpty returns true only when both list and queue are empty', async () => {
|
|
const requestList = await RequestList.open(null, [{ url: 'https://example.com/1' }]);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Mock the isEmpty methods
|
|
vi.spyOn(requestList, 'isEmpty').mockResolvedValue(false);
|
|
vi.spyOn(requestQueue, 'isEmpty').mockResolvedValue(false);
|
|
|
|
// Neither is empty, so tandem should not be empty
|
|
expect(await tandem.isEmpty()).toBe(false);
|
|
|
|
// Only list is empty
|
|
vi.spyOn(requestList, 'isEmpty').mockResolvedValue(true);
|
|
vi.spyOn(requestQueue, 'isEmpty').mockResolvedValue(false);
|
|
expect(await tandem.isEmpty()).toBe(false);
|
|
|
|
// Only queue is empty
|
|
vi.spyOn(requestList, 'isEmpty').mockResolvedValue(false);
|
|
vi.spyOn(requestQueue, 'isEmpty').mockResolvedValue(true);
|
|
expect(await tandem.isEmpty()).toBe(false);
|
|
|
|
// Both are empty
|
|
vi.spyOn(requestList, 'isEmpty').mockResolvedValue(true);
|
|
vi.spyOn(requestQueue, 'isEmpty').mockResolvedValue(true);
|
|
expect(await tandem.isEmpty()).toBe(true);
|
|
});
|
|
|
|
test('handles failed batch transfer appropriately', async () => {
|
|
const requestList = await RequestList.open(null, [
|
|
{ url: 'https://example.com/1' },
|
|
{ url: 'https://example.com/2' },
|
|
]);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
// Mock the queue's addRequests to simulate failure
|
|
vi.spyOn(requestQueue, 'addRequest').mockRejectedValue(new Error('Batch add failed'));
|
|
|
|
// Mock the reclaimRequest method to verify it's called
|
|
const reclaimSpy = vi.spyOn(requestList, 'reclaimRequest');
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Attempt to fetch which should trigger the batch transfer
|
|
await tandem.fetchNextRequest();
|
|
|
|
// Verify that reclaimRequest was called to reclaim the failed requests
|
|
expect(reclaimSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
test('added requests are forwarded to the underlying RequestQueue', async () => {
|
|
const requestList = await RequestList.open(null, []);
|
|
const requestQueue = await RequestQueue.open();
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Mock the addRequest method of the queue
|
|
const addRequestSpy = vi.spyOn(requestQueue, 'addRequestsBatched');
|
|
|
|
// Add a new request directly through tandem
|
|
const request = new Request({ url: 'https://example.com/new' });
|
|
await tandem.addRequestsBatched([request]);
|
|
|
|
// Verify the request was forwarded to the queue
|
|
expect(addRequestSpy).toHaveBeenCalledWith([request], undefined);
|
|
});
|
|
|
|
test('async iterator iterates through all requests', async () => {
|
|
const requestList = await RequestList.open(null, [
|
|
{ url: 'https://example.com/1' },
|
|
{ url: 'https://example.com/2' },
|
|
]);
|
|
|
|
const requestQueue = await RequestQueue.open();
|
|
await requestQueue.addRequest({ url: 'https://example.com/3' });
|
|
|
|
const tandem = new RequestManagerTandem(requestList, requestQueue);
|
|
|
|
// Iterate through all requests
|
|
const urls: string[] = [];
|
|
for await (const request of tandem) {
|
|
urls.push(request.url);
|
|
}
|
|
|
|
// Verify we got both URLs
|
|
expect(urls).toEqual(['https://example.com/1', 'https://example.com/2', 'https://example.com/3']);
|
|
});
|
|
});
|