- Next.js 14+ with App Router and TypeScript - Tailwind CSS and ShadCN UI styling - Zustand state management - Dexie.js for IndexedDB (local-first data) - Auth.js v5 for authentication - BMAD framework integration Co-Authored-By: Claude <noreply@anthropic.com>
87 lines
3.8 KiB
TypeScript
87 lines
3.8 KiB
TypeScript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Epic 4: Power User Settings (BYOD)', () => {
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
// Clear storage to ensure clean state
|
|
await page.goto('/');
|
|
await page.evaluate(() => localStorage.clear());
|
|
|
|
// Mock API responses for validation
|
|
await page.route('**/chat/completions', async route => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ choices: [{ message: { content: 'mock success' } }] })
|
|
});
|
|
});
|
|
});
|
|
|
|
test('P0: Provider Switching Configuration', async ({ page }) => {
|
|
// Navigate to settings
|
|
await page.goto('/settings');
|
|
await expect(page).toHaveURL(/.*settings/);
|
|
|
|
// 1. Add First Provider
|
|
await page.getByRole('button', { name: 'Add Provider', exact: true }).click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Fill Provider 1
|
|
await page.getByRole('textbox', { name: /Provider Name/i }).fill('Mock Provider 1');
|
|
await page.getByRole('textbox', { name: /Base URL/i }).fill('https://mock-provider-1.com/v1');
|
|
await page.getByRole('textbox', { name: /API Key/i }).fill('sk-key-1');
|
|
await page.getByRole('textbox', { name: /Model Name/i }).fill('model-1');
|
|
await page.getByRole('button', { name: /Save/i }).click();
|
|
|
|
// Verify Modal Closes (implicit success check)
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
|
|
// 2. Add Second Provider (Switching Test)
|
|
await page.getByRole('button', { name: 'Add Provider', exact: true }).click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Fill Provider 2
|
|
await page.getByRole('textbox', { name: /Provider Name/i }).fill('Mock Provider 2');
|
|
await page.getByRole('textbox', { name: /Base URL/i }).fill('https://mock-provider-2.com/v1');
|
|
await page.getByRole('textbox', { name: /API Key/i }).fill('sk-key-2');
|
|
await page.getByRole('textbox', { name: /Model Name/i }).fill('model-2');
|
|
await page.getByRole('button', { name: /Save/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
|
|
// 3. Verify Local Storage has the LATEST active provider (Model 2)
|
|
const settings = await page.evaluate(() => localStorage);
|
|
const storageString = JSON.stringify(settings);
|
|
|
|
console.log('Storage:', storageString);
|
|
|
|
expect(storageString).toContain('https://mock-provider-2.com/v1');
|
|
expect(storageString).toContain('model-2');
|
|
});
|
|
|
|
test('P0: Key Storage Security (Obfuscation)', async ({ page }) => {
|
|
await page.goto('/settings');
|
|
const secretKey = 'sk-secret-key-12345';
|
|
|
|
// Open Modal
|
|
await page.getByRole('button', { name: 'Add Provider', exact: true }).click();
|
|
|
|
// Fill Sensitive Data
|
|
await page.getByRole('textbox', { name: /Provider Name/i }).fill('Security Test');
|
|
await page.getByRole('textbox', { name: /Base URL/i }).fill('https://api.openai.com/v1');
|
|
await page.getByRole('textbox', { name: /API Key/i }).fill(secretKey);
|
|
await page.getByRole('textbox', { name: /Model Name/i }).fill('gpt-4');
|
|
await page.getByRole('button', { name: /Save/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
|
|
// Verify key is NOT stored in plain text
|
|
const settings = await page.evaluate(() => localStorage);
|
|
const storageValues = Object.values(settings).join('');
|
|
|
|
// The raw key should NOT be found exactly as entered if obfuscation works
|
|
// Note: If this fails, it means Security P0 Failed (Critical Issue)
|
|
expect(storageValues).not.toContain(secretKey);
|
|
});
|
|
|
|
});
|