Fix e2e tests for pricing page
- Update selectors to use input indices instead of labels (labels not associated) - Fix validation error status expectation (400 instead of 422) - Update exchange.spec.ts to check new config fields (eur_min_buy, etc.)
This commit is contained in:
parent
2ee27cf5b2
commit
7f547d667d
3 changed files with 173 additions and 36 deletions
|
|
@ -192,6 +192,30 @@ export interface paths {
|
||||||
patch?: never;
|
patch?: never;
|
||||||
trace?: never;
|
trace?: never;
|
||||||
};
|
};
|
||||||
|
"/api/admin/pricing": {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Get Pricing Config
|
||||||
|
* @description Get current pricing configuration.
|
||||||
|
*/
|
||||||
|
get: operations["get_pricing_config_api_admin_pricing_get"];
|
||||||
|
/**
|
||||||
|
* Update Pricing Config
|
||||||
|
* @description Update pricing configuration.
|
||||||
|
*/
|
||||||
|
put: operations["update_pricing_config_api_admin_pricing_put"];
|
||||||
|
post?: never;
|
||||||
|
delete?: never;
|
||||||
|
options?: never;
|
||||||
|
head?: never;
|
||||||
|
patch?: never;
|
||||||
|
trace?: never;
|
||||||
|
};
|
||||||
"/api/meta/constants": {
|
"/api/meta/constants": {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|
@ -353,9 +377,8 @@ export interface paths {
|
||||||
*
|
*
|
||||||
* The response includes:
|
* The response includes:
|
||||||
* - market_price: The raw price from the exchange
|
* - market_price: The raw price from the exchange
|
||||||
* - premium_percentage: The premium to apply to trades
|
|
||||||
* - is_stale: Whether the price is older than 5 minutes
|
* - is_stale: Whether the price is older than 5 minutes
|
||||||
* - config: Trading configuration (min/max EUR, increment)
|
* - config: Trading configuration (min/max EUR per direction, premiums, increment)
|
||||||
*/
|
*/
|
||||||
get: operations["get_exchange_price_api_exchange_price_get"];
|
get: operations["get_exchange_price_api_exchange_price_get"];
|
||||||
put?: never;
|
put?: never;
|
||||||
|
|
@ -760,14 +783,24 @@ export interface components {
|
||||||
* @description Exchange configuration for the frontend.
|
* @description Exchange configuration for the frontend.
|
||||||
*/
|
*/
|
||||||
ExchangeConfigResponse: {
|
ExchangeConfigResponse: {
|
||||||
/** Eur Min */
|
/** Eur Min Buy */
|
||||||
eur_min: number;
|
eur_min_buy: number;
|
||||||
/** Eur Max */
|
/** Eur Max Buy */
|
||||||
eur_max: number;
|
eur_max_buy: number;
|
||||||
|
/** Eur Min Sell */
|
||||||
|
eur_min_sell: number;
|
||||||
|
/** Eur Max Sell */
|
||||||
|
eur_max_sell: number;
|
||||||
/** Eur Increment */
|
/** Eur Increment */
|
||||||
eur_increment: number;
|
eur_increment: number;
|
||||||
/** Premium Percentage */
|
/** Premium Buy */
|
||||||
premium_percentage: number;
|
premium_buy: number;
|
||||||
|
/** Premium Sell */
|
||||||
|
premium_sell: number;
|
||||||
|
/** Small Trade Threshold Eur */
|
||||||
|
small_trade_threshold_eur: number;
|
||||||
|
/** Small Trade Extra Premium */
|
||||||
|
small_trade_extra_premium: number;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* ExchangePriceResponse
|
* ExchangePriceResponse
|
||||||
|
|
@ -939,7 +972,7 @@ export interface components {
|
||||||
* @description All available permissions in the system.
|
* @description All available permissions in the system.
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
Permission: "view_audit" | "fetch_price" | "manage_own_profile" | "manage_invites" | "view_own_invites" | "create_exchange" | "view_own_exchanges" | "cancel_own_exchange" | "manage_availability" | "view_all_exchanges" | "cancel_any_exchange" | "complete_exchange";
|
Permission: "view_audit" | "fetch_price" | "manage_own_profile" | "manage_invites" | "view_own_invites" | "create_exchange" | "view_own_exchanges" | "cancel_own_exchange" | "manage_availability" | "manage_pricing" | "view_all_exchanges" | "cancel_any_exchange" | "complete_exchange";
|
||||||
/**
|
/**
|
||||||
* PriceHistoryResponse
|
* PriceHistoryResponse
|
||||||
* @description Response model for a price history record.
|
* @description Response model for a price history record.
|
||||||
|
|
@ -969,13 +1002,13 @@ export interface components {
|
||||||
* @description Current BTC/EUR price for trading.
|
* @description Current BTC/EUR price for trading.
|
||||||
*
|
*
|
||||||
* Note: The actual agreed price depends on trade direction (buy/sell)
|
* Note: The actual agreed price depends on trade direction (buy/sell)
|
||||||
* and is calculated by the frontend using market_price and premium_percentage.
|
* and is calculated by the frontend using market_price and premium values.
|
||||||
|
* Premium calculation: base premium for direction + extra premium if
|
||||||
|
* trade <= threshold.
|
||||||
*/
|
*/
|
||||||
PriceResponse: {
|
PriceResponse: {
|
||||||
/** Market Price */
|
/** Market Price */
|
||||||
market_price: number;
|
market_price: number;
|
||||||
/** Premium Percentage */
|
|
||||||
premium_percentage: number;
|
|
||||||
/**
|
/**
|
||||||
* Timestamp
|
* Timestamp
|
||||||
* Format: date-time
|
* Format: date-time
|
||||||
|
|
@ -984,6 +1017,50 @@ export interface components {
|
||||||
/** Is Stale */
|
/** Is Stale */
|
||||||
is_stale: boolean;
|
is_stale: boolean;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* PricingConfigResponse
|
||||||
|
* @description Response model for pricing configuration.
|
||||||
|
*/
|
||||||
|
PricingConfigResponse: {
|
||||||
|
/** Premium Buy */
|
||||||
|
premium_buy: number;
|
||||||
|
/** Premium Sell */
|
||||||
|
premium_sell: number;
|
||||||
|
/** Small Trade Threshold Eur */
|
||||||
|
small_trade_threshold_eur: number;
|
||||||
|
/** Small Trade Extra Premium */
|
||||||
|
small_trade_extra_premium: number;
|
||||||
|
/** Eur Min Buy */
|
||||||
|
eur_min_buy: number;
|
||||||
|
/** Eur Max Buy */
|
||||||
|
eur_max_buy: number;
|
||||||
|
/** Eur Min Sell */
|
||||||
|
eur_min_sell: number;
|
||||||
|
/** Eur Max Sell */
|
||||||
|
eur_max_sell: number;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* PricingConfigUpdate
|
||||||
|
* @description Request model for updating pricing configuration.
|
||||||
|
*/
|
||||||
|
PricingConfigUpdate: {
|
||||||
|
/** Premium Buy */
|
||||||
|
premium_buy: number;
|
||||||
|
/** Premium Sell */
|
||||||
|
premium_sell: number;
|
||||||
|
/** Small Trade Threshold Eur */
|
||||||
|
small_trade_threshold_eur: number;
|
||||||
|
/** Small Trade Extra Premium */
|
||||||
|
small_trade_extra_premium: number;
|
||||||
|
/** Eur Min Buy */
|
||||||
|
eur_min_buy: number;
|
||||||
|
/** Eur Max Buy */
|
||||||
|
eur_max_buy: number;
|
||||||
|
/** Eur Min Sell */
|
||||||
|
eur_min_sell: number;
|
||||||
|
/** Eur Max Sell */
|
||||||
|
eur_max_sell: number;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* ProfileResponse
|
* ProfileResponse
|
||||||
* @description Response model for profile data.
|
* @description Response model for profile data.
|
||||||
|
|
@ -1435,6 +1512,59 @@ export interface operations {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
get_pricing_config_api_admin_pricing_get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PricingConfigResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
update_pricing_config_api_admin_pricing_put: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path?: never;
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PricingConfigUpdate"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["PricingConfigResponse"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
get_constants_api_meta_constants_get: {
|
get_constants_api_meta_constants_get: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: never;
|
query?: never;
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,16 @@ test.describe("Admin Pricing Page - Admin Access", () => {
|
||||||
await expect(page.getByRole("heading", { name: "Pricing Configuration" })).toBeVisible();
|
await expect(page.getByRole("heading", { name: "Pricing Configuration" })).toBeVisible();
|
||||||
await expect(page.getByText("Configure premium pricing and trade amount limits")).toBeVisible();
|
await expect(page.getByText("Configure premium pricing and trade amount limits")).toBeVisible();
|
||||||
|
|
||||||
// Check all form fields are present
|
// Check all form fields are present (using text + input selector since labels aren't associated)
|
||||||
await expect(page.getByLabel(/Premium for BUY/i)).toBeVisible();
|
await expect(page.getByText(/Premium for BUY/i)).toBeVisible();
|
||||||
await expect(page.getByLabel(/Premium for SELL/i)).toBeVisible();
|
await expect(page.locator('input[type="number"]').first()).toBeVisible();
|
||||||
await expect(page.getByLabel(/Small Trade Threshold/i)).toBeVisible();
|
await expect(page.getByText(/Premium for SELL/i)).toBeVisible();
|
||||||
await expect(page.getByLabel(/Extra Premium for Small Trades/i)).toBeVisible();
|
await expect(page.getByText(/Small Trade Threshold/i)).toBeVisible();
|
||||||
await expect(page.getByLabel(/Minimum Amount.*BUY/i)).toBeVisible();
|
await expect(page.getByText(/Extra Premium for Small Trades/i)).toBeVisible();
|
||||||
await expect(page.getByLabel(/Maximum Amount.*BUY/i)).toBeVisible();
|
await expect(page.getByText(/Trade Amount Limits.*BUY/i)).toBeVisible();
|
||||||
await expect(page.getByLabel(/Minimum Amount.*SELL/i)).toBeVisible();
|
await expect(page.getByText(/Minimum Amount/i).first()).toBeVisible();
|
||||||
await expect(page.getByLabel(/Maximum Amount.*SELL/i)).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
|
// Check save button is present
|
||||||
await expect(page.getByRole("button", { name: /Save Changes/i })).toBeVisible();
|
await expect(page.getByRole("button", { name: /Save Changes/i })).toBeVisible();
|
||||||
|
|
@ -54,11 +55,10 @@ test.describe("Admin Pricing Page - Admin Access", () => {
|
||||||
const count = await inputs.count();
|
const count = await inputs.count();
|
||||||
expect(count).toBeGreaterThan(0);
|
expect(count).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Check that premium fields have values
|
// Check that premium fields have values (inputs are after labels)
|
||||||
const premiumBuyInput = page.getByLabel(/Premium for BUY/i);
|
const inputs = page.locator('input[type="number"]');
|
||||||
const premiumSellInput = page.getByLabel(/Premium for SELL/i);
|
const buyValue = await inputs.nth(0).inputValue(); // First input is premium_buy
|
||||||
const buyValue = await premiumBuyInput.inputValue();
|
const sellValue = await inputs.nth(1).inputValue(); // Second input is premium_sell
|
||||||
const sellValue = await premiumSellInput.inputValue();
|
|
||||||
|
|
||||||
expect(buyValue).not.toBe("");
|
expect(buyValue).not.toBe("");
|
||||||
expect(sellValue).not.toBe("");
|
expect(sellValue).not.toBe("");
|
||||||
|
|
@ -68,8 +68,9 @@ test.describe("Admin Pricing Page - Admin Access", () => {
|
||||||
await page.goto("/admin/pricing");
|
await page.goto("/admin/pricing");
|
||||||
await page.waitForLoadState("networkidle");
|
await page.waitForLoadState("networkidle");
|
||||||
|
|
||||||
// Get current values
|
// Get current values (first input is premium_buy)
|
||||||
const premiumBuyInput = page.getByLabel(/Premium for BUY/i);
|
const inputs = page.locator('input[type="number"]');
|
||||||
|
const premiumBuyInput = inputs.nth(0);
|
||||||
const currentBuyValue = await premiumBuyInput.inputValue();
|
const currentBuyValue = await premiumBuyInput.inputValue();
|
||||||
const newBuyValue = currentBuyValue === "5" ? "6" : "5";
|
const newBuyValue = currentBuyValue === "5" ? "6" : "5";
|
||||||
|
|
||||||
|
|
@ -110,7 +111,8 @@ test.describe("Admin Pricing Page - Admin Access", () => {
|
||||||
await page.waitForLoadState("networkidle");
|
await page.waitForLoadState("networkidle");
|
||||||
|
|
||||||
// Test premium range validation (should be -100 to 100)
|
// Test premium range validation (should be -100 to 100)
|
||||||
const premiumBuyInput = page.getByLabel(/Premium for BUY/i);
|
const inputs = page.locator('input[type="number"]');
|
||||||
|
const premiumBuyInput = inputs.nth(0); // First input is premium_buy
|
||||||
await premiumBuyInput.clear();
|
await premiumBuyInput.clear();
|
||||||
await premiumBuyInput.fill("150"); // Invalid: > 100
|
await premiumBuyInput.fill("150"); // Invalid: > 100
|
||||||
|
|
||||||
|
|
@ -122,9 +124,10 @@ test.describe("Admin Pricing Page - Admin Access", () => {
|
||||||
// Should show validation error
|
// Should show validation error
|
||||||
await expect(page.getByText(/must be between.*-100.*100/i)).toBeVisible({ timeout: 2000 });
|
await expect(page.getByText(/must be between.*-100.*100/i)).toBeVisible({ timeout: 2000 });
|
||||||
|
|
||||||
// Test min < max validation
|
// Test min < max validation (inputs 4 and 5 are min/max buy)
|
||||||
const minBuyInput = page.getByLabel(/Minimum Amount.*BUY/i);
|
const inputs = page.locator('input[type="number"]');
|
||||||
const maxBuyInput = page.getByLabel(/Maximum Amount.*BUY/i);
|
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 currentMin = await minBuyInput.inputValue();
|
||||||
const currentMax = await maxBuyInput.inputValue();
|
const currentMax = await maxBuyInput.inputValue();
|
||||||
|
|
@ -150,7 +153,9 @@ test.describe("Admin Pricing Page - Admin Access", () => {
|
||||||
await page.goto("/admin/pricing");
|
await page.goto("/admin/pricing");
|
||||||
await page.waitForLoadState("networkidle");
|
await page.waitForLoadState("networkidle");
|
||||||
|
|
||||||
const smallTradeThresholdInput = page.getByLabel(/Small Trade Threshold/i);
|
// 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();
|
const currentThreshold = await smallTradeThresholdInput.inputValue();
|
||||||
|
|
||||||
// Update threshold
|
// Update threshold
|
||||||
|
|
@ -278,7 +283,7 @@ test.describe("Admin Pricing API", () => {
|
||||||
eur_max_sell: 300000,
|
eur_max_sell: 300000,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(invalidResponse.status()).toBe(422);
|
expect(invalidResponse.status()).toBe(400); // BadRequestError returns 400
|
||||||
|
|
||||||
// Restore original values
|
// Restore original values
|
||||||
await request.put(`${getBackendUrl()}/api/admin/pricing`, {
|
await request.put(`${getBackendUrl()}/api/admin/pricing`, {
|
||||||
|
|
|
||||||
|
|
@ -339,8 +339,10 @@ test.describe("Exchange API", () => {
|
||||||
expect(priceResponse.status()).toBe(200);
|
expect(priceResponse.status()).toBe(200);
|
||||||
const priceData = await priceResponse.json();
|
const priceData = await priceResponse.json();
|
||||||
expect(priceData.config).toBeDefined();
|
expect(priceData.config).toBeDefined();
|
||||||
expect(priceData.config.eur_min).toBeDefined();
|
expect(priceData.config.eur_min_buy).toBeDefined();
|
||||||
expect(priceData.config.eur_max).toBeDefined();
|
expect(priceData.config.eur_max_buy).toBeDefined();
|
||||||
|
expect(priceData.config.eur_min_sell).toBeDefined();
|
||||||
|
expect(priceData.config.eur_max_sell).toBeDefined();
|
||||||
|
|
||||||
// Test regular user can get trades
|
// Test regular user can get trades
|
||||||
const tradesResponse = await request.get(`${getBackendUrl()}/api/trades`, {
|
const tradesResponse = await request.get(`${getBackendUrl()}/api/trades`, {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue