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 (using text + input selector since labels aren't associated) await expect(page.getByText(/Premium for BUY/i)).toBeVisible(); await expect(page.locator('input[type="number"]').first()).toBeVisible(); await expect(page.getByText(/Premium for SELL/i)).toBeVisible(); await expect(page.getByText(/Small Trade Threshold/i)).toBeVisible(); await expect(page.getByText(/Extra Premium for Small Trades/i)).toBeVisible(); await expect(page.getByText(/Trade Amount Limits.*BUY/i)).toBeVisible(); await expect(page.getByText(/Minimum Amount/i).first()).toBeVisible(); await expect(page.getByText(/Maximum Amount/i).first()).toBeVisible(); await expect(page.getByText(/Trade Amount Limits.*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 (inputs are after labels) const inputs = page.locator('input[type="number"]'); const buyValue = await inputs.nth(0).inputValue(); // First input is premium_buy const sellValue = await inputs.nth(1).inputValue(); // Second input is premium_sell 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 (first input is premium_buy) const inputs = page.locator('input[type="number"]'); const premiumBuyInput = inputs.nth(0); 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 inputs = page.locator('input[type="number"]'); const premiumBuyInput = inputs.nth(0); // First input is premium_buy 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 (inputs 4 and 5 are min/max buy) const inputs = page.locator('input[type="number"]'); const minBuyInput = inputs.nth(4); // Min buy is 5th input (after 4 premium/threshold inputs) const maxBuyInput = inputs.nth(5); // Max buy is 6th input 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"); // Small trade threshold is the 3rd input (after premium_buy and premium_sell) const inputs = page.locator('input[type="number"]'); const smallTradeThresholdInput = inputs.nth(2); 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(400); // BadRequestError returns 400 // Restore original values await request.put(`${getBackendUrl()}/api/admin/pricing`, { headers: { Cookie: `auth_token=${authCookie.value}`, "Content-Type": "application/json", }, data: currentConfig, }); } }); });