mirror of
https://github.com/velocitatem/PHANTOM.git
synced 2026-06-01 00:53:36 +00:00
Add dynamic pricing E2E test suite with Playwright
Implement comprehensive E2E tests to validate the surge pricing pipeline: - Test SimpleSurgePricer with configurable thresholds (high=3, surge=1.5x) - Verify discount pricing when demand is below low_threshold - Test multi-product differential pricing based on demand signals - Validate price propagation from pipeline through Redis to API Test infrastructure: - Playwright configuration with custom fixtures - Python pipeline worker for direct test execution (bypasses Airflow) - API client for event ingestion and price verification - Event generator for creating realistic interaction sequences - docker-compose.e2e.yml with minimal services for testing
This commit is contained in:
143
e2e/lib/fixtures.ts
Normal file
143
e2e/lib/fixtures.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
import { PhantomApiClient, apiClient } from './api-client';
|
||||
import { EventGenerator, TestProducts } from './event-generator';
|
||||
import { runPricingPipeline, waitForPriceUpdate, PipelineResult } from './pipeline-runner';
|
||||
import { testConfig } from '../playwright.config';
|
||||
|
||||
/**
|
||||
* Extended test fixtures for PHANTOM E2E tests
|
||||
*/
|
||||
export interface PhantomTestFixtures {
|
||||
/** API client for interacting with PHANTOM services */
|
||||
api: PhantomApiClient;
|
||||
|
||||
/** Event generator for creating test events */
|
||||
events: EventGenerator;
|
||||
|
||||
/** Run the pricing pipeline and wait for updates */
|
||||
triggerPriceUpdate: (options?: {
|
||||
storeMode?: 'hotel' | 'airline';
|
||||
highThreshold?: number;
|
||||
lowThreshold?: number;
|
||||
surgeMultiplier?: number;
|
||||
discountMultiplier?: number;
|
||||
}) => Promise<PipelineResult>;
|
||||
|
||||
/** Wait for a specific price condition */
|
||||
waitForPrice: (
|
||||
productId: string,
|
||||
condition: (price: number, basePrice: number) => boolean,
|
||||
storeMode?: 'hotel' | 'airline'
|
||||
) => Promise<{ price: number; basePrice: number; markup: number }>;
|
||||
|
||||
/** Test configuration */
|
||||
config: typeof testConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom test with PHANTOM fixtures
|
||||
*/
|
||||
export const test = base.extend<PhantomTestFixtures>({
|
||||
api: async ({}, use) => {
|
||||
await use(apiClient);
|
||||
},
|
||||
|
||||
events: async ({}, use) => {
|
||||
// Create a new event generator with a fresh session for each test
|
||||
const generator = new EventGenerator({
|
||||
storeMode: 'hotel',
|
||||
});
|
||||
await use(generator);
|
||||
},
|
||||
|
||||
triggerPriceUpdate: async ({}, use) => {
|
||||
const trigger = async (options = {}) => {
|
||||
const result = await runPricingPipeline({
|
||||
storeMode: 'hotel',
|
||||
highThreshold: testConfig.pricing.highThreshold,
|
||||
lowThreshold: testConfig.pricing.lowThreshold,
|
||||
surgeMultiplier: testConfig.pricing.surgeMultiplier,
|
||||
discountMultiplier: testConfig.pricing.discountMultiplier,
|
||||
...options,
|
||||
});
|
||||
|
||||
// Wait a moment for Redis to be fully updated
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
await use(trigger);
|
||||
},
|
||||
|
||||
waitForPrice: async ({ api }, use) => {
|
||||
const waiter = async (
|
||||
productId: string,
|
||||
condition: (price: number, basePrice: number) => boolean,
|
||||
storeMode: 'hotel' | 'airline' = 'hotel'
|
||||
) => {
|
||||
let lastPrice = 0;
|
||||
let lastBasePrice = 0;
|
||||
|
||||
const updated = await waitForPriceUpdate(async () => {
|
||||
const priceResponse = await api.getPrice(storeMode, productId);
|
||||
lastPrice = priceResponse.price;
|
||||
lastBasePrice = priceResponse.base_price;
|
||||
return condition(priceResponse.price, priceResponse.base_price);
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
throw new Error(
|
||||
`Price condition not met within timeout. Last price: ${lastPrice}, base: ${lastBasePrice}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
price: lastPrice,
|
||||
basePrice: lastBasePrice,
|
||||
markup: lastPrice / lastBasePrice,
|
||||
};
|
||||
};
|
||||
|
||||
await use(waiter);
|
||||
},
|
||||
|
||||
config: async ({}, use) => {
|
||||
await use(testConfig);
|
||||
},
|
||||
});
|
||||
|
||||
export { expect };
|
||||
|
||||
/**
|
||||
* Helper assertions for pricing tests
|
||||
*/
|
||||
export const PricingAssertions = {
|
||||
/**
|
||||
* Assert that a price has surge markup applied
|
||||
*/
|
||||
isSurged: (price: number, basePrice: number, expectedMultiplier: number, tolerance = 0.01) => {
|
||||
const actualMarkup = price / basePrice;
|
||||
const minExpected = expectedMultiplier * (1 - tolerance);
|
||||
const maxExpected = expectedMultiplier * (1 + tolerance);
|
||||
return actualMarkup >= minExpected && actualMarkup <= maxExpected;
|
||||
},
|
||||
|
||||
/**
|
||||
* Assert that a price has discount applied
|
||||
*/
|
||||
isDiscounted: (price: number, basePrice: number, expectedMultiplier: number, tolerance = 0.01) => {
|
||||
const actualMarkup = price / basePrice;
|
||||
const minExpected = expectedMultiplier * (1 - tolerance);
|
||||
const maxExpected = expectedMultiplier * (1 + tolerance);
|
||||
return actualMarkup >= minExpected && actualMarkup <= maxExpected;
|
||||
},
|
||||
|
||||
/**
|
||||
* Assert that a price is at base (no surge/discount)
|
||||
*/
|
||||
isBase: (price: number, basePrice: number, tolerance = 0.01) => {
|
||||
const actualMarkup = price / basePrice;
|
||||
return actualMarkup >= (1 - tolerance) && actualMarkup <= (1 + tolerance);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user