103 lines
4.2 KiB
TypeScript
103 lines
4.2 KiB
TypeScript
import { expect, test, type APIRequestContext, type APIResponse } from "@playwright/test";
|
|
import { readFileSync } from "node:fs";
|
|
|
|
function envFileValue(key: string): string | undefined {
|
|
try {
|
|
const lines = readFileSync(".env.development", "utf8").split(/\r?\n/);
|
|
const prefix = `${key}=`;
|
|
const line = lines.find((item) => item.startsWith(prefix));
|
|
return line?.slice(prefix.length).trim().replace(/^["']|["']$/g, "");
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
const apiBase =
|
|
process.env.PLAYWRIGHT_API_URL ??
|
|
process.env.VITE_API_URL ??
|
|
envFileValue("VITE_API_URL") ??
|
|
"http://127.0.0.1:8000";
|
|
|
|
async function expectOk(response: APIResponse): Promise<void> {
|
|
expect(response.ok(), await response.text()).toBeTruthy();
|
|
}
|
|
|
|
async function postJson<T>(
|
|
request: APIRequestContext,
|
|
path: string,
|
|
data: Record<string, unknown>
|
|
): Promise<T> {
|
|
const response = await request.post(`${apiBase}${path}`, { data });
|
|
await expectOk(response);
|
|
return (await response.json()) as T;
|
|
}
|
|
|
|
test("local MVP ledger flow", async ({ page, request }) => {
|
|
test.setTimeout(60_000);
|
|
const suffix = Date.now().toString(36);
|
|
const household = await postJson<{ id: string; name: string }>(request, "/households", {
|
|
name: `Smoke household ${suffix}`,
|
|
default_currency: "EUR"
|
|
});
|
|
const userA = await postJson<{ id: string; display_name: string }>(request, "/users", {
|
|
household_id: household.id,
|
|
display_name: `Alice ${suffix}`,
|
|
email: `alice-${suffix}@example.test`
|
|
});
|
|
await postJson<{ id: string }>(request, "/users", {
|
|
household_id: household.id,
|
|
display_name: `Bob ${suffix}`,
|
|
email: `bob-${suffix}@example.test`
|
|
});
|
|
const account = await postJson<{ id: string; name: string }>(request, "/accounts", {
|
|
household_id: household.id,
|
|
owner_user_id: userA.id,
|
|
name: `Alice checking ${suffix}`,
|
|
account_type: "checking",
|
|
institution_name: "Smoke Bank",
|
|
currency: "EUR",
|
|
visibility_mode: "shared_full",
|
|
is_active: true
|
|
});
|
|
|
|
try {
|
|
await page.goto("/");
|
|
const householdSelect = page.getByLabel("Household");
|
|
await expect(householdSelect).toContainText(household.name);
|
|
await householdSelect.selectOption({ value: household.id });
|
|
const sessionSelect = page.getByLabel("Signed in");
|
|
await expect(sessionSelect).toBeEnabled();
|
|
await expect(sessionSelect).toContainText(userA.display_name);
|
|
await sessionSelect.selectOption({ value: userA.id });
|
|
await expect(page.getByRole("button", { name: "Logout" })).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "Entry" }).click();
|
|
const manualEntry = page.locator("section.panel").filter({ has: page.getByRole("heading", { name: "Entry" }) });
|
|
await manualEntry.getByLabel("Amount", { exact: true }).fill("42.50");
|
|
await manualEntry.getByLabel("Merchant", { exact: true }).fill(`Smoke Shop ${suffix}`);
|
|
await manualEntry.getByLabel("Description", { exact: true }).fill("Smoke groceries");
|
|
await manualEntry.locator('select[name="payer_user_id"]').selectOption({ label: userA.display_name });
|
|
await manualEntry.locator('select[name="source_account_id"]').selectOption({ label: account.name });
|
|
await manualEntry.getByRole("button", { name: "Observation" }).click();
|
|
|
|
await page.getByRole("button", { name: "Inbox" }).click();
|
|
const reviewCard = page.locator(".review-card").filter({ hasText: `Smoke Shop ${suffix}` }).first();
|
|
await expect(reviewCard).toBeVisible();
|
|
await reviewCard.getByRole("button", { name: "Confirm" }).click();
|
|
|
|
await page.getByRole("button", { name: "Ledger" }).click();
|
|
const ledgerRow = page.locator("tbody tr").filter({ hasText: `Smoke Shop ${suffix}` }).first();
|
|
await expect(ledgerRow).toBeVisible();
|
|
await expect(ledgerRow.getByText("confirmed")).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "Setup" }).click();
|
|
await expect(page.getByRole("button", { name: "Export JSON" })).toBeEnabled();
|
|
await expect(page.getByRole("button", { name: "SQLite backup" })).toBeEnabled();
|
|
} finally {
|
|
const cleanup = await request.delete(`${apiBase}/households/${household.id}`);
|
|
if (!cleanup.ok() && cleanup.status() !== 404) {
|
|
console.warn(await cleanup.text());
|
|
}
|
|
}
|
|
});
|