mirror of
https://github.com/acedanger/finance.git
synced 2025-12-05 22:50:12 -08:00
Replace in-memory store with database persistence
- Remove in-memory store and related tests - Add Decimal to number conversion in API responses - Update integration tests to handle Prisma Decimal type - Fix test configuration to only run db-integration tests
This commit is contained in:
@@ -1,57 +0,0 @@
|
|||||||
// TODO: Database Integration & Persistence
|
|
||||||
// - Implement database schema design
|
|
||||||
// - Add database connection pooling
|
|
||||||
// - Implement data migration strategy
|
|
||||||
// - Add database backup and recovery plans
|
|
||||||
// - Implement data validation layer
|
|
||||||
// - Add transaction logging
|
|
||||||
// - Implement audit trail
|
|
||||||
// - Add data archival strategy
|
|
||||||
|
|
||||||
import type { Account, Transaction } from '../types';
|
|
||||||
|
|
||||||
// TODO: Replace in-memory store with persistent database
|
|
||||||
// - Implement database connection and configuration
|
|
||||||
// - Create database schema for accounts and transactions
|
|
||||||
// - Add migration system for schema changes
|
|
||||||
// - Update all CRUD operations to use database instead of arrays
|
|
||||||
|
|
||||||
// Temporary in-memory store for development
|
|
||||||
export const accounts: Account[] = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: 'Checking Account',
|
|
||||||
last4: '4321',
|
|
||||||
balance: 2500.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
name: 'Savings Account',
|
|
||||||
last4: '8765',
|
|
||||||
balance: 10000.0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const transactions: Transaction[] = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
accountId: '1',
|
|
||||||
date: '2025-04-20',
|
|
||||||
description: 'Grocery Store',
|
|
||||||
amount: -75.5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
accountId: '1',
|
|
||||||
date: '2025-04-21',
|
|
||||||
description: 'Salary Deposit',
|
|
||||||
amount: 3000.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
accountId: '2',
|
|
||||||
date: '2025-04-22',
|
|
||||||
description: 'Transfer to Savings',
|
|
||||||
amount: 500.0,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -5,7 +5,13 @@ export const GET: APIRoute = async ({ params }) => {
|
|||||||
try {
|
try {
|
||||||
const accountTransactions = await transactionService.getByAccountId(params.id as string);
|
const accountTransactions = await transactionService.getByAccountId(params.id as string);
|
||||||
|
|
||||||
return new Response(JSON.stringify(accountTransactions), {
|
// Convert Decimal to number for each transaction in response
|
||||||
|
const response = accountTransactions.map((transaction) => ({
|
||||||
|
...transaction,
|
||||||
|
amount: Number(transaction.amount),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(response), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ export const GET: APIRoute = async ({ params }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(JSON.stringify(transaction), {
|
// Convert Decimal to number for response
|
||||||
|
const response = {
|
||||||
|
...transaction,
|
||||||
|
amount: Number(transaction.amount),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(response), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
@@ -66,7 +72,13 @@ export const PUT: APIRoute = async ({ request, params }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(JSON.stringify(updatedTransaction), {
|
// Convert Decimal to number for response
|
||||||
|
const response = {
|
||||||
|
...updatedTransaction,
|
||||||
|
amount: Number(updatedTransaction.amount),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(response), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -69,7 +69,13 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
tags: transaction.tags,
|
tags: transaction.tags,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Response(JSON.stringify(newTransaction), {
|
// Convert Decimal to number for response
|
||||||
|
const response = {
|
||||||
|
...newTransaction,
|
||||||
|
amount: Number(newTransaction.amount),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(response), {
|
||||||
status: 201,
|
status: 201,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
import { GET as getAccount } from '../pages/api/accounts/[id]/index';
|
|
||||||
import { GET as listTransactions } from '../pages/api/accounts/[id]/transactions/index';
|
|
||||||
import { GET as listAccounts } from '../pages/api/accounts/index';
|
|
||||||
import { createMockAPIContext } from './setup';
|
|
||||||
|
|
||||||
describe('Accounts API', () => {
|
|
||||||
describe('GET /api/accounts', () => {
|
|
||||||
it('should return all accounts', async () => {
|
|
||||||
const response = await listAccounts(createMockAPIContext());
|
|
||||||
const accounts = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(accounts).toHaveLength(2);
|
|
||||||
expect(accounts[0]).toHaveProperty('id', '1');
|
|
||||||
expect(accounts[1]).toHaveProperty('id', '2');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/accounts/:id', () => {
|
|
||||||
it('should return a specific account', async () => {
|
|
||||||
const response = await getAccount(
|
|
||||||
createMockAPIContext({ params: { id: '1' } }) as APIContext,
|
|
||||||
);
|
|
||||||
const account = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(account).toHaveProperty('id', '1');
|
|
||||||
expect(account).toHaveProperty('name', 'Test Checking');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 404 for non-existent account', async () => {
|
|
||||||
const response = await getAccount(
|
|
||||||
createMockAPIContext({ params: { id: '999' } }) as APIContext,
|
|
||||||
);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Account not found');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /api/accounts/:id/transactions', () => {
|
|
||||||
it('should return transactions for an account', async () => {
|
|
||||||
const response = await listTransactions(
|
|
||||||
createMockAPIContext({ params: { id: '1' } }) as APIContext,
|
|
||||||
);
|
|
||||||
const transactions = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(transactions).toHaveLength(1);
|
|
||||||
expect(transactions[0]).toHaveProperty('accountId', '1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return empty array for account with no transactions', async () => {
|
|
||||||
const response = await listTransactions(
|
|
||||||
createMockAPIContext({ params: { id: '999' } }) as APIContext,
|
|
||||||
);
|
|
||||||
const transactions = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(transactions).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -4,8 +4,7 @@ import { accountService, transactionService } from '../data/db.service';
|
|||||||
import { prisma } from '../data/prisma';
|
import { prisma } from '../data/prisma';
|
||||||
|
|
||||||
// Define a test server
|
// Define a test server
|
||||||
// Note: In a real scenario, you might want to start an actual server or mock the Astro API routes
|
const BASE_URL = 'http://localhost:4322';
|
||||||
const BASE_URL = 'http://localhost:3000';
|
|
||||||
const request = supertest(BASE_URL);
|
const request = supertest(BASE_URL);
|
||||||
|
|
||||||
// Test variables to store IDs across tests
|
// Test variables to store IDs across tests
|
||||||
@@ -179,7 +178,7 @@ describe('Database Integration Tests', () => {
|
|||||||
|
|
||||||
if (account) {
|
if (account) {
|
||||||
// The account should have been debited by the transaction amount
|
// The account should have been debited by the transaction amount
|
||||||
expect(account.balance).toBe(1000 - 75.5);
|
expect(Number(account.balance)).toBe(1000 - 75.5);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,7 +187,7 @@ describe('Database Integration Tests', () => {
|
|||||||
|
|
||||||
// Get the initial account data
|
// Get the initial account data
|
||||||
const accountBefore = await accountService.getById(testAccountId);
|
const accountBefore = await accountService.getById(testAccountId);
|
||||||
const initialBalance = accountBefore?.balance || 0;
|
const initialBalance = Number(accountBefore?.balance || 0);
|
||||||
|
|
||||||
const response = await request.delete(`/api/transactions/${testTransactionId}`);
|
const response = await request.delete(`/api/transactions/${testTransactionId}`);
|
||||||
expect(response.status).toBe(204);
|
expect(response.status).toBe(204);
|
||||||
@@ -199,7 +198,7 @@ describe('Database Integration Tests', () => {
|
|||||||
|
|
||||||
// Verify account balance was restored
|
// Verify account balance was restored
|
||||||
const accountAfter = await accountService.getById(testAccountId);
|
const accountAfter = await accountService.getById(testAccountId);
|
||||||
expect(accountAfter?.balance).toBe(initialBalance + 75.5);
|
expect(Number(accountAfter?.balance)).toBe(initialBalance + 75.5);
|
||||||
|
|
||||||
// Clear the testTransactionId since it's been deleted
|
// Clear the testTransactionId since it's been deleted
|
||||||
testTransactionId = '';
|
testTransactionId = '';
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import type { APIContext } from 'astro';
|
import type { APIContext } from 'astro';
|
||||||
import { beforeEach } from 'vitest';
|
|
||||||
import { accounts, transactions } from '../data/store';
|
|
||||||
|
|
||||||
// Create a mock APIContext factory
|
// Create a mock APIContext factory
|
||||||
export function createMockAPIContext(options: Partial<APIContext> = {}): APIContext {
|
export function createMockAPIContext(options: Partial<APIContext> = {}): APIContext {
|
||||||
@@ -17,42 +15,3 @@ export function createMockAPIContext(options: Partial<APIContext> = {}): APICont
|
|||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset test data before each test
|
|
||||||
beforeEach(() => {
|
|
||||||
// Reset accounts to initial state
|
|
||||||
accounts.length = 0;
|
|
||||||
accounts.push(
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
name: 'Test Checking',
|
|
||||||
last4: '1234',
|
|
||||||
balance: 1000.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
name: 'Test Savings',
|
|
||||||
last4: '5678',
|
|
||||||
balance: 5000.0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reset transactions to initial state
|
|
||||||
transactions.length = 0;
|
|
||||||
transactions.push(
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
accountId: '1',
|
|
||||||
date: '2025-04-24',
|
|
||||||
description: 'Test Transaction 1',
|
|
||||||
amount: -50.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
accountId: '2',
|
|
||||||
date: '2025-04-24',
|
|
||||||
description: 'Test Transaction 2',
|
|
||||||
amount: 100.0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,325 +0,0 @@
|
|||||||
// TODO: Testing Improvements
|
|
||||||
// - Add integration tests for API endpoints
|
|
||||||
// - Add end-to-end tests with Playwright/Cypress
|
|
||||||
// - Add performance testing
|
|
||||||
// - Add accessibility testing with axe-core
|
|
||||||
// - Add visual regression testing
|
|
||||||
// - Add load testing for API endpoints
|
|
||||||
// - Implement test data factories
|
|
||||||
|
|
||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
import { accounts, transactions } from '../data/store';
|
|
||||||
import {
|
|
||||||
DELETE as deleteTransaction,
|
|
||||||
PUT as updateTransaction,
|
|
||||||
} from '../pages/api/transactions/[id]/index';
|
|
||||||
import { POST as createTransaction } from '../pages/api/transactions/index';
|
|
||||||
import type { Transaction } from '../types';
|
|
||||||
import { createMockAPIContext } from './setup';
|
|
||||||
|
|
||||||
describe('Transactions API', () => {
|
|
||||||
describe('POST /api/transactions', () => {
|
|
||||||
it('should create a new transaction', async () => {
|
|
||||||
const initialBalance = accounts[0].balance;
|
|
||||||
const newTransaction = {
|
|
||||||
accountId: '1',
|
|
||||||
date: '2025-04-24',
|
|
||||||
description: 'Test New Transaction',
|
|
||||||
amount: -25.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ctx = createMockAPIContext() as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(newTransaction),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await createTransaction(ctx);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(201);
|
|
||||||
expect(result).toHaveProperty('id');
|
|
||||||
expect(result.description).toBe(newTransaction.description);
|
|
||||||
expect(accounts[0].balance).toBe(initialBalance + newTransaction.amount);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject transaction with missing fields', async () => {
|
|
||||||
const invalidTransaction = {
|
|
||||||
accountId: '1',
|
|
||||||
// Missing required fields
|
|
||||||
};
|
|
||||||
|
|
||||||
const ctx = createMockAPIContext() as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(invalidTransaction),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await createTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
|
||||||
expect(error).toHaveProperty('error', 'Missing required fields');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject transaction with invalid account', async () => {
|
|
||||||
const invalidTransaction = {
|
|
||||||
accountId: '999',
|
|
||||||
date: '2025-04-24',
|
|
||||||
description: 'Invalid Account Test',
|
|
||||||
amount: 100,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ctx = createMockAPIContext() as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(invalidTransaction),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await createTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Account not found');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject invalid request body', async () => {
|
|
||||||
const ctx = createMockAPIContext() as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: 'invalid json',
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await createTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
|
||||||
expect(error).toHaveProperty('error', 'Invalid request body');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PUT /api/transactions/:id', () => {
|
|
||||||
it('should update an existing transaction', async () => {
|
|
||||||
const initialBalance = accounts[0].balance;
|
|
||||||
const originalAmount = transactions[0].amount;
|
|
||||||
const updates = {
|
|
||||||
description: 'Updated Description',
|
|
||||||
amount: -75.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ctx = createMockAPIContext({ params: { id: '1' } }) as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/1', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(updates),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(result.description).toBe(updates.description);
|
|
||||||
expect(result.amount).toBe(updates.amount);
|
|
||||||
expect(accounts[0].balance).toBe(initialBalance - originalAmount + updates.amount);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject update with invalid request body', async () => {
|
|
||||||
const ctx = createMockAPIContext({ params: { id: '1' } }) as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/1', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: 'invalid json',
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
|
||||||
expect(error).toHaveProperty('error', 'Invalid request body');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject update for non-existent transaction', async () => {
|
|
||||||
const ctx = createMockAPIContext({ params: { id: '999' } }) as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/999', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ description: 'Test' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Transaction not found');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject update for non-existent account', async () => {
|
|
||||||
// First update the transaction to point to a non-existent account
|
|
||||||
transactions[0].accountId = '999';
|
|
||||||
|
|
||||||
const ctx = createMockAPIContext({ params: { id: '1' } }) as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/1', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ amount: -100 }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Account not found');
|
|
||||||
|
|
||||||
// Reset account ID for other tests
|
|
||||||
transactions[0].accountId = '1';
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle account balance updates correctly when switching accounts', async () => {
|
|
||||||
// Create initial state
|
|
||||||
const oldAccount = accounts[0];
|
|
||||||
const newAccount = accounts[1];
|
|
||||||
const initialOldBalance = oldAccount.balance;
|
|
||||||
const initialNewBalance = newAccount.balance;
|
|
||||||
const oldTransaction = transactions.find((t) => t.id === '1');
|
|
||||||
if (!oldTransaction) throw new Error('Test transaction not found');
|
|
||||||
|
|
||||||
const ctx = createMockAPIContext({ params: { id: '1' } }) as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/1', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
accountId: newAccount.id,
|
|
||||||
amount: -100,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(result.accountId).toBe(newAccount.id);
|
|
||||||
|
|
||||||
// Old account should have the old amount removed
|
|
||||||
expect(oldAccount.balance).toBe(initialOldBalance + Math.abs(oldTransaction.amount));
|
|
||||||
|
|
||||||
// New account should have the new amount added
|
|
||||||
expect(newAccount.balance).toBe(initialNewBalance - 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject update without transaction ID', async () => {
|
|
||||||
const ctx = createMockAPIContext() as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/undefined', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ description: 'Test' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
|
||||||
expect(error).toHaveProperty('error', 'Transaction ID is required');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject update when old account is missing', async () => {
|
|
||||||
// Store current accounts and clear the array
|
|
||||||
const savedAccounts = [...accounts];
|
|
||||||
accounts.length = 0;
|
|
||||||
|
|
||||||
const ctx = createMockAPIContext({ params: { id: '1' } }) as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/1', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ amount: -100 }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Account not found');
|
|
||||||
|
|
||||||
// Restore accounts
|
|
||||||
accounts.push(...savedAccounts);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should reject update when new account doesn't exist", async () => {
|
|
||||||
const ctx = createMockAPIContext({ params: { id: '1' } }) as APIContext;
|
|
||||||
ctx.request = new Request('http://localhost:4321/api/transactions/1', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
accountId: '999', // Non-existent account
|
|
||||||
amount: -100,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await updateTransaction(ctx);
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Account not found');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DELETE /api/transactions/:id', () => {
|
|
||||||
it('should delete a transaction', async () => {
|
|
||||||
const initialBalance = accounts[0].balance;
|
|
||||||
const transactionAmount = transactions[0].amount;
|
|
||||||
const initialCount = transactions.length;
|
|
||||||
|
|
||||||
const response = await deleteTransaction(
|
|
||||||
createMockAPIContext({ params: { id: '1' } }) as any,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
|
||||||
expect(transactions).toHaveLength(initialCount - 1);
|
|
||||||
expect(accounts[0].balance).toBe(initialBalance - transactionAmount);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject delete without transaction ID', async () => {
|
|
||||||
const response = await deleteTransaction(createMockAPIContext() as APIContext);
|
|
||||||
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
|
||||||
expect(error).toHaveProperty('error', 'Transaction ID is required');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return 404 for non-existent transaction', async () => {
|
|
||||||
const response = await deleteTransaction(
|
|
||||||
createMockAPIContext({ params: { id: '999' } }) as any,
|
|
||||||
);
|
|
||||||
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Transaction not found');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle deletion with non-existent account', async () => {
|
|
||||||
// Create a transaction then remove its account
|
|
||||||
const testTransaction: Transaction = {
|
|
||||||
id: 'test-delete',
|
|
||||||
accountId: 'test-account',
|
|
||||||
date: '2025-04-24',
|
|
||||||
description: 'Test Delete',
|
|
||||||
amount: 100,
|
|
||||||
};
|
|
||||||
transactions.push(testTransaction);
|
|
||||||
|
|
||||||
const response = await deleteTransaction(
|
|
||||||
createMockAPIContext({ params: { id: 'test-delete' } }) as any,
|
|
||||||
);
|
|
||||||
|
|
||||||
const error = await response.json();
|
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
|
||||||
expect(error).toHaveProperty('error', 'Account not found');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -9,8 +9,8 @@ export default defineConfig({
|
|||||||
setupFiles: ['./src/test/setup.ts'],
|
setupFiles: ['./src/test/setup.ts'],
|
||||||
// Ensure we're using the right environment
|
// Ensure we're using the right environment
|
||||||
environment: 'node',
|
environment: 'node',
|
||||||
// Only include test files
|
// Only include database integration tests
|
||||||
include: ['src/test/**/*.{test,spec}.{ts,js}'],
|
include: ['src/test/db-integration.test.ts'],
|
||||||
// Configure coverage collection
|
// Configure coverage collection
|
||||||
coverage: {
|
coverage: {
|
||||||
provider: 'v8',
|
provider: 'v8',
|
||||||
|
|||||||
Reference in New Issue
Block a user