From 2ee27cf5b220104fa82eac8070d4e7e143946544 Mon Sep 17 00:00:00 2001 From: counterweight Date: Fri, 26 Dec 2025 20:57:32 +0100 Subject: [PATCH] Add e2e tests for admin pricing page - Test admin can access pricing page and view current configuration - Test admin can update pricing configuration with confirmation - Test form validation prevents invalid values - Test regular users cannot access pricing page - Test API permissions (admin can access, regular user cannot) - Test API validation works correctly --- frontend/e2e/admin-pricing.spec.ts | 293 +++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 frontend/e2e/admin-pricing.spec.ts diff --git a/frontend/e2e/admin-pricing.spec.ts b/frontend/e2e/admin-pricing.spec.ts new file mode 100644 index 0000000..275b30d --- /dev/null +++ b/frontend/e2e/admin-pricing.spec.ts @@ -0,0 +1,293 @@ +import { test, expect } from "@playwright/test"; +import { REGULAR_USER, ADMIN_USER, clearAuth, loginUser } from "./helpers/auth"; +import { getBackendUrl } from "./helpers/backend-url"; + +/** + * Admin Pricing Page E2E Tests + * + * Tests for the admin pricing configuration page. + */ + +test.describe("Admin Pricing Page - Admin Access", () => { + test.beforeEach(async ({ context, page }) => { + await context.addInitScript(() => { + window.localStorage.setItem("arbret-locale", "en"); + }); + await clearAuth(page); + await loginUser(page, ADMIN_USER.email, ADMIN_USER.password); + }); + + test("admin can access pricing page and UI elements are correct", async ({ page }) => { + // Test navigation link + await page.goto("/admin/trades"); + const pricingLink = page.locator('a[href="/admin/pricing"]'); + await expect(pricingLink).toBeVisible(); + + // Test page access and structure + await page.goto("/admin/pricing"); + await expect(page).toHaveURL("/admin/pricing"); + await expect(page.getByRole("heading", { name: "Pricing Configuration" })).toBeVisible(); + await expect(page.getByText("Configure premium pricing and trade amount limits")).toBeVisible(); + + // Check all form fields are present + await expect(page.getByLabel(/Premium for BUY/i)).toBeVisible(); + await expect(page.getByLabel(/Premium for SELL/i)).toBeVisible(); + await expect(page.getByLabel(/Small Trade Threshold/i)).toBeVisible(); + await expect(page.getByLabel(/Extra Premium for Small Trades/i)).toBeVisible(); + await expect(page.getByLabel(/Minimum Amount.*BUY/i)).toBeVisible(); + await expect(page.getByLabel(/Maximum Amount.*BUY/i)).toBeVisible(); + await expect(page.getByLabel(/Minimum Amount.*SELL/i)).toBeVisible(); + await expect(page.getByLabel(/Maximum Amount.*SELL/i)).toBeVisible(); + + // Check save button is present + await expect(page.getByRole("button", { name: /Save Changes/i })).toBeVisible(); + }); + + test("can view current pricing configuration", async ({ page }) => { + await page.goto("/admin/pricing"); + + // Wait for data to load + await page.waitForLoadState("networkidle"); + + // All input fields should have values (not empty) + const inputs = page.locator('input[type="number"]'); + const count = await inputs.count(); + expect(count).toBeGreaterThan(0); + + // Check that premium fields have values + const premiumBuyInput = page.getByLabel(/Premium for BUY/i); + const premiumSellInput = page.getByLabel(/Premium for SELL/i); + const buyValue = await premiumBuyInput.inputValue(); + const sellValue = await premiumSellInput.inputValue(); + + expect(buyValue).not.toBe(""); + expect(sellValue).not.toBe(""); + }); + + test("can update pricing configuration", async ({ page }) => { + await page.goto("/admin/pricing"); + await page.waitForLoadState("networkidle"); + + // Get current values + const premiumBuyInput = page.getByLabel(/Premium for BUY/i); + const currentBuyValue = await premiumBuyInput.inputValue(); + const newBuyValue = currentBuyValue === "5" ? "6" : "5"; + + // Update premium buy value + await premiumBuyInput.clear(); + await premiumBuyInput.fill(newBuyValue); + + // Set up response listener before clicking save + const putPromise = page.waitForResponse( + (resp) => resp.url().includes("/api/admin/pricing") && resp.request().method() === "PUT" + ); + const getPromise = page.waitForResponse( + (resp) => resp.url().includes("/api/admin/pricing") && resp.request().method() === "GET" + ); + + // Click save button (with confirmation) + await page.getByRole("button", { name: /Save Changes/i }).click(); + + // Confirm the save action + await expect(page.getByText(/Are you sure/i)).toBeVisible(); + await page.getByRole("button", { name: /confirm|yes|ok/i }).click(); + + // Wait for API calls to complete + await putPromise; + await getPromise; + + // Check for success message + await expect(page.getByText(/saved successfully/i)).toBeVisible({ timeout: 5000 }); + + // Verify the value was updated + await page.waitForLoadState("networkidle"); + const updatedValue = await premiumBuyInput.inputValue(); + expect(updatedValue).toBe(newBuyValue); + }); + + test("validation prevents invalid values", async ({ page }) => { + await page.goto("/admin/pricing"); + await page.waitForLoadState("networkidle"); + + // Test premium range validation (should be -100 to 100) + const premiumBuyInput = page.getByLabel(/Premium for BUY/i); + await premiumBuyInput.clear(); + await premiumBuyInput.fill("150"); // Invalid: > 100 + + // Try to save + await page.getByRole("button", { name: /Save Changes/i }).click(); + await expect(page.getByText(/confirm|yes|ok/i)).toBeVisible(); + await page.getByRole("button", { name: /confirm|yes|ok/i }).click(); + + // Should show validation error + await expect(page.getByText(/must be between.*-100.*100/i)).toBeVisible({ timeout: 2000 }); + + // Test min < max validation + const minBuyInput = page.getByLabel(/Minimum Amount.*BUY/i); + const maxBuyInput = page.getByLabel(/Maximum Amount.*BUY/i); + + const currentMin = await minBuyInput.inputValue(); + const currentMax = await maxBuyInput.inputValue(); + + // Set min >= max (should fail) + await minBuyInput.clear(); + await minBuyInput.fill(currentMax); + await maxBuyInput.clear(); + await maxBuyInput.fill(currentMin); + + // Try to save + await page.getByRole("button", { name: /Save Changes/i }).click(); + await expect(page.getByText(/confirm|yes|ok/i)).toBeVisible(); + await page.getByRole("button", { name: /confirm|yes|ok/i }).click(); + + // Should show validation error + await expect(page.getByText(/minimum must be less than maximum/i)).toBeVisible({ + timeout: 2000, + }); + }); + + test("form fields update correctly when values change", async ({ page }) => { + await page.goto("/admin/pricing"); + await page.waitForLoadState("networkidle"); + + const smallTradeThresholdInput = page.getByLabel(/Small Trade Threshold/i); + const currentThreshold = await smallTradeThresholdInput.inputValue(); + + // Update threshold + const newThreshold = currentThreshold === "200" ? "250" : "200"; + await smallTradeThresholdInput.clear(); + await smallTradeThresholdInput.fill(newThreshold); + + // Verify the value is set + const updatedThreshold = await smallTradeThresholdInput.inputValue(); + expect(updatedThreshold).toBe(newThreshold); + }); +}); + +test.describe("Admin Pricing Page - Access Control", () => { + test("regular user cannot access pricing page", async ({ page }) => { + await clearAuth(page); + await loginUser(page, REGULAR_USER.email, REGULAR_USER.password); + + // Regular user should not see pricing link + await page.goto("/"); + const pricingLink = page.locator('a[href="/admin/pricing"]'); + await expect(pricingLink).toHaveCount(0); + + // Direct access should redirect + await page.goto("/admin/pricing"); + await expect(page).not.toHaveURL("/admin/pricing"); + }); + + test("unauthenticated user cannot access pricing page", async ({ page }) => { + await clearAuth(page); + await page.goto("/admin/pricing"); + await expect(page).toHaveURL("/login"); + }); +}); + +test.describe("Admin Pricing API", () => { + test("admin can access pricing API, regular user cannot", async ({ page, request }) => { + // Test admin API access + await clearAuth(page); + await loginUser(page, ADMIN_USER.email, ADMIN_USER.password); + const cookies = await page.context().cookies(); + const authCookie = cookies.find((c) => c.name === "auth_token"); + + if (authCookie) { + // Test GET pricing config + const getResponse = await request.get(`${getBackendUrl()}/api/admin/pricing`, { + headers: { + Cookie: `auth_token=${authCookie.value}`, + }, + }); + expect(getResponse.status()).toBe(200); + const data = await getResponse.json(); + expect(data).toHaveProperty("premium_buy"); + expect(data).toHaveProperty("premium_sell"); + expect(data).toHaveProperty("eur_min_buy"); + expect(data).toHaveProperty("eur_max_buy"); + } + + // Test regular user API access + await clearAuth(page); + await loginUser(page, REGULAR_USER.email, REGULAR_USER.password); + const regularCookies = await page.context().cookies(); + const regularAuthCookie = regularCookies.find((c) => c.name === "auth_token"); + + if (regularAuthCookie) { + const response = await request.get(`${getBackendUrl()}/api/admin/pricing`, { + headers: { + Cookie: `auth_token=${regularAuthCookie.value}`, + }, + }); + expect(response.status()).toBe(403); + } + }); + + test("admin can update pricing via API with validation", async ({ page, request }) => { + await clearAuth(page); + await loginUser(page, ADMIN_USER.email, ADMIN_USER.password); + const cookies = await page.context().cookies(); + const authCookie = cookies.find((c) => c.name === "auth_token"); + + if (authCookie) { + // First get current config + const getResponse = await request.get(`${getBackendUrl()}/api/admin/pricing`, { + headers: { + Cookie: `auth_token=${authCookie.value}`, + }, + }); + const currentConfig = await getResponse.json(); + + // Update with valid values + const updateResponse = await request.put(`${getBackendUrl()}/api/admin/pricing`, { + headers: { + Cookie: `auth_token=${authCookie.value}`, + "Content-Type": "application/json", + }, + data: { + premium_buy: 6, + premium_sell: 6, + small_trade_threshold_eur: 20000, + small_trade_extra_premium: 2, + eur_min_buy: 10000, + eur_max_buy: 300000, + eur_min_sell: 10000, + eur_max_sell: 300000, + }, + }); + expect(updateResponse.status()).toBe(200); + const updatedData = await updateResponse.json(); + expect(updatedData.premium_buy).toBe(6); + + // Test validation - invalid premium (> 100) + const invalidResponse = await request.put(`${getBackendUrl()}/api/admin/pricing`, { + headers: { + Cookie: `auth_token=${authCookie.value}`, + "Content-Type": "application/json", + }, + data: { + premium_buy: 150, // Invalid + premium_sell: 6, + small_trade_threshold_eur: 20000, + small_trade_extra_premium: 2, + eur_min_buy: 10000, + eur_max_buy: 300000, + eur_min_sell: 10000, + eur_max_sell: 300000, + }, + }); + expect(invalidResponse.status()).toBe(422); + + // Restore original values + await request.put(`${getBackendUrl()}/api/admin/pricing`, { + headers: { + Cookie: `auth_token=${authCookie.value}`, + "Content-Type": "application/json", + }, + data: currentConfig, + }); + } + }); +});