mirror of
https://github.com/acedanger/finance.git
synced 2025-12-05 22:50:12 -08:00
feat(tests): add unit tests for accounts and transactions APIs
- Updated package.json to include Vitest for testing and added necessary devDependencies. - Created accounts.test.ts to test the accounts API endpoints for listing and retrieving accounts. - Implemented setup.ts to reset test data before each test run. - Developed transactions.test.ts to cover creating, updating, and deleting transactions through the API. - Added vitest.config.ts for configuring Vitest with appropriate settings and coverage options.
This commit is contained in:
2470
package-lock.json
generated
2470
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -6,9 +6,17 @@
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
"astro": "astro",
|
||||
"test": "vitest",
|
||||
"test:coverage": "vitest run --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.7.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@vitest/coverage-v8": "^0.34.6",
|
||||
"supertest": "^6.3.3",
|
||||
"vitest": "^0.34.3"
|
||||
}
|
||||
}
|
||||
56
src/test/accounts.test.ts
Normal file
56
src/test/accounts.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { GET as listAccounts } from "../pages/api/accounts/index";
|
||||
import { GET as getAccount } from "../pages/api/accounts/[id]/index";
|
||||
import { GET as listTransactions } from "../pages/api/accounts/[id]/transactions/index";
|
||||
|
||||
describe("Accounts API", () => {
|
||||
describe("GET /api/accounts", () => {
|
||||
it("should return all accounts", async () => {
|
||||
const response = await listAccounts();
|
||||
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({ params: { id: "1" } });
|
||||
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({ params: { id: "999" } });
|
||||
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 a specific account", async () => {
|
||||
const response = await listTransactions({ params: { id: "1" } });
|
||||
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({ params: { id: "999" } });
|
||||
const transactions = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(transactions).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
41
src/test/setup.ts
Normal file
41
src/test/setup.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { beforeEach } from "vitest";
|
||||
import { accounts, transactions } from "../data/store";
|
||||
|
||||
// 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,
|
||||
}
|
||||
);
|
||||
});
|
||||
128
src/test/transactions.test.ts
Normal file
128
src/test/transactions.test.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { POST as createTransaction } from "../pages/api/transactions/index";
|
||||
import {
|
||||
PUT as updateTransaction,
|
||||
DELETE as deleteTransaction,
|
||||
} from "../pages/api/transactions/[id]/index";
|
||||
import { accounts, transactions } from "../data/store";
|
||||
|
||||
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 response = await createTransaction({
|
||||
request: new Request("http://localhost:4321/api/transactions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(newTransaction),
|
||||
}),
|
||||
});
|
||||
|
||||
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 response = await createTransaction({
|
||||
request: new Request("http://localhost:4321/api/transactions", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(invalidTransaction),
|
||||
}),
|
||||
});
|
||||
|
||||
const error = await response.json();
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(error).toHaveProperty("error", "Missing required fields");
|
||||
});
|
||||
});
|
||||
|
||||
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 response = await updateTransaction({
|
||||
params: { id: "1" },
|
||||
request: new Request("http://localhost:4321/api/transactions/1", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(updates),
|
||||
}),
|
||||
});
|
||||
|
||||
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 for non-existent transaction", async () => {
|
||||
const response = await updateTransaction({
|
||||
params: { id: "999" },
|
||||
request: new Request("http://localhost:4321/api/transactions/999", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ description: "Test" }),
|
||||
}),
|
||||
});
|
||||
|
||||
const error = await response.json();
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(error).toHaveProperty("error", "Transaction 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({
|
||||
params: { id: "1" },
|
||||
});
|
||||
|
||||
expect(response.status).toBe(204);
|
||||
expect(transactions).toHaveLength(initialCount - 1);
|
||||
expect(accounts[0].balance).toBe(initialBalance - transactionAmount);
|
||||
});
|
||||
|
||||
it("should return 404 for non-existent transaction", async () => {
|
||||
const response = await deleteTransaction({
|
||||
params: { id: "999" },
|
||||
});
|
||||
|
||||
const error = await response.json();
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(error).toHaveProperty("error", "Transaction not found");
|
||||
});
|
||||
});
|
||||
});
|
||||
21
vitest.config.ts
Normal file
21
vitest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// Increase timeout for slower CI environments
|
||||
testTimeout: 10000,
|
||||
// Use the setup file we created
|
||||
setupFiles: ["./src/test/setup.ts"],
|
||||
// Ensure we're using the right environment
|
||||
environment: "node",
|
||||
// Only include test files
|
||||
include: ["src/test/**/*.{test,spec}.{ts,js}"],
|
||||
// Configure coverage collection
|
||||
coverage: {
|
||||
provider: "v8",
|
||||
reporter: ["text", "json", "html"],
|
||||
exclude: ["node_modules/", "src/test/**/*", "**/*.d.ts"],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user