feat: complete tRPC integration and fix platform UI issues

- Implement comprehensive tRPC setup with type-safe API
- Create tRPC routers for dashboard, admin, and auth endpoints
- Migrate frontend components to use tRPC client
- Fix platform dashboard Settings button functionality
- Add platform settings page with profile and security management
- Create OpenAI API mocking infrastructure for cost-safe testing
- Update tests to work with new tRPC architecture
- Sync database schema to fix AIBatchRequest table errors
This commit is contained in:
2025-07-11 15:37:53 +02:00
committed by Kaj Kowalski
parent f2a3d87636
commit fa7e815a3b
38 changed files with 4285 additions and 518 deletions

View File

@ -77,38 +77,48 @@ describe("Dashboard Components", () => {
it("should render chart with questions data", () => {
render(<TopQuestionsChart data={mockQuestions} />);
expect(screen.getByTestId("card")).toBeInTheDocument();
expect(screen.getByTestId("card-title")).toHaveTextContent("Top 5 Asked Questions");
expect(screen.getByText("How do I reset my password?")).toBeInTheDocument();
expect(screen.getByTestId("card-title")).toHaveTextContent(
"Top 5 Asked Questions"
);
expect(
screen.getByText("How do I reset my password?")
).toBeInTheDocument();
});
it("should render with custom title", () => {
render(<TopQuestionsChart data={mockQuestions} title="Custom Title" />);
expect(screen.getByTestId("card-title")).toHaveTextContent("Custom Title");
expect(screen.getByTestId("card-title")).toHaveTextContent(
"Custom Title"
);
});
it("should handle empty questions data", () => {
render(<TopQuestionsChart data={[]} />);
expect(screen.getByTestId("card")).toBeInTheDocument();
expect(screen.getByTestId("card-title")).toHaveTextContent("Top 5 Asked Questions");
expect(screen.getByText("No questions data available")).toBeInTheDocument();
expect(screen.getByTestId("card-title")).toHaveTextContent(
"Top 5 Asked Questions"
);
expect(
screen.getByText("No questions data available")
).toBeInTheDocument();
});
it("should display question counts as badges", () => {
render(<TopQuestionsChart data={mockQuestions} />);
expect(screen.getByText("25")).toBeInTheDocument();
expect(screen.getByText("20")).toBeInTheDocument();
});
it("should show all questions with progress bars", () => {
render(<TopQuestionsChart data={mockQuestions} />);
// All questions should be rendered
mockQuestions.forEach(question => {
mockQuestions.forEach((question) => {
expect(screen.getByText(question.question)).toBeInTheDocument();
expect(screen.getByText(question.count.toString())).toBeInTheDocument();
});
@ -116,7 +126,7 @@ describe("Dashboard Components", () => {
it("should calculate and display total questions", () => {
render(<TopQuestionsChart data={mockQuestions} />);
const totalQuestions = mockQuestions.reduce((sum, q) => sum + q.count, 0);
expect(screen.getByText(totalQuestions.toString())).toBeInTheDocument();
expect(screen.getByText("Total questions analyzed")).toBeInTheDocument();
@ -133,71 +143,75 @@ Assistant: Let me help you with that. Can you tell me what error message you're
it("should render transcript content", () => {
render(
<TranscriptViewer
<TranscriptViewer
transcriptContent={mockTranscriptContent}
transcriptUrl={mockTranscriptUrl}
/>
);
expect(screen.getByText("Session Transcript")).toBeInTheDocument();
expect(screen.getByText(/Hello, I need help with my account/)).toBeInTheDocument();
expect(
screen.getByText(/Hello, I need help with my account/)
).toBeInTheDocument();
});
it("should handle empty transcript content", () => {
render(
<TranscriptViewer
<TranscriptViewer
transcriptContent=""
transcriptUrl={mockTranscriptUrl}
/>
);
expect(screen.getByText("No transcript content available.")).toBeInTheDocument();
expect(
screen.getByText("No transcript content available.")
).toBeInTheDocument();
});
it("should render without transcript URL", () => {
render(
<TranscriptViewer
transcriptContent={mockTranscriptContent}
/>
);
render(<TranscriptViewer transcriptContent={mockTranscriptContent} />);
// Should still render content
expect(screen.getByText("Session Transcript")).toBeInTheDocument();
expect(screen.getByText(/Hello, I need help with my account/)).toBeInTheDocument();
expect(
screen.getByText(/Hello, I need help with my account/)
).toBeInTheDocument();
});
it("should toggle between formatted and raw view", () => {
render(
<TranscriptViewer
<TranscriptViewer
transcriptContent={mockTranscriptContent}
transcriptUrl={mockTranscriptUrl}
/>
);
// Find the raw text toggle button
const rawToggleButton = screen.getByText("Raw Text");
expect(rawToggleButton).toBeInTheDocument();
// Click to show raw view
fireEvent.click(rawToggleButton);
// Should now show "Formatted" button and raw content
expect(screen.getByText("Formatted")).toBeInTheDocument();
});
it("should handle malformed transcript content gracefully", () => {
const malformedContent = "This is not a properly formatted transcript";
render(
<TranscriptViewer
<TranscriptViewer
transcriptContent={malformedContent}
transcriptUrl={mockTranscriptUrl}
/>
);
// Should show "No transcript content available" in formatted view for malformed content
expect(screen.getByText("No transcript content available.")).toBeInTheDocument();
expect(
screen.getByText("No transcript content available.")
).toBeInTheDocument();
// But should show the raw content when toggled to raw view
const rawToggleButton = screen.getByText("Raw Text");
fireEvent.click(rawToggleButton);
@ -206,28 +220,30 @@ Assistant: Let me help you with that. Can you tell me what error message you're
it("should parse and display conversation messages", () => {
render(
<TranscriptViewer
<TranscriptViewer
transcriptContent={mockTranscriptContent}
transcriptUrl={mockTranscriptUrl}
/>
);
// Check for message content
expect(screen.getByText(/Hello, I need help with my account/)).toBeInTheDocument();
expect(
screen.getByText(/Hello, I need help with my account/)
).toBeInTheDocument();
expect(screen.getByText(/I'd be happy to help you/)).toBeInTheDocument();
});
it("should display transcript URL link when provided", () => {
render(
<TranscriptViewer
<TranscriptViewer
transcriptContent={mockTranscriptContent}
transcriptUrl={mockTranscriptUrl}
/>
);
const link = screen.getByText("View Full Raw");
expect(link).toBeInTheDocument();
expect(link.closest("a")).toHaveAttribute("href", mockTranscriptUrl);
});
});
});
});

View File

@ -1,6 +1,11 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { InMemoryRateLimiter, extractClientIP } from "../../lib/rateLimiter";
import { validateInput, registerSchema, loginSchema, forgotPasswordSchema } from "../../lib/validation";
import {
validateInput,
registerSchema,
loginSchema,
forgotPasswordSchema,
} from "../../lib/validation";
import { z } from "zod";
// Import password schema directly from validation file
@ -63,7 +68,7 @@ describe("Security Tests", () => {
expect(rateLimiter.checkRateLimit("test-ip").allowed).toBe(false);
// Wait for window to expire
await new Promise(resolve => setTimeout(resolve, 1100));
await new Promise((resolve) => setTimeout(resolve, 1100));
// Should be allowed again
expect(rateLimiter.checkRateLimit("test-ip").allowed).toBe(true);
@ -89,7 +94,7 @@ describe("Security Tests", () => {
}
// Wait for entries to expire
await new Promise(resolve => setTimeout(resolve, 1100));
await new Promise((resolve) => setTimeout(resolve, 1100));
// Force cleanup by checking rate limit
rateLimiter.checkRateLimit("cleanup-trigger");
@ -157,13 +162,13 @@ describe("Security Tests", () => {
const weakPasswords = [
"short", // Too short
"nouppercase123!", // No uppercase
"NOLOWERCASE123!", // No lowercase
"NOLOWERCASE123!", // No lowercase
"NoNumbers!@#", // No numbers
"NoSpecialChars123", // No special chars
"password123!", // Common password pattern
];
weakPasswords.forEach(password => {
weakPasswords.forEach((password) => {
const result = validateInput(passwordSchema, password);
expect(result.success).toBe(false);
});
@ -176,7 +181,7 @@ describe("Security Tests", () => {
"MyS3cur3P@ssword!",
];
strongPasswords.forEach(password => {
strongPasswords.forEach((password) => {
const result = validateInput(passwordSchema, password);
expect(result.success).toBe(true);
});
@ -302,4 +307,4 @@ describe("Security Tests", () => {
expect(true).toBe(true); // Placeholder for cookie config tests
});
});
});
});