first ruggedy draft
This commit is contained in:
102
apps/web/tests/mvp.spec.ts
Normal file
102
apps/web/tests/mvp.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user