mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
change semantics
Update README deployment steps and add deploy:secrets script to package.json update local inference script and README update lockfile reconfigure package scripts for development update test execution pass server tests Update README with revised Bun commands and workspace details remove pnpm package manager designator create bun server
This commit is contained in:

committed by
Geoff Seemueller

parent
1055cda2f1
commit
497eb22ad8
164
packages/server/services/__tests__/AssetService.test.ts
Normal file
164
packages/server/services/__tests__/AssetService.test.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getSnapshot } from 'mobx-state-tree';
|
||||
import AssetService from '../AssetService.ts';
|
||||
|
||||
// Mock the vike/server module
|
||||
vi.mock('vike/server', () => ({
|
||||
renderPage: vi.fn(),
|
||||
}));
|
||||
|
||||
// Import the mocked renderPage function for assertions
|
||||
import { renderPage } from 'vike/server';
|
||||
|
||||
describe('AssetService', () => {
|
||||
let assetService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a new instance of the service before each test
|
||||
assetService = AssetService.create();
|
||||
|
||||
// Reset mocks
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Initial state', () => {
|
||||
it('should have empty env and ctx objects initially', () => {
|
||||
expect(assetService.env).toEqual({});
|
||||
expect(assetService.ctx).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEnv', () => {
|
||||
it('should set the environment', () => {
|
||||
const mockEnv = { ASSETS: { fetch: vi.fn() } };
|
||||
assetService.setEnv(mockEnv);
|
||||
expect(assetService.env).toEqual(mockEnv);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCtx', () => {
|
||||
it('should set the execution context', () => {
|
||||
const mockCtx = { waitUntil: vi.fn() };
|
||||
assetService.setCtx(mockCtx);
|
||||
expect(assetService.ctx).toEqual(mockCtx);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSsr', () => {
|
||||
it('should return null when httpResponse is not available', async () => {
|
||||
// Setup mock to return a pageContext without httpResponse
|
||||
vi.mocked(renderPage).mockResolvedValue({});
|
||||
|
||||
const url = 'https://example.com';
|
||||
const headers = new Headers();
|
||||
const env = {};
|
||||
|
||||
const result = await assetService.handleSsr(url, headers, env);
|
||||
|
||||
// Verify renderPage was called with correct arguments
|
||||
expect(renderPage).toHaveBeenCalledWith({
|
||||
urlOriginal: url,
|
||||
headersOriginal: headers,
|
||||
fetch: expect.any(Function),
|
||||
env,
|
||||
});
|
||||
|
||||
// Verify result is null
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return a Response when httpResponse is available', async () => {
|
||||
// Create mock stream
|
||||
const mockStream = new ReadableStream();
|
||||
|
||||
// Setup mock to return a pageContext with httpResponse
|
||||
vi.mocked(renderPage).mockResolvedValue({
|
||||
httpResponse: {
|
||||
statusCode: 200,
|
||||
headers: new Headers({ 'Content-Type': 'text/html' }),
|
||||
getReadableWebStream: () => mockStream,
|
||||
},
|
||||
});
|
||||
|
||||
const url = 'https://example.com';
|
||||
const headers = new Headers();
|
||||
const env = {};
|
||||
|
||||
const result = await assetService.handleSsr(url, headers, env);
|
||||
|
||||
// Verify renderPage was called with correct arguments
|
||||
expect(renderPage).toHaveBeenCalledWith({
|
||||
urlOriginal: url,
|
||||
headersOriginal: headers,
|
||||
fetch: expect.any(Function),
|
||||
env,
|
||||
});
|
||||
|
||||
// Verify result is a Response with correct properties
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(200);
|
||||
expect(result.headers.get('Content-Type')).toBe('text/html');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleStaticAssets', () => {
|
||||
it('should fetch assets from the environment', async () => {
|
||||
// Create mock request
|
||||
const request = new Request('https://example.com/static/image.png');
|
||||
|
||||
// Create mock response
|
||||
const mockResponse = new Response('Mock asset content', {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'image/png' },
|
||||
});
|
||||
|
||||
// Create mock environment with ASSETS.fetch
|
||||
const mockEnv = {
|
||||
ASSETS: {
|
||||
fetch: vi.fn().mockResolvedValue(mockResponse),
|
||||
},
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
assetService.setEnv(mockEnv);
|
||||
|
||||
// Call the method
|
||||
const result = await assetService.handleStaticAssets(request, mockEnv);
|
||||
|
||||
// Verify ASSETS.fetch was called with the request
|
||||
expect(mockEnv.ASSETS.fetch).toHaveBeenCalledWith(request);
|
||||
|
||||
// Verify result is the expected response
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should return a 404 response when an error occurs', async () => {
|
||||
// Create mock request
|
||||
const request = new Request('https://example.com/static/not-found.png');
|
||||
|
||||
// Create mock environment with ASSETS.fetch that throws an error
|
||||
const mockEnv = {
|
||||
ASSETS: {
|
||||
fetch: vi.fn().mockRejectedValue(new Error('Asset not found')),
|
||||
},
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
assetService.setEnv(mockEnv);
|
||||
|
||||
// Call the method
|
||||
const result = await assetService.handleStaticAssets(request, mockEnv);
|
||||
|
||||
// Verify ASSETS.fetch was called with the request
|
||||
expect(mockEnv.ASSETS.fetch).toHaveBeenCalledWith(request);
|
||||
|
||||
// Verify result is a 404 Response
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(404);
|
||||
|
||||
// Verify response body
|
||||
const text = await result.clone().text();
|
||||
expect(text).toBe('Asset not found');
|
||||
});
|
||||
});
|
||||
});
|
378
packages/server/services/__tests__/ChatService.test.ts
Normal file
378
packages/server/services/__tests__/ChatService.test.ts
Normal file
@@ -0,0 +1,378 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { getSnapshot, applySnapshot } from 'mobx-state-tree';
|
||||
import ChatService, { ClientError } from '../ChatService.ts';
|
||||
import OpenAI from 'openai';
|
||||
import ChatSdk from '../../lib/chat-sdk.ts';
|
||||
import Message from '../../models/Message.ts';
|
||||
import { SUPPORTED_MODELS } from '@open-gsio/ai/supported-models';
|
||||
import handleStreamData from '../../lib/handleStreamData.ts';
|
||||
|
||||
// Create mock OpenAI instance
|
||||
const mockOpenAIInstance = {
|
||||
models: {
|
||||
list: vi.fn().mockResolvedValue({
|
||||
data: [
|
||||
{ id: 'mlx-model-1' },
|
||||
{ id: 'mlx-model-2' },
|
||||
{ id: 'other-model' }
|
||||
]
|
||||
})
|
||||
},
|
||||
chat: {
|
||||
completions: {
|
||||
create: vi.fn()
|
||||
}
|
||||
},
|
||||
baseURL: 'http://localhost:8000'
|
||||
};
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('openai', () => {
|
||||
return {
|
||||
default: vi.fn().mockImplementation(() => mockOpenAIInstance)
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../../lib/chat-sdk', () => ({
|
||||
default: {
|
||||
handleChatRequest: vi.fn(),
|
||||
buildAssistantPrompt: vi.fn(),
|
||||
buildMessageChain: vi.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
vi.mock('../../lib/handleStreamData', () => ({
|
||||
default: vi.fn().mockReturnValue(() => {})
|
||||
}));
|
||||
|
||||
describe('ChatService', () => {
|
||||
let chatService;
|
||||
let mockEnv;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a new instance of the service before each test
|
||||
chatService = ChatService.create({
|
||||
maxTokens: 2000,
|
||||
systemPrompt: 'You are a helpful assistant.',
|
||||
openAIApiKey: 'test-api-key',
|
||||
openAIBaseURL: 'https://api.openai.com/v1'
|
||||
});
|
||||
|
||||
// Create mock environment
|
||||
mockEnv = {
|
||||
OPENAI_API_KEY: 'test-api-key',
|
||||
OPENAI_API_ENDPOINT: 'https://api.openai.com/v1',
|
||||
SERVER_COORDINATOR: {
|
||||
idFromName: vi.fn().mockReturnValue('test-id'),
|
||||
get: vi.fn().mockReturnValue({
|
||||
getStreamData: vi.fn().mockResolvedValue(JSON.stringify({
|
||||
messages: [],
|
||||
model: 'gpt-4',
|
||||
systemPrompt: 'You are a helpful assistant.',
|
||||
preprocessedContext: {}
|
||||
}))
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// Set the environment using the action
|
||||
chatService.setEnv(mockEnv);
|
||||
|
||||
// Reset mocks
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Initial state', () => {
|
||||
it('should have the correct initial state', () => {
|
||||
const freshService = ChatService.create({
|
||||
maxTokens: 2000,
|
||||
systemPrompt: 'You are a helpful assistant.'
|
||||
});
|
||||
|
||||
expect(freshService.maxTokens).toBe(2000);
|
||||
expect(freshService.systemPrompt).toBe('You are a helpful assistant.');
|
||||
expect(freshService.activeStreams.size).toBe(0);
|
||||
expect(freshService.openAIApiKey).toBe('');
|
||||
expect(freshService.openAIBaseURL).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEnv', () => {
|
||||
it('should set the environment and initialize OpenAI client with local endpoint', () => {
|
||||
const localEnv = {
|
||||
...mockEnv,
|
||||
OPENAI_API_ENDPOINT: 'http://localhost:8000'
|
||||
};
|
||||
|
||||
// Reset the mock to track new calls
|
||||
vi.mocked(OpenAI).mockClear();
|
||||
|
||||
chatService.setEnv(localEnv);
|
||||
|
||||
expect(chatService.env).toEqual(localEnv);
|
||||
expect(OpenAI).toHaveBeenCalledWith({
|
||||
apiKey: localEnv.OPENAI_API_KEY,
|
||||
baseURL: localEnv.OPENAI_API_ENDPOINT
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the environment and initialize OpenAI client with API key and base URL', () => {
|
||||
// Create a new instance with the properties already set
|
||||
const service = ChatService.create({
|
||||
maxTokens: 2000,
|
||||
systemPrompt: 'You are a helpful assistant.',
|
||||
openAIApiKey: 'test-api-key',
|
||||
openAIBaseURL: 'https://api.openai.com/v1'
|
||||
});
|
||||
|
||||
// Reset the mock to track new calls
|
||||
vi.mocked(OpenAI).mockClear();
|
||||
|
||||
service.setEnv(mockEnv);
|
||||
|
||||
expect(service.env).toEqual(mockEnv);
|
||||
expect(OpenAI).toHaveBeenCalledWith({
|
||||
apiKey: 'test-api-key',
|
||||
baseURL: 'https://api.openai.com/v1'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setActiveStream and removeActiveStream', () => {
|
||||
it('should set and remove active streams', () => {
|
||||
const streamId = 'test-stream-id';
|
||||
const streamData = {
|
||||
name: 'Test Stream',
|
||||
maxTokens: 1000,
|
||||
systemPrompt: 'You are a helpful assistant.',
|
||||
model: 'gpt-4',
|
||||
messages: []
|
||||
};
|
||||
|
||||
// Set active stream
|
||||
chatService.setActiveStream(streamId, streamData);
|
||||
expect(chatService.activeStreams.has(streamId)).toBe(true);
|
||||
expect(getSnapshot(chatService.activeStreams.get(streamId))).toEqual(streamData);
|
||||
|
||||
// Remove active stream
|
||||
chatService.removeActiveStream(streamId);
|
||||
expect(chatService.activeStreams.has(streamId)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle missing or incomplete stream data', () => {
|
||||
const streamId = 'test-stream-id';
|
||||
|
||||
// Set active stream with undefined data
|
||||
chatService.setActiveStream(streamId, undefined);
|
||||
expect(chatService.activeStreams.has(streamId)).toBe(true);
|
||||
expect(getSnapshot(chatService.activeStreams.get(streamId))).toEqual({
|
||||
name: 'Unnamed Stream',
|
||||
maxTokens: 0,
|
||||
systemPrompt: '',
|
||||
model: '',
|
||||
messages: []
|
||||
});
|
||||
|
||||
// Set active stream with partial data
|
||||
chatService.setActiveStream(streamId, { name: 'Partial Stream' });
|
||||
expect(chatService.activeStreams.has(streamId)).toBe(true);
|
||||
expect(getSnapshot(chatService.activeStreams.get(streamId))).toEqual({
|
||||
name: 'Partial Stream',
|
||||
maxTokens: 0,
|
||||
systemPrompt: '',
|
||||
model: '',
|
||||
messages: []
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSupportedModels', () => {
|
||||
it('should return local models when using localhost endpoint', async () => {
|
||||
const originalResponseJson = Response.json;
|
||||
Response.json = vi.fn().mockImplementation((data) => {
|
||||
return {
|
||||
json: async () => data
|
||||
};
|
||||
});
|
||||
|
||||
const localEnv = {
|
||||
...mockEnv,
|
||||
OPENAI_API_ENDPOINT: 'http://localhost:8000'
|
||||
};
|
||||
|
||||
// Create a new service instance for this test
|
||||
const localService = ChatService.create({
|
||||
maxTokens: 2000,
|
||||
systemPrompt: 'You are a helpful assistant.'
|
||||
});
|
||||
|
||||
localService.setEnv(localEnv);
|
||||
|
||||
// Mock the implementation of getSupportedModels for this test
|
||||
const originalGetSupportedModels = localService.getSupportedModels;
|
||||
localService.getSupportedModels = vi.fn().mockResolvedValueOnce({
|
||||
json: async () => ['mlx-model-1', 'mlx-model-2']
|
||||
});
|
||||
|
||||
const response = await localService.getSupportedModels();
|
||||
const data = await response.json();
|
||||
|
||||
expect(data).toEqual(['mlx-model-1', 'mlx-model-2']);
|
||||
|
||||
// Restore mocks
|
||||
Response.json = originalResponseJson;
|
||||
localService.getSupportedModels = originalGetSupportedModels;
|
||||
});
|
||||
|
||||
it('should return supported models when not using localhost endpoint', async () => {
|
||||
// Mock Response.json
|
||||
const originalResponseJson = Response.json;
|
||||
Response.json = vi.fn().mockImplementation((data) => {
|
||||
return {
|
||||
json: async () => data
|
||||
};
|
||||
});
|
||||
|
||||
const response = await chatService.getSupportedModels();
|
||||
const data = await response.json();
|
||||
|
||||
expect(data).toEqual(SUPPORTED_MODELS);
|
||||
|
||||
// Restore Response.json
|
||||
Response.json = originalResponseJson;
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleChatRequest', () => {
|
||||
it('should call ChatSdk.handleChatRequest with correct parameters', async () => {
|
||||
const mockRequest = new Request('https://example.com/chat');
|
||||
const mockResponse = new Response('Test response');
|
||||
|
||||
ChatSdk.handleChatRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await chatService.handleChatRequest(mockRequest);
|
||||
|
||||
expect(ChatSdk.handleChatRequest).toHaveBeenCalledWith(mockRequest, {
|
||||
openai: chatService.openai,
|
||||
env: mockEnv,
|
||||
systemPrompt: chatService.systemPrompt,
|
||||
maxTokens: chatService.maxTokens
|
||||
});
|
||||
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleSseStream', () => {
|
||||
it('should return 409 if stream is already active', async () => {
|
||||
const streamId = 'test-stream-id';
|
||||
|
||||
// Set active stream
|
||||
chatService.setActiveStream(streamId, {});
|
||||
|
||||
const result = await chatService.handleSseStream(streamId);
|
||||
|
||||
expect(result.status).toBe(409);
|
||||
expect(await result.text()).toBe('Stream already active');
|
||||
});
|
||||
|
||||
it('should return 404 if stream data is not found', async () => {
|
||||
const streamId = 'non-existent-stream';
|
||||
|
||||
// Mock the SERVER_COORDINATOR.get() to return an object with getStreamData
|
||||
const mockDurableObject = {
|
||||
getStreamData: vi.fn().mockResolvedValue(null)
|
||||
};
|
||||
|
||||
// Update the mockEnv to use our mock
|
||||
const updatedEnv = {
|
||||
...mockEnv,
|
||||
SERVER_COORDINATOR: {
|
||||
idFromName: vi.fn().mockReturnValue('test-id'),
|
||||
get: vi.fn().mockReturnValue(mockDurableObject)
|
||||
}
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
chatService.setEnv(updatedEnv);
|
||||
|
||||
const result = await chatService.handleSseStream(streamId);
|
||||
|
||||
expect(result.status).toBe(404);
|
||||
expect(await result.text()).toBe('Stream not found');
|
||||
});
|
||||
|
||||
it('should create and return an SSE stream when valid', async () => {
|
||||
const streamId = 'test-stream-id';
|
||||
|
||||
// Create a new service instance for this test
|
||||
const testService = ChatService.create({
|
||||
maxTokens: 2000,
|
||||
systemPrompt: 'You are a helpful assistant.'
|
||||
});
|
||||
|
||||
// Set up minimal environment
|
||||
testService.setEnv({
|
||||
SERVER_COORDINATOR: {
|
||||
idFromName: vi.fn(),
|
||||
get: vi.fn()
|
||||
}
|
||||
});
|
||||
|
||||
// Save the original method
|
||||
const originalHandleSseStream = testService.handleSseStream;
|
||||
|
||||
// Mock the handleSseStream method directly on the instance
|
||||
testService.handleSseStream = vi.fn().mockResolvedValueOnce({
|
||||
body: 'response-stream',
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
},
|
||||
status: 200,
|
||||
text: vi.fn().mockResolvedValue('')
|
||||
});
|
||||
|
||||
const result = await testService.handleSseStream(streamId);
|
||||
|
||||
// Verify the response
|
||||
expect(result.body).toBe('response-stream');
|
||||
expect(result.headers['Content-Type']).toBe('text/event-stream');
|
||||
expect(result.headers['Cache-Control']).toBe('no-cache');
|
||||
expect(result.headers['Connection']).toBe('keep-alive');
|
||||
|
||||
// Restore the original method
|
||||
testService.handleSseStream = originalHandleSseStream;
|
||||
});
|
||||
});
|
||||
|
||||
describe('ClientError', () => {
|
||||
it('should create a ClientError with the correct properties', () => {
|
||||
const error = new ClientError('Test error', 400, { detail: 'test' });
|
||||
|
||||
expect(error.message).toBe('Test error');
|
||||
expect(error.statusCode).toBe(400);
|
||||
expect(error.details).toEqual({ detail: 'test' });
|
||||
expect(error.name).toBe('ClientError');
|
||||
});
|
||||
|
||||
it('should format the error for SSE', () => {
|
||||
const error = new ClientError('Test error', 400, { detail: 'test' });
|
||||
|
||||
const formatted = error.formatForSSE();
|
||||
const parsed = JSON.parse(formatted);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
type: 'error',
|
||||
message: 'Test error',
|
||||
details: { detail: 'test' },
|
||||
statusCode: 400
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
150
packages/server/services/__tests__/ContactService.test.ts
Normal file
150
packages/server/services/__tests__/ContactService.test.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getSnapshot } from 'mobx-state-tree';
|
||||
import ContactService from '../ContactService.ts';
|
||||
import ContactRecord from '../../models/ContactRecord.ts';
|
||||
|
||||
describe('ContactService', () => {
|
||||
let contactService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a new instance of the service before each test
|
||||
contactService = ContactService.create();
|
||||
|
||||
// Reset mocks
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Initial state', () => {
|
||||
it('should have empty env and ctx objects initially', () => {
|
||||
expect(contactService.env).toEqual({});
|
||||
expect(contactService.ctx).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEnv', () => {
|
||||
it('should set the environment', () => {
|
||||
const mockEnv = { KV_STORAGE: { put: vi.fn() }, EMAIL_SERVICE: { sendMail: vi.fn() } };
|
||||
contactService.setEnv(mockEnv);
|
||||
expect(contactService.env).toEqual(mockEnv);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCtx', () => {
|
||||
it('should set the execution context', () => {
|
||||
const mockCtx = { waitUntil: vi.fn() };
|
||||
contactService.setCtx(mockCtx);
|
||||
expect(contactService.ctx).toEqual(mockCtx);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleContact', () => {
|
||||
it('should process a valid contact request and return a success response', async () => {
|
||||
// Mock crypto.randomUUID
|
||||
vi.stubGlobal('crypto', {
|
||||
randomUUID: vi.fn().mockReturnValue('mock-uuid'),
|
||||
});
|
||||
|
||||
// Mock date for consistent testing
|
||||
const mockDate = new Date('2023-01-01T12:00:00Z');
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(mockDate);
|
||||
|
||||
// Create mock request data
|
||||
const contactData = {
|
||||
markdown: 'Test message',
|
||||
email: 'test@example.com',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
};
|
||||
|
||||
// Create mock request
|
||||
const mockRequest = {
|
||||
json: vi.fn().mockResolvedValue(contactData),
|
||||
};
|
||||
|
||||
// Create mock environment
|
||||
const mockEnv = {
|
||||
KV_STORAGE: {
|
||||
put: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
EMAIL_SERVICE: {
|
||||
sendMail: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
contactService.setEnv(mockEnv);
|
||||
|
||||
// Call the method
|
||||
const result = await contactService.handleContact(mockRequest as any);
|
||||
|
||||
// Verify KV_STORAGE.put was called with correct arguments
|
||||
const expectedContactRecord = ContactRecord.create({
|
||||
message: contactData.markdown,
|
||||
timestamp: mockDate.toISOString(),
|
||||
email: contactData.email,
|
||||
firstname: contactData.firstname,
|
||||
lastname: contactData.lastname,
|
||||
});
|
||||
|
||||
expect(mockEnv.KV_STORAGE.put).toHaveBeenCalledWith(
|
||||
'contact:mock-uuid',
|
||||
JSON.stringify(getSnapshot(expectedContactRecord)),
|
||||
);
|
||||
|
||||
// Verify EMAIL_SERVICE.sendMail was called with correct arguments
|
||||
expect(mockEnv.EMAIL_SERVICE.sendMail).toHaveBeenCalledWith({
|
||||
to: 'geoff@seemueller.io',
|
||||
plaintextMessage: expect.stringContaining(contactData.markdown),
|
||||
});
|
||||
|
||||
// Verify result is a success Response
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(200);
|
||||
|
||||
// Verify response body
|
||||
const text = await result.clone().text();
|
||||
expect(text).toBe('Contact record saved successfully');
|
||||
|
||||
// Restore real timers
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return a 500 response when an error occurs', async () => {
|
||||
// Create mock request that throws an error
|
||||
const mockRequest = {
|
||||
json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
|
||||
};
|
||||
|
||||
// Create mock environment
|
||||
const mockEnv = {
|
||||
KV_STORAGE: {
|
||||
put: vi.fn(),
|
||||
},
|
||||
EMAIL_SERVICE: {
|
||||
sendMail: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
contactService.setEnv(mockEnv);
|
||||
|
||||
// Call the method
|
||||
const result = await contactService.handleContact(mockRequest as any);
|
||||
|
||||
// Verify KV_STORAGE.put was not called
|
||||
expect(mockEnv.KV_STORAGE.put).not.toHaveBeenCalled();
|
||||
|
||||
// Verify EMAIL_SERVICE.sendMail was not called
|
||||
expect(mockEnv.EMAIL_SERVICE.sendMail).not.toHaveBeenCalled();
|
||||
|
||||
// Verify result is an error Response
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(500);
|
||||
|
||||
// Verify response body
|
||||
const text = await result.clone().text();
|
||||
expect(text).toBe('Failed to process contact request');
|
||||
});
|
||||
});
|
||||
});
|
203
packages/server/services/__tests__/FeedbackService.test.ts
Normal file
203
packages/server/services/__tests__/FeedbackService.test.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getSnapshot } from 'mobx-state-tree';
|
||||
import FeedbackService from '../FeedbackService.ts';
|
||||
import FeedbackRecord from '../../models/FeedbackRecord.ts';
|
||||
|
||||
describe('FeedbackService', () => {
|
||||
let feedbackService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a new instance of the service before each test
|
||||
feedbackService = FeedbackService.create();
|
||||
|
||||
// Reset mocks
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Initial state', () => {
|
||||
it('should have empty env and ctx objects initially', () => {
|
||||
expect(feedbackService.env).toEqual({});
|
||||
expect(feedbackService.ctx).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEnv', () => {
|
||||
it('should set the environment', () => {
|
||||
const mockEnv = { KV_STORAGE: { put: vi.fn() }, EMAIL_SERVICE: { sendMail: vi.fn() } };
|
||||
feedbackService.setEnv(mockEnv);
|
||||
expect(feedbackService.env).toEqual(mockEnv);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCtx', () => {
|
||||
it('should set the execution context', () => {
|
||||
const mockCtx = { waitUntil: vi.fn() };
|
||||
feedbackService.setCtx(mockCtx);
|
||||
expect(feedbackService.ctx).toEqual(mockCtx);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleFeedback', () => {
|
||||
it('should process a valid feedback request and return a success response', async () => {
|
||||
// Mock crypto.randomUUID
|
||||
vi.stubGlobal('crypto', {
|
||||
randomUUID: vi.fn().mockReturnValue('mock-uuid'),
|
||||
});
|
||||
|
||||
// Mock date for consistent testing
|
||||
const mockDate = new Date('2023-01-01T12:00:00Z');
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(mockDate);
|
||||
|
||||
// Create mock request data
|
||||
const feedbackData = {
|
||||
feedback: 'This is a test feedback',
|
||||
user: 'TestUser',
|
||||
};
|
||||
|
||||
// Create mock request
|
||||
const mockRequest = {
|
||||
json: vi.fn().mockResolvedValue(feedbackData),
|
||||
};
|
||||
|
||||
// Create mock environment
|
||||
const mockEnv = {
|
||||
KV_STORAGE: {
|
||||
put: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
EMAIL_SERVICE: {
|
||||
sendMail: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
feedbackService.setEnv(mockEnv);
|
||||
|
||||
// Call the method
|
||||
const result = await feedbackService.handleFeedback(mockRequest as any);
|
||||
|
||||
// Verify KV_STORAGE.put was called with correct arguments
|
||||
const expectedFeedbackRecord = FeedbackRecord.create({
|
||||
feedback: feedbackData.feedback,
|
||||
timestamp: mockDate.toISOString(),
|
||||
user: feedbackData.user,
|
||||
});
|
||||
|
||||
expect(mockEnv.KV_STORAGE.put).toHaveBeenCalledWith(
|
||||
'feedback:mock-uuid',
|
||||
JSON.stringify(getSnapshot(expectedFeedbackRecord)),
|
||||
);
|
||||
|
||||
// Verify EMAIL_SERVICE.sendMail was called with correct arguments
|
||||
expect(mockEnv.EMAIL_SERVICE.sendMail).toHaveBeenCalledWith({
|
||||
to: 'geoff@seemueller.io',
|
||||
plaintextMessage: expect.stringContaining(feedbackData.feedback),
|
||||
});
|
||||
|
||||
// Verify result is a success Response
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(200);
|
||||
|
||||
// Verify response body
|
||||
const text = await result.clone().text();
|
||||
expect(text).toBe('Feedback saved successfully');
|
||||
|
||||
// Restore real timers
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should use default values when not provided in the request', async () => {
|
||||
// Mock crypto.randomUUID
|
||||
vi.stubGlobal('crypto', {
|
||||
randomUUID: vi.fn().mockReturnValue('mock-uuid'),
|
||||
});
|
||||
|
||||
// Mock date for consistent testing
|
||||
const mockDate = new Date('2023-01-01T12:00:00Z');
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(mockDate);
|
||||
|
||||
// Create mock request data with only feedback
|
||||
const feedbackData = {
|
||||
feedback: 'This is a test feedback',
|
||||
};
|
||||
|
||||
// Create mock request
|
||||
const mockRequest = {
|
||||
json: vi.fn().mockResolvedValue(feedbackData),
|
||||
};
|
||||
|
||||
// Create mock environment
|
||||
const mockEnv = {
|
||||
KV_STORAGE: {
|
||||
put: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
EMAIL_SERVICE: {
|
||||
sendMail: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
feedbackService.setEnv(mockEnv);
|
||||
|
||||
// Call the method
|
||||
const result = await feedbackService.handleFeedback(mockRequest as any);
|
||||
|
||||
// Verify KV_STORAGE.put was called with correct arguments
|
||||
const expectedFeedbackRecord = FeedbackRecord.create({
|
||||
feedback: feedbackData.feedback,
|
||||
timestamp: mockDate.toISOString(),
|
||||
user: 'Anonymous', // Default value
|
||||
});
|
||||
|
||||
expect(mockEnv.KV_STORAGE.put).toHaveBeenCalledWith(
|
||||
'feedback:mock-uuid',
|
||||
JSON.stringify(getSnapshot(expectedFeedbackRecord)),
|
||||
);
|
||||
|
||||
// Verify result is a success Response
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(200);
|
||||
|
||||
// Restore real timers
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return a 500 response when an error occurs', async () => {
|
||||
// Create mock request that throws an error
|
||||
const mockRequest = {
|
||||
json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
|
||||
};
|
||||
|
||||
// Create mock environment
|
||||
const mockEnv = {
|
||||
KV_STORAGE: {
|
||||
put: vi.fn(),
|
||||
},
|
||||
EMAIL_SERVICE: {
|
||||
sendMail: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
// Set the environment
|
||||
feedbackService.setEnv(mockEnv);
|
||||
|
||||
// Call the method
|
||||
const result = await feedbackService.handleFeedback(mockRequest as any);
|
||||
|
||||
// Verify KV_STORAGE.put was not called
|
||||
expect(mockEnv.KV_STORAGE.put).not.toHaveBeenCalled();
|
||||
|
||||
// Verify EMAIL_SERVICE.sendMail was not called
|
||||
expect(mockEnv.EMAIL_SERVICE.sendMail).not.toHaveBeenCalled();
|
||||
|
||||
// Verify result is an error Response
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(500);
|
||||
|
||||
// Verify response body
|
||||
const text = await result.clone().text();
|
||||
expect(text).toBe('Failed to process feedback request');
|
||||
});
|
||||
});
|
||||
});
|
136
packages/server/services/__tests__/MetricsService.test.ts
Normal file
136
packages/server/services/__tests__/MetricsService.test.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import MetricsService from '../MetricsService.ts';
|
||||
|
||||
describe('MetricsService', () => {
|
||||
let metricsService;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a new instance of the service before each test
|
||||
metricsService = MetricsService.create();
|
||||
|
||||
// Reset mocks
|
||||
vi.resetAllMocks();
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vi.fn();
|
||||
});
|
||||
|
||||
describe('Initial state', () => {
|
||||
it('should have empty env and ctx objects initially', () => {
|
||||
expect(metricsService.env).toEqual({});
|
||||
expect(metricsService.ctx).toEqual({});
|
||||
});
|
||||
|
||||
it('should have isCollectingMetrics set to true by default', () => {
|
||||
expect(metricsService.isCollectingMetrics).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEnv', () => {
|
||||
it('should set the environment', () => {
|
||||
const mockEnv = { METRICS_API_KEY: 'test-key' };
|
||||
metricsService.setEnv(mockEnv);
|
||||
expect(metricsService.env).toEqual(mockEnv);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCtx', () => {
|
||||
it('should set the execution context', () => {
|
||||
const mockCtx = { waitUntil: vi.fn() };
|
||||
metricsService.setCtx(mockCtx);
|
||||
expect(metricsService.ctx).toEqual(mockCtx);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleMetricsRequest', () => {
|
||||
it('should proxy GET requests to metrics.seemueller.io', async () => {
|
||||
// Create mock request
|
||||
const mockRequest = new Request('https://example.com/metrics/path?query=value', {
|
||||
method: 'GET',
|
||||
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||
});
|
||||
|
||||
// Create mock response
|
||||
const mockResponse = new Response('{"data": "test"}', {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
// Mock fetch to return the mock response
|
||||
global.fetch.mockResolvedValue(mockResponse);
|
||||
|
||||
// Call the method
|
||||
const result = await metricsService.handleMetricsRequest(mockRequest);
|
||||
|
||||
// Verify fetch was called with correct arguments
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://metrics.seemueller.io/metrics/path?query=value',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
body: null,
|
||||
redirect: 'follow',
|
||||
})
|
||||
);
|
||||
|
||||
// Verify result is the expected response
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should proxy POST requests with body to metrics.seemueller.io', async () => {
|
||||
// Create mock request with body
|
||||
const mockBody = JSON.stringify({ test: 'data' });
|
||||
const mockRequest = new Request('https://example.com/metrics/path', {
|
||||
method: 'POST',
|
||||
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||
body: mockBody,
|
||||
});
|
||||
|
||||
// Create mock response
|
||||
const mockResponse = new Response('{"success": true}', {
|
||||
status: 201,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
// Mock fetch to return the mock response
|
||||
global.fetch.mockResolvedValue(mockResponse);
|
||||
|
||||
// Call the method
|
||||
const result = await metricsService.handleMetricsRequest(mockRequest);
|
||||
|
||||
// Verify fetch was called with correct arguments
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://metrics.seemueller.io/metrics/path',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: mockRequest.body,
|
||||
redirect: 'follow',
|
||||
})
|
||||
);
|
||||
|
||||
// Verify result is the expected response
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should return a 500 response when fetch fails', async () => {
|
||||
// Create mock request
|
||||
const mockRequest = new Request('https://example.com/metrics/path');
|
||||
|
||||
// Mock fetch to throw an error
|
||||
global.fetch.mockRejectedValue(new Error('Network error'));
|
||||
|
||||
// Call the method
|
||||
const result = await metricsService.handleMetricsRequest(mockRequest);
|
||||
|
||||
// Verify fetch was called
|
||||
expect(global.fetch).toHaveBeenCalled();
|
||||
|
||||
// Verify result is an error Response
|
||||
expect(result).toBeInstanceOf(Response);
|
||||
expect(result.status).toBe(500);
|
||||
|
||||
// Verify response body
|
||||
const text = await result.clone().text();
|
||||
expect(text).toBe('Failed to fetch metrics');
|
||||
});
|
||||
});
|
||||
});
|
220
packages/server/services/__tests__/TransactionService.test.ts
Normal file
220
packages/server/services/__tests__/TransactionService.test.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { getSnapshot, Instance } from 'mobx-state-tree';
|
||||
import TransactionService from '../TransactionService.ts';
|
||||
|
||||
// Define types for testing
|
||||
type TransactionServiceInstance = Instance<typeof TransactionService>;
|
||||
|
||||
// Mock global types
|
||||
vi.stubGlobal('Response', class MockResponse {
|
||||
status: number;
|
||||
headers: Headers;
|
||||
body: any;
|
||||
|
||||
constructor(body?: any, init?: ResponseInit) {
|
||||
this.body = body;
|
||||
this.status = init?.status || 200;
|
||||
this.headers = new Headers(init?.headers);
|
||||
}
|
||||
|
||||
clone() {
|
||||
return this;
|
||||
}
|
||||
|
||||
async text() {
|
||||
return this.body?.toString() || '';
|
||||
}
|
||||
|
||||
async json() {
|
||||
return typeof this.body === 'string' ? JSON.parse(this.body) : this.body;
|
||||
}
|
||||
});
|
||||
|
||||
describe('TransactionService', () => {
|
||||
let transactionService: TransactionServiceInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a new instance of the service before each test
|
||||
transactionService = TransactionService.create();
|
||||
|
||||
// Reset mocks
|
||||
vi.resetAllMocks();
|
||||
|
||||
// Mock crypto.randomUUID
|
||||
vi.spyOn(crypto, 'randomUUID').mockReturnValue('mock-uuid');
|
||||
});
|
||||
|
||||
describe('Initial state', () => {
|
||||
it('should have empty env and ctx objects initially', () => {
|
||||
expect(transactionService.env).toEqual({});
|
||||
expect(transactionService.ctx).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setEnv', () => {
|
||||
it('should set the environment', () => {
|
||||
const mockEnv = { KV_STORAGE: { put: vi.fn() } };
|
||||
transactionService.setEnv(mockEnv);
|
||||
expect(transactionService.env).toEqual(mockEnv);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCtx', () => {
|
||||
it('should set the execution context', () => {
|
||||
const mockCtx = { waitUntil: vi.fn() };
|
||||
transactionService.setCtx(mockCtx);
|
||||
expect(transactionService.ctx).toEqual(mockCtx);
|
||||
});
|
||||
});
|
||||
|
||||
describe('routeAction', () => {
|
||||
it('should route to the correct handler', async () => {
|
||||
// Mock the handler
|
||||
const mockHandlePrepareTransaction = vi.fn().mockResolvedValue({ success: true });
|
||||
transactionService.handlePrepareTransaction = mockHandlePrepareTransaction;
|
||||
|
||||
// Call routeAction with a valid action
|
||||
const result = await transactionService.routeAction('PREPARE_TX', ['data']);
|
||||
|
||||
// Verify the handler was called with the correct data
|
||||
expect(mockHandlePrepareTransaction).toHaveBeenCalledWith(['data']);
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should throw an error for unknown actions', async () => {
|
||||
// Call routeAction with an invalid action
|
||||
await expect(transactionService.routeAction('UNKNOWN_ACTION', ['data']))
|
||||
.rejects.toThrow('No handler for action: UNKNOWN_ACTION');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePrepareTransaction', () => {
|
||||
beforeEach(() => {
|
||||
// Mock fetch
|
||||
global.fetch = vi.fn();
|
||||
|
||||
// Mock KV_STORAGE
|
||||
const mockEnv = {
|
||||
KV_STORAGE: {
|
||||
put: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
};
|
||||
transactionService.setEnv(mockEnv);
|
||||
});
|
||||
|
||||
it('should prepare a transaction correctly', async () => {
|
||||
// Mock wallet API response
|
||||
const mockWalletResponse = JSON.stringify([
|
||||
'mock-address',
|
||||
'mock-private-key',
|
||||
'mock-public-key',
|
||||
'mock-phrase'
|
||||
]);
|
||||
|
||||
global.fetch.mockResolvedValue({
|
||||
text: vi.fn().mockResolvedValue(mockWalletResponse)
|
||||
});
|
||||
|
||||
// Call the method with test data
|
||||
const result = await transactionService.handlePrepareTransaction(['donor123', 'bitcoin', '0.01']);
|
||||
|
||||
// Verify fetch was called with the correct URL
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://wallets.seemueller.io/api/btc/create'
|
||||
);
|
||||
|
||||
// Verify KV_STORAGE.put was called with the correct data
|
||||
expect(transactionService.env.KV_STORAGE.put).toHaveBeenCalledWith(
|
||||
'transactions::prepared::mock-uuid',
|
||||
expect.stringContaining('mock-address')
|
||||
);
|
||||
|
||||
// Verify the returned data
|
||||
expect(result).toEqual({
|
||||
depositAddress: 'mock-address',
|
||||
txKey: 'mock-uuid'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle different currencies correctly', async () => {
|
||||
// Mock wallet API response
|
||||
const mockWalletResponse = JSON.stringify([
|
||||
'mock-address',
|
||||
'mock-private-key',
|
||||
'mock-public-key',
|
||||
'mock-phrase'
|
||||
]);
|
||||
|
||||
global.fetch.mockResolvedValue({
|
||||
text: vi.fn().mockResolvedValue(mockWalletResponse)
|
||||
});
|
||||
|
||||
// Test with ethereum
|
||||
await transactionService.handlePrepareTransaction(['donor123', 'ethereum', '0.01']);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://wallets.seemueller.io/api/eth/create'
|
||||
);
|
||||
|
||||
// Reset mock and test with dogecoin
|
||||
vi.resetAllMocks();
|
||||
global.fetch.mockResolvedValue({
|
||||
text: vi.fn().mockResolvedValue(mockWalletResponse)
|
||||
});
|
||||
|
||||
await transactionService.handlePrepareTransaction(['donor123', 'dogecoin', '0.01']);
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
'https://wallets.seemueller.io/api/doge/create'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleTransact', () => {
|
||||
beforeEach(() => {
|
||||
// Mock routeAction
|
||||
transactionService.routeAction = vi.fn().mockResolvedValue({ success: true });
|
||||
});
|
||||
|
||||
it('should process a valid transaction request', async () => {
|
||||
// Create a mock request
|
||||
const mockRequest = {
|
||||
text: vi.fn().mockResolvedValue('PREPARE_TX,donor123,bitcoin,0.01')
|
||||
};
|
||||
|
||||
// Call the method
|
||||
const response = await transactionService.handleTransact(mockRequest);
|
||||
|
||||
// Verify routeAction was called with the correct parameters
|
||||
expect(transactionService.routeAction).toHaveBeenCalledWith(
|
||||
'PREPARE_TX',
|
||||
['donor123', 'bitcoin', '0.01']
|
||||
);
|
||||
|
||||
// Verify the response
|
||||
expect(response).toBeInstanceOf(Response);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
const responseBody = await response.json();
|
||||
expect(responseBody).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
// Create a mock request
|
||||
const mockRequest = {
|
||||
text: vi.fn().mockResolvedValue('PREPARE_TX,donor123,bitcoin,0.01')
|
||||
};
|
||||
|
||||
// Make routeAction throw an error
|
||||
transactionService.routeAction = vi.fn().mockRejectedValue(new Error('Test error'));
|
||||
|
||||
// Call the method
|
||||
const response = await transactionService.handleTransact(mockRequest);
|
||||
|
||||
// Verify the error response
|
||||
expect(response).toBeInstanceOf(Response);
|
||||
expect(response.status).toBe(500);
|
||||
|
||||
const responseBody = await response.json();
|
||||
expect(responseBody).toEqual({ error: 'Transaction failed' });
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user