290 lines
7.6 KiB
TypeScript
290 lines
7.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
|
|
import type { ExecutionContext, IServiceContainer } from '@stock-bot/types';
|
|
import * as utils from '@stock-bot/utils';
|
|
import { BaseHandler } from '../src/base/BaseHandler';
|
|
|
|
// Mock fetch
|
|
const mockFetch = mock();
|
|
|
|
class TestHandler extends BaseHandler {
|
|
async testGet(url: string, options?: any) {
|
|
return this.http.get(url, options);
|
|
}
|
|
|
|
async testPost(url: string, data?: any, options?: any) {
|
|
return this.http.post(url, data, options);
|
|
}
|
|
|
|
async testPut(url: string, data?: any, options?: any) {
|
|
return this.http.put(url, data, options);
|
|
}
|
|
|
|
async testDelete(url: string, options?: any) {
|
|
return this.http.delete(url, options);
|
|
}
|
|
}
|
|
|
|
describe('BaseHandler HTTP Methods', () => {
|
|
let handler: TestHandler;
|
|
let mockServices: IServiceContainer;
|
|
|
|
beforeEach(() => {
|
|
mockServices = {
|
|
cache: null,
|
|
globalCache: null,
|
|
queueManager: null,
|
|
proxy: null,
|
|
browser: null,
|
|
mongodb: null,
|
|
postgres: null,
|
|
questdb: null,
|
|
logger: {
|
|
info: mock(),
|
|
debug: mock(),
|
|
error: mock(),
|
|
warn: mock(),
|
|
} as any,
|
|
} as IServiceContainer;
|
|
|
|
handler = new TestHandler(mockServices, 'TestHandler');
|
|
|
|
// Mock utils.fetch
|
|
spyOn(utils, 'fetch').mockImplementation(mockFetch);
|
|
mockFetch.mockReset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// spyOn automatically restores
|
|
});
|
|
|
|
describe('GET requests', () => {
|
|
it('should make GET requests with fetch', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
json: async () => ({ data: 'test' }),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await handler.testGet('https://api.example.com/data');
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/data',
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should pass custom options to GET requests', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await handler.testGet('https://api.example.com/data', {
|
|
headers: { Authorization: 'Bearer token' },
|
|
});
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/data',
|
|
expect.objectContaining({
|
|
headers: { Authorization: 'Bearer token' },
|
|
method: 'GET',
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('POST requests', () => {
|
|
it('should make POST requests with JSON data', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
json: async () => ({ success: true }),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const data = { name: 'test', value: 123 };
|
|
await handler.testPost('https://api.example.com/create', data);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/create',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
headers: { 'Content-Type': 'application/json' },
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should merge custom headers in POST requests', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await handler.testPost(
|
|
'https://api.example.com/create',
|
|
{ test: 'data' },
|
|
{
|
|
headers: { 'X-Custom': 'value' },
|
|
}
|
|
);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/create',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify({ test: 'data' }),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Custom': 'value',
|
|
},
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('PUT requests', () => {
|
|
it('should make PUT requests with JSON data', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const data = { id: 1, name: 'updated' };
|
|
await handler.testPut('https://api.example.com/update/1', data);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/update/1',
|
|
expect.objectContaining({
|
|
method: 'PUT',
|
|
body: JSON.stringify(data),
|
|
headers: { 'Content-Type': 'application/json' },
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should handle PUT requests with custom options', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await handler.testPut(
|
|
'https://api.example.com/update',
|
|
{ data: 'test' },
|
|
{
|
|
headers: { 'If-Match': 'etag' },
|
|
timeout: 5000,
|
|
}
|
|
);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/update',
|
|
expect.objectContaining({
|
|
method: 'PUT',
|
|
body: JSON.stringify({ data: 'test' }),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'If-Match': 'etag',
|
|
},
|
|
timeout: 5000,
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('DELETE requests', () => {
|
|
it('should make DELETE requests', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await handler.testDelete('https://api.example.com/delete/1');
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/delete/1',
|
|
expect.objectContaining({
|
|
method: 'DELETE',
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should pass options to DELETE requests', async () => {
|
|
const mockResponse = {
|
|
ok: true,
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers(),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await handler.testDelete('https://api.example.com/delete/1', {
|
|
headers: { Authorization: 'Bearer token' },
|
|
});
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/delete/1',
|
|
expect.objectContaining({
|
|
headers: { Authorization: 'Bearer token' },
|
|
method: 'DELETE',
|
|
logger: expect.any(Object),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Error handling', () => {
|
|
it('should propagate fetch errors', async () => {
|
|
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
|
|
await expect(handler.testGet('https://api.example.com/data')).rejects.toThrow(
|
|
'Network error'
|
|
);
|
|
});
|
|
|
|
it('should handle non-ok responses', async () => {
|
|
const mockResponse = {
|
|
ok: false,
|
|
status: 404,
|
|
statusText: 'Not Found',
|
|
headers: new Headers(),
|
|
};
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const response = await handler.testGet('https://api.example.com/missing');
|
|
|
|
expect(response.ok).toBe(false);
|
|
expect(response.status).toBe(404);
|
|
});
|
|
});
|
|
});
|