Add mock data and unit tests for Channels and Members APIs
This commit is contained in:
213
test/unit/net/http/HttpRequests.test.ts
Normal file
213
test/unit/net/http/HttpRequests.test.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import {describe, expect, it, beforeEach, afterEach, mock} from 'bun:test';
|
||||
import {HttpRequests, HttpRequestError} from '../../../../src/net/http/HttpRequests';
|
||||
import {HttpMethod} from '../../../../src/net/http/HttpMethod';
|
||||
|
||||
describe('HttpRequestError', () => {
|
||||
describe('when created with only a message', () => {
|
||||
it('should set the message and name properties', () => {
|
||||
const error = new HttpRequestError('Test error');
|
||||
|
||||
expect(error.message).toBe('Test error');
|
||||
expect(error.name).toBe('HttpRequestError');
|
||||
});
|
||||
|
||||
it('should leave optional properties undefined', () => {
|
||||
const error = new HttpRequestError('Test error');
|
||||
|
||||
expect(error.status).toBeUndefined();
|
||||
expect(error.statusText).toBeUndefined();
|
||||
expect(error.originalError).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when created with all properties', () => {
|
||||
it('should store all provided values', () => {
|
||||
const originalError = new Error('Original');
|
||||
const error = new HttpRequestError('Test error', 404, 'Not Found', originalError);
|
||||
|
||||
expect(error.message).toBe('Test error');
|
||||
expect(error.status).toBe(404);
|
||||
expect(error.statusText).toBe('Not Found');
|
||||
expect(error.originalError).toBe(originalError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('HttpRequests', () => {
|
||||
let originalFetch: typeof globalThis.fetch;
|
||||
|
||||
const createHttpRequests = (options: {requestTimeoutDuration?: number} = {}) => {
|
||||
const baseHeaders = new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
});
|
||||
return new HttpRequests('https://api.example.com', baseHeaders, options);
|
||||
};
|
||||
|
||||
const mockFetch = (response: Partial<Response>) => {
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: new Headers({'content-type': 'application/json'}),
|
||||
json: async () => ({}),
|
||||
text: async () => '',
|
||||
...response
|
||||
} as Response;
|
||||
|
||||
globalThis.fetch = mock(() => Promise.resolve(mockResponse));
|
||||
return globalThis.fetch;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
originalFetch = globalThis.fetch;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
describe('when making a GET request', () => {
|
||||
it('should return parsed JSON response on success', async () => {
|
||||
const responseData = {status: 'ok', data: 'test'};
|
||||
mockFetch({json: async () => responseData});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
const result = await httpRequests.request<typeof responseData>(HttpMethod.GET, '/test');
|
||||
|
||||
expect(result).toEqual(responseData);
|
||||
});
|
||||
|
||||
it('should strip any body from the request', async () => {
|
||||
const fetchMock = mockFetch({json: async () => ({})});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
await httpRequests.request(HttpMethod.GET, '/test', {body: JSON.stringify({key: 'value'})});
|
||||
|
||||
const [, options] = (fetchMock as any).mock.calls[0];
|
||||
expect(options.body).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should construct full URL from base URI and path', async () => {
|
||||
const fetchMock = mockFetch({json: async () => ({})});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
await httpRequests.request(HttpMethod.GET, '/api/v1/resource');
|
||||
|
||||
const [url] = (fetchMock as any).mock.calls[0];
|
||||
expect(url).toBe('https://api.example.com/api/v1/resource');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making a POST request', () => {
|
||||
it('should include the body in the request', async () => {
|
||||
const fetchMock = mockFetch({json: async () => ({status: 'ok'})});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
const body = {key: 'value'};
|
||||
await httpRequests.request(HttpMethod.POST, '/test', {body: JSON.stringify(body)});
|
||||
|
||||
const [, options] = (fetchMock as any).mock.calls[0];
|
||||
expect(options.body).toBe(JSON.stringify(body));
|
||||
});
|
||||
|
||||
it('should set the correct HTTP method', async () => {
|
||||
const fetchMock = mockFetch({json: async () => ({})});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
await httpRequests.request(HttpMethod.POST, '/test', {body: '{}'});
|
||||
|
||||
const [, options] = (fetchMock as any).mock.calls[0];
|
||||
expect(options.method).toBe('POST');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when making a PUT request with an object body', () => {
|
||||
it('should stringify the body automatically', async () => {
|
||||
const fetchMock = mockFetch({json: async () => ({status: 'ok'})});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
const body = {key: 'value', nested: {foo: 'bar'}};
|
||||
await httpRequests.request(HttpMethod.PUT, '/test', {body: body as any});
|
||||
|
||||
const [, options] = (fetchMock as any).mock.calls[0];
|
||||
expect(options.body).toBe(JSON.stringify(body));
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the response content-type is text/plain', () => {
|
||||
it('should return text response instead of JSON', async () => {
|
||||
mockFetch({
|
||||
headers: new Headers({'content-type': 'text/plain'}),
|
||||
text: async () => 'plain text response'
|
||||
});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
const result = await httpRequests.request<string>(HttpMethod.GET, '/test');
|
||||
|
||||
expect(result).toBe('plain text response');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the server returns a non-ok response', () => {
|
||||
it('should throw HttpRequestError with status details', async () => {
|
||||
mockFetch({
|
||||
ok: false,
|
||||
status: 404,
|
||||
statusText: 'Not Found'
|
||||
});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
|
||||
await expect(httpRequests.request(HttpMethod.GET, '/test')).rejects.toThrow(HttpRequestError);
|
||||
});
|
||||
|
||||
it('should include status code and status text in error', async () => {
|
||||
mockFetch({
|
||||
ok: false,
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error'
|
||||
});
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
|
||||
try {
|
||||
await httpRequests.request(HttpMethod.GET, '/test');
|
||||
expect.unreachable('Should have thrown');
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(HttpRequestError);
|
||||
const error = e as HttpRequestError;
|
||||
expect(error.status).toBe(500);
|
||||
expect(error.statusText).toBe('Internal Server Error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a network error occurs', () => {
|
||||
it('should wrap the error in HttpRequestError', async () => {
|
||||
globalThis.fetch = mock(() => Promise.reject(new Error('Network error')));
|
||||
|
||||
const httpRequests = createHttpRequests();
|
||||
|
||||
await expect(httpRequests.request(HttpMethod.GET, '/test')).rejects.toThrow('Request failed: Network error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the request times out', () => {
|
||||
it('should throw HttpRequestError with timeout message', async () => {
|
||||
const abortError = new Error('Aborted');
|
||||
abortError.name = 'AbortError';
|
||||
globalThis.fetch = mock(() => Promise.reject(abortError));
|
||||
|
||||
const httpRequests = createHttpRequests({requestTimeoutDuration: 100});
|
||||
|
||||
try {
|
||||
await httpRequests.request(HttpMethod.GET, '/test');
|
||||
expect.unreachable('Should have thrown');
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(HttpRequestError);
|
||||
expect((e as HttpRequestError).message).toContain('Request timeout after');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user