375 lines
13 KiB
TypeScript
375 lines
13 KiB
TypeScript
import { expect, test } from '../../core/fixtures/base.fixture'
|
|
import { createRoutingRuleData } from './routing-rules.data'
|
|
|
|
// Track created rules for cleanup
|
|
const createdRules: string[] = []
|
|
|
|
test.describe('Routing Rules', () => {
|
|
test.beforeEach(async ({ routingRulesPage }) => {
|
|
await routingRulesPage.goto()
|
|
})
|
|
|
|
test.afterEach(async ({ routingRulesPage }) => {
|
|
// Clean up any rules created during tests
|
|
for (const ruleName of [...createdRules]) {
|
|
try {
|
|
const exists = await routingRulesPage.ruleExists(ruleName)
|
|
if (exists) {
|
|
await routingRulesPage.deleteRoutingRule(ruleName)
|
|
}
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
createdRules.length = 0
|
|
})
|
|
|
|
test.describe('Routing Rule Creation', () => {
|
|
test('should display create routing rule button', async ({ routingRulesPage }) => {
|
|
await expect(routingRulesPage.createBtn).toBeVisible()
|
|
})
|
|
|
|
test('should open routing rule creation sheet', async ({ routingRulesPage }) => {
|
|
await routingRulesPage.createBtn.click()
|
|
|
|
await expect(routingRulesPage.sheet).toBeVisible({ timeout: 5000 })
|
|
await expect(routingRulesPage.nameInput).toBeVisible()
|
|
})
|
|
|
|
test('should create a basic routing rule', async ({ routingRulesPage }) => {
|
|
// Note: CEL expression is auto-generated from the visual Rule Builder
|
|
// An empty builder means the rule applies to all requests
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Basic Rule ${Date.now()}`,
|
|
})
|
|
createdRules.push(ruleData.name)
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
const exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(true)
|
|
})
|
|
|
|
test('should create routing rule with description', async ({ routingRulesPage }) => {
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Described Rule ${Date.now()}`,
|
|
description: 'A rule with a detailed description for testing',
|
|
})
|
|
createdRules.push(ruleData.name)
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
const exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(true)
|
|
})
|
|
|
|
test('should create disabled routing rule', async ({ routingRulesPage }) => {
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Disabled Rule ${Date.now()}`,
|
|
enabled: false,
|
|
})
|
|
createdRules.push(ruleData.name)
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
const exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(true)
|
|
})
|
|
|
|
test('should cancel routing rule creation', async ({ routingRulesPage }) => {
|
|
await routingRulesPage.createBtn.click()
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
|
|
const testName = `Cancelled Rule ${Date.now()}`
|
|
await routingRulesPage.nameInput.fill(testName)
|
|
|
|
await routingRulesPage.cancelRule()
|
|
|
|
const exists = await routingRulesPage.ruleExists(testName)
|
|
expect(exists).toBe(false)
|
|
})
|
|
})
|
|
|
|
test.describe('Routing Rule Management', () => {
|
|
test('should edit routing rule', async ({ routingRulesPage }) => {
|
|
// Create a rule first
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Edit Test Rule ${Date.now()}`,
|
|
})
|
|
createdRules.push(ruleData.name)
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
// Edit it - change description
|
|
await routingRulesPage.editRoutingRule(ruleData.name, {
|
|
description: 'Updated description',
|
|
})
|
|
|
|
// Verify description was saved and displayed in table
|
|
const description = await routingRulesPage.getRuleDescription(ruleData.name)
|
|
expect(description).toContain('Updated description')
|
|
})
|
|
|
|
test('should delete routing rule', async ({ routingRulesPage }) => {
|
|
// Create a rule first
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Delete Test Rule ${Date.now()}`,
|
|
})
|
|
// Don't add to createdRules since we're testing delete
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
// Verify it exists
|
|
let exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(true)
|
|
|
|
// Delete it
|
|
await routingRulesPage.deleteRoutingRule(ruleData.name)
|
|
|
|
// Verify it's gone
|
|
exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(false)
|
|
})
|
|
|
|
test('should toggle rule enabled state', async ({ routingRulesPage }) => {
|
|
// Create a rule first
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Toggle Test Rule ${Date.now()}`,
|
|
enabled: true,
|
|
})
|
|
createdRules.push(ruleData.name)
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
// Toggle it
|
|
await routingRulesPage.toggleRuleEnabled(ruleData.name)
|
|
|
|
// Verify it still exists
|
|
const exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(true)
|
|
})
|
|
})
|
|
|
|
test.describe('Form Validation', () => {
|
|
test('should require name for routing rule', async ({ routingRulesPage }) => {
|
|
await routingRulesPage.createBtn.click()
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
|
|
// Try to save without name
|
|
await routingRulesPage.saveBtn.click()
|
|
|
|
// Form should still be visible (not submitted)
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
|
|
await routingRulesPage.cancelRule()
|
|
})
|
|
})
|
|
|
|
test.describe('Table Display', () => {
|
|
test('should display routing rules table', async ({ routingRulesPage }) => {
|
|
// With 0 rules the view shows empty state (no table); with 1+ rules it shows the table
|
|
const count = await routingRulesPage.getRuleCount()
|
|
if (count === 0) {
|
|
await expect(routingRulesPage.emptyState).toBeVisible()
|
|
await expect(routingRulesPage.table).not.toBeVisible()
|
|
} else {
|
|
await expect(routingRulesPage.table).toBeVisible()
|
|
await expect(routingRulesPage.emptyState).not.toBeVisible()
|
|
}
|
|
})
|
|
|
|
test('should show empty state when no rules', async ({ routingRulesPage }) => {
|
|
const count = await routingRulesPage.getRuleCount()
|
|
if (count === 0) {
|
|
await expect(routingRulesPage.emptyState).toBeVisible()
|
|
}
|
|
// When rules exist, getRuleCount > 0 is already implied by the condition
|
|
})
|
|
})
|
|
|
|
test.describe('Advanced Rule Features', () => {
|
|
test('should create rule with provider filter', async ({ routingRulesPage }) => {
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Provider Filter Rule ${Date.now()}`,
|
|
provider: 'openai', // Set target provider
|
|
})
|
|
createdRules.push(ruleData.name)
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
const exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(true)
|
|
})
|
|
|
|
test('should create rule with model filter', async ({ routingRulesPage }) => {
|
|
const ruleData = createRoutingRuleData({
|
|
name: `Model Filter Rule ${Date.now()}`,
|
|
provider: 'openai',
|
|
model: 'gpt-4',
|
|
})
|
|
createdRules.push(ruleData.name)
|
|
|
|
await routingRulesPage.createRoutingRule(ruleData)
|
|
|
|
const exists = await routingRulesPage.ruleExists(ruleData.name)
|
|
expect(exists).toBe(true)
|
|
})
|
|
|
|
test('should reorder rules by changing priority', async ({ routingRulesPage }) => {
|
|
// Create two rules with unique priorities (avoid fixed 500/600 so parallel workers don't collide)
|
|
const rule1 = createRoutingRuleData({ name: `Reorder Test Rule 1 ${Date.now()}` })
|
|
const rule2 = createRoutingRuleData({ name: `Reorder Test Rule 2 ${Date.now()}` })
|
|
createdRules.push(rule1.name, rule2.name)
|
|
|
|
await routingRulesPage.createRoutingRule(rule1)
|
|
await routingRulesPage.createRoutingRule(rule2)
|
|
|
|
// Change first rule's priority (edit to a new value to test reorder)
|
|
const newPriority = (rule1.priority! + 100) % 901
|
|
await routingRulesPage.editRoutingRule(rule1.name, { priority: newPriority })
|
|
|
|
// Verify priority was saved and displayed
|
|
const displayedPriority = await routingRulesPage.getRulePriority(rule1.name)
|
|
expect(displayedPriority).toBe(newPriority)
|
|
})
|
|
|
|
test('should create rule with virtual key scope', async ({ routingRulesPage }) => {
|
|
await routingRulesPage.createBtn.click()
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
|
|
const ruleName = `VK Scope Rule ${Date.now()}`
|
|
await routingRulesPage.nameInput.fill(ruleName)
|
|
|
|
// Try to set scope to virtual key
|
|
const scopeSelect = routingRulesPage.sheet.locator('[role="combobox"]').filter({ hasText: /Global|Scope/i }).first()
|
|
const scopeVisible = await scopeSelect.isVisible().catch(() => false)
|
|
|
|
if (scopeVisible) {
|
|
// Scope selection is available
|
|
await scopeSelect.click()
|
|
const vkOption = routingRulesPage.page.getByRole('option', { name: /Virtual Key/i })
|
|
const vkVisible = await vkOption.isVisible().catch(() => false)
|
|
|
|
if (vkVisible) {
|
|
await vkOption.click()
|
|
// Note: Would need to select a specific VK - for now just verify the option exists
|
|
}
|
|
}
|
|
|
|
// Cancel since we're just testing the UI
|
|
await routingRulesPage.cancelRule()
|
|
})
|
|
})
|
|
|
|
test.describe('Rule Builder and CEL Generation', () => {
|
|
test('should show CEL preview with "No rules defined" when empty', async ({ routingRulesPage }) => {
|
|
await routingRulesPage.createBtn.click()
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
await routingRulesPage.waitForSheetAnimation()
|
|
|
|
// Wait for rule builder to fully load
|
|
await routingRulesPage.waitForRuleBuilder()
|
|
|
|
// Get CEL expression - should show no rules message when empty
|
|
const celExpression = await routingRulesPage.getCelExpression()
|
|
expect(celExpression).toContain('No rules defined')
|
|
|
|
await routingRulesPage.cancelRule()
|
|
})
|
|
|
|
test('should add rule condition and update CEL preview', async ({ routingRulesPage }) => {
|
|
await routingRulesPage.createBtn.click()
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
await routingRulesPage.waitForSheetAnimation()
|
|
await routingRulesPage.waitForRuleBuilder()
|
|
|
|
// Fill required name
|
|
const ruleName = `CEL Test ${Date.now()}`
|
|
await routingRulesPage.nameInput.fill(ruleName)
|
|
createdRules.push(ruleName)
|
|
|
|
// Verify initial CEL is empty/no rules
|
|
const initialCel = await routingRulesPage.getCelExpression()
|
|
expect(initialCel).toContain('No rules defined')
|
|
|
|
// Add a rule condition
|
|
await routingRulesPage.clickAddRule()
|
|
|
|
// Wait for rule row to appear and CEL to update
|
|
await routingRulesPage.page.waitForTimeout(500)
|
|
|
|
// After adding a rule, CEL should no longer say "No rules defined"
|
|
// The default rule shows model == "" (empty model condition)
|
|
const celAfterAdd = await routingRulesPage.getCelExpression()
|
|
expect(celAfterAdd).not.toContain('No rules defined')
|
|
expect(celAfterAdd).toContain('model') // Default field is Model
|
|
|
|
await routingRulesPage.cancelRule()
|
|
})
|
|
|
|
test('should switch between AND and OR combinators', async ({ routingRulesPage }) => {
|
|
await routingRulesPage.createBtn.click()
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
await routingRulesPage.waitForSheetAnimation()
|
|
await routingRulesPage.waitForRuleBuilder()
|
|
|
|
// Fill required name
|
|
const ruleName = `CEL Combinator Test ${Date.now()}`
|
|
await routingRulesPage.nameInput.fill(ruleName)
|
|
createdRules.push(ruleName)
|
|
|
|
// Add two rule conditions to see the combinator in action
|
|
await routingRulesPage.clickAddRule()
|
|
await routingRulesPage.clickAddRule()
|
|
|
|
// Wait for rules to render
|
|
await routingRulesPage.page.waitForTimeout(500)
|
|
|
|
// Get CEL with default AND combinator
|
|
const celWithAnd = await routingRulesPage.getCelExpression()
|
|
// Default is AND - should have && operator
|
|
expect(celWithAnd).toContain('&&')
|
|
|
|
// Switch to OR
|
|
await routingRulesPage.setCombinator('or')
|
|
await routingRulesPage.page.waitForTimeout(300)
|
|
|
|
// Verify CEL now contains OR logic
|
|
const celWithOr = await routingRulesPage.getCelExpression()
|
|
expect(celWithOr).toContain('||')
|
|
|
|
await routingRulesPage.cancelRule()
|
|
})
|
|
|
|
test('should save rule with conditions successfully', async ({ routingRulesPage }) => {
|
|
const ruleName = `CEL Save Test ${Date.now()}`
|
|
createdRules.push(ruleName)
|
|
|
|
await routingRulesPage.createBtn.click()
|
|
await expect(routingRulesPage.sheet).toBeVisible()
|
|
await routingRulesPage.waitForSheetAnimation()
|
|
await routingRulesPage.waitForRuleBuilder()
|
|
|
|
// Fill name
|
|
await routingRulesPage.nameInput.fill(ruleName)
|
|
|
|
// Add a condition (default Model field with default operator)
|
|
await routingRulesPage.clickAddRule()
|
|
await routingRulesPage.page.waitForTimeout(500)
|
|
|
|
// Verify CEL was generated before saving
|
|
const celBeforeSave = await routingRulesPage.getCelExpression()
|
|
expect(celBeforeSave).not.toContain('No rules defined')
|
|
|
|
// Save the rule
|
|
await routingRulesPage.saveBtn.click()
|
|
await routingRulesPage.waitForSuccessToast()
|
|
await expect(routingRulesPage.sheet).not.toBeVisible({ timeout: 10000 })
|
|
|
|
// Verify rule was created
|
|
const exists = await routingRulesPage.ruleExists(ruleName)
|
|
expect(exists).toBe(true)
|
|
})
|
|
})
|
|
})
|