(null);
const { user, isLoading, logout, hasPermission } = useAuth();
const router = useRouter();
@@ -28,6 +30,7 @@ export default function SumPage() {
const handleSum = async () => {
const numA = parseFloat(a) || 0;
const numB = parseFloat(b) || 0;
+ setError(null);
try {
const res = await fetch(`${API_URL}/api/sum`, {
@@ -36,13 +39,14 @@ export default function SumPage() {
credentials: "include",
body: JSON.stringify({ a: numA, b: numB }),
});
+ if (!res.ok) {
+ throw new Error("Calculation failed");
+ }
const data = await res.json();
setResult(data.result);
setShowResult(true);
- } catch {
- // Fallback to local calculation if API fails
- setResult(numA + numB);
- setShowResult(true);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Calculation failed");
}
};
@@ -51,6 +55,7 @@ export default function SumPage() {
setB("");
setResult(null);
setShowResult(false);
+ setError(null);
};
const handleLogout = async () => {
@@ -123,6 +128,9 @@ export default function SumPage() {
=
Calculate
+ {error && (
+ {error}
+ )}
) : (
@@ -145,80 +153,7 @@ export default function SumPage() {
);
}
-const styles: Record = {
- main: {
- minHeight: "100vh",
- background: "linear-gradient(135deg, #0f0f23 0%, #1a1a3e 50%, #2d1b4e 100%)",
- display: "flex",
- flexDirection: "column",
- },
- loader: {
- flex: 1,
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- fontFamily: "'DM Sans', system-ui, sans-serif",
- color: "rgba(255, 255, 255, 0.5)",
- fontSize: "1.125rem",
- },
- header: {
- padding: "1.5rem 2rem",
- borderBottom: "1px solid rgba(255, 255, 255, 0.06)",
- display: "flex",
- justifyContent: "space-between",
- alignItems: "center",
- },
- nav: {
- display: "flex",
- alignItems: "center",
- gap: "0.75rem",
- },
- navLink: {
- fontFamily: "'DM Sans', system-ui, sans-serif",
- color: "rgba(255, 255, 255, 0.5)",
- fontSize: "0.875rem",
- textDecoration: "none",
- transition: "color 0.2s",
- },
- navDivider: {
- color: "rgba(255, 255, 255, 0.2)",
- fontSize: "0.75rem",
- },
- navCurrent: {
- fontFamily: "'DM Sans', system-ui, sans-serif",
- color: "#a78bfa",
- fontSize: "0.875rem",
- fontWeight: 600,
- },
- userInfo: {
- display: "flex",
- alignItems: "center",
- gap: "1rem",
- },
- userEmail: {
- fontFamily: "'DM Sans', system-ui, sans-serif",
- color: "rgba(255, 255, 255, 0.6)",
- fontSize: "0.875rem",
- },
- logoutBtn: {
- fontFamily: "'DM Sans', system-ui, sans-serif",
- padding: "0.5rem 1rem",
- fontSize: "0.875rem",
- fontWeight: 500,
- background: "rgba(255, 255, 255, 0.05)",
- color: "rgba(255, 255, 255, 0.7)",
- border: "1px solid rgba(255, 255, 255, 0.1)",
- borderRadius: "8px",
- cursor: "pointer",
- transition: "all 0.2s",
- },
- content: {
- flex: 1,
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- padding: "2rem",
- },
+const pageStyles: Record = {
card: {
background: "rgba(255, 255, 255, 0.03)",
backdropFilter: "blur(10px)",
@@ -344,5 +279,13 @@ const styles: Record = {
resetIcon: {
fontSize: "1.25rem",
},
+ error: {
+ fontFamily: "'DM Sans', system-ui, sans-serif",
+ color: "#f87171",
+ fontSize: "0.875rem",
+ marginTop: "0.5rem",
+ },
};
+const styles = { ...sharedStyles, ...pageStyles };
+
diff --git a/frontend/e2e/permissions.spec.ts b/frontend/e2e/permissions.spec.ts
index e189241..c832e5f 100644
--- a/frontend/e2e/permissions.spec.ts
+++ b/frontend/e2e/permissions.spec.ts
@@ -1,4 +1,4 @@
-import { test, expect, Page, APIRequestContext } from "@playwright/test";
+import { test, expect, Page } from "@playwright/test";
/**
* Permission-based E2E tests
@@ -14,14 +14,23 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
// Test credentials - must match what's seeded in the database via seed.py
// These come from environment variables DEV_USER_EMAIL/PASSWORD and DEV_ADMIN_EMAIL/PASSWORD
+// Tests will fail fast if these are not set
+function getRequiredEnv(name: string): string {
+ const value = process.env[name];
+ if (!value) {
+ throw new Error(`Required environment variable ${name} is not set. Run 'source .env' or set it in your environment.`);
+ }
+ return value;
+}
+
const REGULAR_USER = {
- email: process.env.DEV_USER_EMAIL || "user@example.com",
- password: process.env.DEV_USER_PASSWORD || "user123",
+ email: getRequiredEnv("DEV_USER_EMAIL"),
+ password: getRequiredEnv("DEV_USER_PASSWORD"),
};
const ADMIN_USER = {
- email: process.env.DEV_ADMIN_EMAIL || "admin@example.com",
- password: process.env.DEV_ADMIN_PASSWORD || "admin123",
+ email: getRequiredEnv("DEV_ADMIN_EMAIL"),
+ password: getRequiredEnv("DEV_ADMIN_PASSWORD"),
};
// Helper to clear auth cookies
@@ -29,17 +38,6 @@ async function clearAuth(page: Page) {
await page.context().clearCookies();
}
-// Helper to create a user with specific role via API
-async function createUserWithRole(
- request: APIRequestContext,
- email: string,
- password: string,
- roleName: string
-): Promise {
- // This requires direct DB access or a test endpoint
- // For now, we'll use the seeded users from conftest
-}
-
// Helper to login a user
async function loginUser(page: Page, email: string, password: string) {
await page.goto("/login");
@@ -149,19 +147,9 @@ test.describe("Regular User Access", () => {
});
test.describe("Admin User Access", () => {
- // Skip these tests if admin user isn't set up
- // In real scenario, you'd create admin user in beforeAll
- test.skip(
- !process.env.DEV_ADMIN_EMAIL,
- "Admin tests require DEV_ADMIN_EMAIL and DEV_ADMIN_PASSWORD env vars"
- );
-
- const adminEmail = process.env.DEV_ADMIN_EMAIL || ADMIN_USER.email;
- const adminPassword = process.env.DEV_ADMIN_PASSWORD || ADMIN_USER.password;
-
test.beforeEach(async ({ page }) => {
await clearAuth(page);
- await loginUser(page, adminEmail, adminPassword);
+ await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
});
test("redirected from counter page to audit", async ({ page }) => {
@@ -258,17 +246,9 @@ test.describe("Permission Boundary via API", () => {
});
test("admin user API call to counter returns 403", async ({ page, request }) => {
- const adminEmail = process.env.DEV_ADMIN_EMAIL;
- const adminPassword = process.env.DEV_ADMIN_PASSWORD;
-
- if (!adminEmail || !adminPassword) {
- test.skip();
- return;
- }
-
// Login as admin
await clearAuth(page);
- await loginUser(page, adminEmail, adminPassword);
+ await loginUser(page, ADMIN_USER.email, ADMIN_USER.password);
// Get cookies
const cookies = await page.context().cookies();