diff --git a/src/pages/api/transactions/[id]/index.ts b/src/pages/api/transactions/[id]/index.ts index 9d50928..f84ea5e 100644 --- a/src/pages/api/transactions/[id]/index.ts +++ b/src/pages/api/transactions/[id]/index.ts @@ -40,13 +40,14 @@ export const PUT: APIRoute = async ({ request, params }) => { // If account is changing, validate new account exists let newAccount = oldAccount; if (updates.accountId && updates.accountId !== oldTransaction.accountId) { - newAccount = accounts.find((a) => a.id === updates.accountId); - if (!newAccount) { + const foundAccount = accounts.find((a) => a.id === updates.accountId); + if (!foundAccount) { return new Response(JSON.stringify({ error: "Account not found" }), { status: 404, headers: { "Content-Type": "application/json" }, }); } + newAccount = foundAccount; } // First, remove the old transaction's effect on the old account diff --git a/src/test/accounts.test.ts b/src/test/accounts.test.ts index 6d2fdbf..47eeed6 100644 --- a/src/test/accounts.test.ts +++ b/src/test/accounts.test.ts @@ -2,11 +2,12 @@ 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"; +import { createMockAPIContext } from "./setup"; describe("Accounts API", () => { describe("GET /api/accounts", () => { it("should return all accounts", async () => { - const response = await listAccounts(); + const response = await listAccounts(createMockAPIContext() as any); const accounts = await response.json(); expect(response.status).toBe(200); @@ -18,7 +19,9 @@ describe("Accounts API", () => { describe("GET /api/accounts/:id", () => { it("should return a specific account", async () => { - const response = await getAccount({ params: { id: "1" } }); + const response = await getAccount( + createMockAPIContext({ params: { id: "1" } }) as any + ); const account = await response.json(); expect(response.status).toBe(200); @@ -27,7 +30,9 @@ describe("Accounts API", () => { }); it("should return 404 for non-existent account", async () => { - const response = await getAccount({ params: { id: "999" } }); + const response = await getAccount( + createMockAPIContext({ params: { id: "999" } }) as any + ); const error = await response.json(); expect(response.status).toBe(404); @@ -36,8 +41,10 @@ describe("Accounts API", () => { }); describe("GET /api/accounts/:id/transactions", () => { - it("should return transactions for a specific account", async () => { - const response = await listTransactions({ params: { id: "1" } }); + it("should return transactions for an account", async () => { + const response = await listTransactions( + createMockAPIContext({ params: { id: "1" } }) as any + ); const transactions = await response.json(); expect(response.status).toBe(200); @@ -46,7 +53,9 @@ describe("Accounts API", () => { }); it("should return empty array for account with no transactions", async () => { - const response = await listTransactions({ params: { id: "999" } }); + const response = await listTransactions( + createMockAPIContext({ params: { id: "999" } }) as any + ); const transactions = await response.json(); expect(response.status).toBe(200); diff --git a/src/test/setup.ts b/src/test/setup.ts index 2a40360..da93d5a 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,5 +1,31 @@ import { beforeEach } from "vitest"; import { accounts, transactions } from "../data/store"; +import type { APIContext } from "astro"; + +// Create a mock APIContext factory +export function createMockAPIContext< + T extends Record = Record +>({ params = {} as T } = {}): Partial { + return { + params, + props: {}, + request: new Request("http://localhost:4321"), + site: new URL("http://localhost:4321"), + generator: "test", + url: new URL("http://localhost:4321"), + clientAddress: "127.0.0.1", + cookies: new Headers() as any, // Cast Headers to cookies as we don't need cookie functionality in tests + redirect: () => new Response(), + locals: {}, + preferredLocale: undefined, + preferredLocaleList: [], + currentLocale: undefined, + routePattern: "/api/[...path]", + originPathname: "/api", + getActionResult: () => undefined, + isPrerendered: false, + }; +} // Reset test data before each test beforeEach(() => { diff --git a/src/test/transactions.test.ts b/src/test/transactions.test.ts index 1bbf9bc..f6b4dee 100644 --- a/src/test/transactions.test.ts +++ b/src/test/transactions.test.ts @@ -6,6 +6,7 @@ import { } from "../pages/api/transactions/[id]/index"; import { accounts, transactions } from "../data/store"; import type { Transaction } from "../types"; +import { createMockAPIContext } from "./setup"; describe("Transactions API", () => { describe("POST /api/transactions", () => { @@ -18,14 +19,14 @@ describe("Transactions API", () => { 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 ctx = createMockAPIContext() as any; + 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); @@ -40,14 +41,14 @@ describe("Transactions API", () => { // 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 ctx = createMockAPIContext() as any; + 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); @@ -56,20 +57,20 @@ describe("Transactions API", () => { it("should reject transaction with invalid account", async () => { const invalidTransaction = { - accountId: "999", // Non-existent account + accountId: "999", date: "2025-04-24", description: "Invalid Account Test", amount: 100, }; - const response = await createTransaction({ - request: new Request("http://localhost:4321/api/transactions", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(invalidTransaction), - }), + const ctx = createMockAPIContext() as any; + 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); @@ -77,14 +78,14 @@ describe("Transactions API", () => { }); it("should reject invalid request body", async () => { - const response = await createTransaction({ - request: new Request("http://localhost:4321/api/transactions", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: "invalid json", - }), + const ctx = createMockAPIContext() as any; + 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); @@ -101,15 +102,14 @@ describe("Transactions API", () => { 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 ctx = createMockAPIContext({ params: { id: "1" } }) as any; + 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); @@ -121,15 +121,14 @@ describe("Transactions API", () => { }); it("should reject update with invalid request body", async () => { - const response = await updateTransaction({ - params: { id: "1" }, - request: new Request("http://localhost:4321/api/transactions/1", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: "invalid json", - }), + const ctx = createMockAPIContext({ params: { id: "1" } }) as any; + 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); @@ -137,15 +136,14 @@ describe("Transactions API", () => { }); 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 ctx = createMockAPIContext({ params: { id: "999" } }) as any; + 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); @@ -156,15 +154,14 @@ describe("Transactions API", () => { // First update the transaction to point to a non-existent account transactions[0].accountId = "999"; - 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({ amount: -100 }), - }), + const ctx = createMockAPIContext({ params: { id: "1" } }) as any; + 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); @@ -183,19 +180,17 @@ describe("Transactions API", () => { const oldTransaction = transactions.find((t) => t.id === "1"); if (!oldTransaction) throw new Error("Test transaction not found"); - // Update transaction to move it to a different account - 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({ - accountId: newAccount.id, - amount: -100, - }), + const ctx = createMockAPIContext({ params: { id: "1" } }) as any; + 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); @@ -211,15 +206,17 @@ describe("Transactions API", () => { }); it("should reject update without transaction ID", async () => { - const response = await updateTransaction({ - params: {}, - request: new Request("http://localhost:4321/api/transactions/undefined", { + const ctx = createMockAPIContext() as any; + 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); @@ -231,15 +228,14 @@ describe("Transactions API", () => { const savedAccounts = [...accounts]; accounts.length = 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({ amount: -100 }), - }), + const ctx = createMockAPIContext({ params: { id: "1" } }) as any; + 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); @@ -250,18 +246,17 @@ describe("Transactions API", () => { }); it("should reject update when new account doesn't exist", async () => { - 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({ - accountId: "999", // Non-existent account - amount: -100, - }), + const ctx = createMockAPIContext({ params: { id: "1" } }) as any; + 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); @@ -275,9 +270,9 @@ describe("Transactions API", () => { const transactionAmount = transactions[0].amount; const initialCount = transactions.length; - const response = await deleteTransaction({ - params: { id: "1" }, - }); + const response = await deleteTransaction( + createMockAPIContext({ params: { id: "1" } }) as any + ); expect(response.status).toBe(204); expect(transactions).toHaveLength(initialCount - 1); @@ -285,9 +280,7 @@ describe("Transactions API", () => { }); it("should reject delete without transaction ID", async () => { - const response = await deleteTransaction({ - params: {}, - }); + const response = await deleteTransaction(createMockAPIContext() as any); const error = await response.json(); @@ -296,9 +289,9 @@ describe("Transactions API", () => { }); it("should return 404 for non-existent transaction", async () => { - const response = await deleteTransaction({ - params: { id: "999" }, - }); + const response = await deleteTransaction( + createMockAPIContext({ params: { id: "999" } }) as any + ); const error = await response.json(); @@ -317,9 +310,9 @@ describe("Transactions API", () => { }; transactions.push(testTransaction); - const response = await deleteTransaction({ - params: { id: "test-delete" }, - }); + const response = await deleteTransaction( + createMockAPIContext({ params: { id: "test-delete" } }) as any + ); const error = await response.json();