- Add 'Twilight Velvet' color palette to globals.css with OKLCH values - Update SettingsPage headers, cards, and dialogs to use semantic theme variables - Update HistoryCard, HistoryFeed, and DraftContent to support dark mode - Update ProviderSelector and ProviderList to use custom card background (#2A2A3D) - Add ThemeToggle component with improved visibility - Ensure consistent use of 'bg-card', 'text-foreground', and 'text-muted-foreground'
118 lines
5.5 KiB
TypeScript
118 lines
5.5 KiB
TypeScript
import { test, expect } from '../support/fixtures';
|
|
import { faker } from '@faker-js/faker';
|
|
|
|
test.describe('Settings Management (Story 4.1, 4.4)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/settings');
|
|
});
|
|
|
|
test('[P1] should configure a new LLM provider', async ({ page }) => {
|
|
// Mock validation request
|
|
await page.route('/api/llm', async route => {
|
|
const body = JSON.parse(route.request().postData() || '{}');
|
|
// Check if it's a validation request (has 'hello' message usually, see LLMService) or just success
|
|
await route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({ success: true, data: { text: 'Validation success' } })
|
|
});
|
|
});
|
|
|
|
// GIVEN: User is on settings page
|
|
const providerName = `Custom Provider ${faker.number.int({ min: 1000 })}`;
|
|
const baseUrl = faker.internet.url();
|
|
const modelName = 'gpt-4-custom';
|
|
const apiKey = 'sk-test-key-' + faker.string.alphanumeric(10);
|
|
|
|
// WHEN: User clicks Add New Provider
|
|
await page.getByRole('button', { name: /add new provider/i }).click();
|
|
|
|
// THEN: Dialog should open
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// WHEN: User fills in the form
|
|
await page.getByLabel('Provider Name').fill(providerName);
|
|
await page.getByLabel('Base URL').fill(baseUrl);
|
|
await page.getByLabel('Model Name').fill(modelName);
|
|
await page.getByPlaceholder('sk-...').fill(apiKey);
|
|
|
|
// WHEN: User saves (Button text depends on mode, usually "Save & Validate" or "Save as New Provider")
|
|
// "Save as New Provider" is likely for Add mode
|
|
await page.getByRole('button', { name: /save/i }).click();
|
|
|
|
// THEN: Dialog should close
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
|
|
// THEN: New provider should appear in the list
|
|
await expect(page.getByText(providerName)).toBeVisible();
|
|
await expect(page.getByText(modelName)).toBeVisible();
|
|
});
|
|
|
|
test('[P1] should switch active provider', async ({ page }) => {
|
|
// GIVEN: A provider exists (using the default one from store or we just add one)
|
|
// Since we don't inject store here (unless we want to refactor to do so),
|
|
// we might rely on default empty state and add one, OR we assume persistence from previous test if workers reused (not guaranteed).
|
|
// Best to add one first or inject state. Let's add one quickly via UI to be safe/independent.
|
|
|
|
const providerName = `Switch Test Provider ${faker.number.int()}`;
|
|
await page.getByRole('button', { name: /add new provider/i }).click();
|
|
await page.getByLabel('Provider Name').fill(providerName);
|
|
await page.getByLabel('Base URL').fill('https://api.example.com');
|
|
await page.getByLabel('Model Name').fill('gpt-test');
|
|
await page.getByPlaceholder('sk-...').fill('sk-test');
|
|
// Mock validation for this save too
|
|
await page.getByRole('button', { name: /save/i }).click();
|
|
|
|
// WHEN: User selects the new provider in the selector
|
|
// The selector uses radio behavior or clickable cards
|
|
await page.getByText(providerName).click();
|
|
|
|
// THEN: It should become active
|
|
// We check for the data-active attribute or visual indicator
|
|
// Based on test: closest('[data-active]')
|
|
const providerCard = page.getByText(providerName).locator('xpath=ancestor::*[contains(@data-active, "true") or contains(@data-active, "false")]').first();
|
|
await expect(providerCard).toHaveAttribute('data-active', 'true');
|
|
});
|
|
|
|
test('[P0] should enforce Key Storage Security (Obfuscation)', async ({ page }) => {
|
|
const secretKey = 'sk-secret-key-12345';
|
|
|
|
// Mock validation request
|
|
await page.route('/api/llm', async route => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
body: JSON.stringify({ success: true, data: { text: 'Validation success' } })
|
|
});
|
|
});
|
|
|
|
// Open Modal
|
|
await page.getByRole('button', { name: /add new provider/i }).click();
|
|
|
|
// Fill Sensitive Data
|
|
await page.getByLabel('Provider Name').fill('Security Test');
|
|
await page.getByLabel('Base URL').fill('https://api.openai.com/v1');
|
|
await page.getByLabel('Model Name').fill('gpt-4');
|
|
await page.getByPlaceholder('sk-...').fill(secretKey);
|
|
|
|
await page.getByRole('button', { name: /save/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeHidden();
|
|
|
|
// Verify key is NOT stored in plain text in localStorage
|
|
const settings = await page.evaluate(() => localStorage.getItem('test01-settings-storage'));
|
|
expect(settings).not.toBeNull();
|
|
expect(settings).not.toContain(secretKey); // Should be base64 encoded
|
|
});
|
|
|
|
test('[P2] should validate provider inputs', async ({ page }) => {
|
|
// WHEN: User clicks Add New Provider
|
|
await page.getByRole('button', { name: /add new provider/i }).click();
|
|
|
|
// WHEN: User tries to save empty form
|
|
await page.getByRole('button', { name: /save/i }).click();
|
|
|
|
// THEN: validation errors should appear (assuming HTML5 validation or UI errors)
|
|
// Since component uses Radix UI or similar, we might check for :invalid state or error messages
|
|
// For now, check that dialog is still open
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
});
|
|
});
|