mirror of https://github.com/buster-so/buster.git
184 lines
6.2 KiB
TypeScript
184 lines
6.2 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { CompanyResearchError } from '../../src/deep-research/types.js';
|
|
import { FirecrawlService } from '../../src/services/firecrawl.js';
|
|
|
|
interface MockFirecrawlApp {
|
|
deepResearch: ReturnType<typeof vi.fn>;
|
|
checkDeepResearchStatus: ReturnType<typeof vi.fn>;
|
|
scrapeUrl: ReturnType<typeof vi.fn>;
|
|
}
|
|
|
|
// Mock the FirecrawlApp
|
|
vi.mock('@mendable/firecrawl-js', () => {
|
|
return {
|
|
default: vi.fn().mockImplementation(
|
|
(): MockFirecrawlApp => ({
|
|
deepResearch: vi.fn(),
|
|
checkDeepResearchStatus: vi.fn(),
|
|
scrapeUrl: vi.fn(),
|
|
})
|
|
),
|
|
};
|
|
});
|
|
|
|
describe('FirecrawlService', () => {
|
|
let service: FirecrawlService;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Set API key for tests
|
|
process.env.FIRECRAWL_API_KEY = 'test-api-key';
|
|
});
|
|
|
|
it('should create service with API key from environment', () => {
|
|
service = new FirecrawlService();
|
|
expect(service).toBeInstanceOf(FirecrawlService);
|
|
});
|
|
|
|
it('should create service with API key from config', () => {
|
|
service = new FirecrawlService({ apiKey: 'custom-api-key' });
|
|
expect(service).toBeInstanceOf(FirecrawlService);
|
|
});
|
|
|
|
describe('startDeepResearch', () => {
|
|
beforeEach(() => {
|
|
service = new FirecrawlService();
|
|
});
|
|
|
|
it('should start deep research with default options', async () => {
|
|
const mockResponse = { id: 'job-123' };
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.deepResearch.mockResolvedValue(mockResponse);
|
|
|
|
const jobId = await service.startDeepResearch('test query');
|
|
|
|
expect(jobId).toBe('job-123');
|
|
expect(mockApp.deepResearch).toHaveBeenCalledWith('test query', {});
|
|
});
|
|
|
|
it('should start deep research with custom options', async () => {
|
|
const mockResponse = { data: { id: 'job-456' } };
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.deepResearch.mockResolvedValue(mockResponse);
|
|
|
|
const jobId = await service.startDeepResearch('test query', {
|
|
systemPrompt: 'Custom system prompt',
|
|
analysisPrompt: 'Custom analysis prompt',
|
|
});
|
|
|
|
expect(jobId).toBe('job-456');
|
|
expect(mockApp.deepResearch).toHaveBeenCalledWith('test query', {
|
|
systemPrompt: 'Custom system prompt',
|
|
analysisPrompt: 'Custom analysis prompt',
|
|
});
|
|
});
|
|
|
|
it('should throw error when no job ID is returned', async () => {
|
|
const mockResponse = {};
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.deepResearch.mockResolvedValue(mockResponse);
|
|
|
|
await expect(service.startDeepResearch('test query')).rejects.toThrow(CompanyResearchError);
|
|
});
|
|
|
|
it('should handle API errors', async () => {
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.deepResearch.mockRejectedValue(new Error('API Error'));
|
|
|
|
await expect(service.startDeepResearch('test query')).rejects.toThrow(CompanyResearchError);
|
|
});
|
|
});
|
|
|
|
describe('getJobStatus', () => {
|
|
beforeEach(() => {
|
|
service = new FirecrawlService();
|
|
});
|
|
|
|
it('should get job status', async () => {
|
|
const mockResponse = { status: 'completed', data: { result: 'test' } };
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.checkDeepResearchStatus.mockResolvedValue(mockResponse);
|
|
|
|
const status = await service.getJobStatus('job-123');
|
|
|
|
expect(status).toEqual(mockResponse);
|
|
expect(mockApp.checkDeepResearchStatus).toHaveBeenCalledWith('job-123');
|
|
});
|
|
|
|
it('should handle status check errors', async () => {
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.checkDeepResearchStatus.mockRejectedValue(new Error('Network Error'));
|
|
|
|
await expect(service.getJobStatus('job-123')).rejects.toThrow(CompanyResearchError);
|
|
});
|
|
});
|
|
|
|
describe('scrapeUrl', () => {
|
|
beforeEach(() => {
|
|
service = new FirecrawlService();
|
|
});
|
|
|
|
it('should scrape URL with default options', async () => {
|
|
const mockResponse = { data: 'scraped content' };
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.scrapeUrl.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.scrapeUrl('https://example.com');
|
|
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockApp.scrapeUrl).toHaveBeenCalledWith('https://example.com', {
|
|
formats: ['markdown'],
|
|
onlyMainContent: true,
|
|
});
|
|
});
|
|
|
|
it('should scrape URL with custom options', async () => {
|
|
const mockResponse = { data: 'scraped content' };
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.scrapeUrl.mockResolvedValue(mockResponse);
|
|
|
|
const result = await service.scrapeUrl('https://example.com', {
|
|
formats: ['html', 'markdown'],
|
|
onlyMainContent: false,
|
|
});
|
|
|
|
expect(result).toEqual(mockResponse);
|
|
expect(mockApp.scrapeUrl).toHaveBeenCalledWith('https://example.com', {
|
|
formats: ['html', 'markdown'],
|
|
onlyMainContent: false,
|
|
});
|
|
});
|
|
|
|
it('should handle scraping errors', async () => {
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.scrapeUrl.mockRejectedValue(new Error('Scraping failed'));
|
|
|
|
await expect(service.scrapeUrl('https://example.com')).rejects.toThrow(CompanyResearchError);
|
|
});
|
|
});
|
|
|
|
describe('validateUrl', () => {
|
|
beforeEach(() => {
|
|
service = new FirecrawlService();
|
|
});
|
|
|
|
it('should return true for valid accessible URL', async () => {
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.scrapeUrl.mockResolvedValue({ data: 'content' });
|
|
|
|
const isValid = await service.validateUrl('https://example.com');
|
|
|
|
expect(isValid).toBe(true);
|
|
});
|
|
|
|
it('should return false for inaccessible URL', async () => {
|
|
const mockApp = (service as unknown as { app: MockFirecrawlApp }).app;
|
|
mockApp.scrapeUrl.mockRejectedValue(new Error('Not accessible'));
|
|
|
|
const isValid = await service.validateUrl('https://invalid.com');
|
|
|
|
expect(isValid).toBe(false);
|
|
});
|
|
});
|
|
});
|