Fixing everything, making the project structure ready for real code.
Some checks failed
Tests / Frontend Tests (TypeScript + Vue + Yarn Berry) (push) Failing after 7m49s
Tests / Backend Tests (Python 3.13 + uv) (push) Failing after 14m36s

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-10-23 12:58:32 -04:00
parent 17a8ab1a06
commit 2c8f424a81
13 changed files with 173 additions and 562 deletions

View File

@@ -3,6 +3,9 @@
"private": true,
"version": "0.0.0",
"type": "module",
"engines": {
"node": ">=24.6.0"
},
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",

133
frontend/src/test-setup.ts Normal file
View File

@@ -0,0 +1,133 @@
/**
* Automatic Zod validation setup for test runs
* This file sets up global hooks that automatically validate all Zod schemas during testing
*/
import { beforeAll, afterAll } from 'vitest';
import { z } from 'zod';
// Store original Zod parse methods
const originalParse = z.ZodType.prototype.parse;
const originalSafeParse = z.ZodType.prototype.safeParse;
let validationCount = 0;
let validationErrors: string[] = [];
/**
* Enhanced parse method that logs validation attempts during tests
* @param data - The data to validate against the schema
* @returns The parsed and validated data
*/
function enhancedParse<T>(this: z.ZodType<T>, data: unknown): T {
try {
const result = originalParse.call(this, data);
validationCount++;
// Log successful validations in test mode
if (process.env.NODE_ENV === 'test') {
console.log(`✅ Zod validation passed (${validationCount} total)`);
}
return result;
} catch (error) {
validationErrors.push(`Zod validation failed: ${error}`);
// Log failed validations in test mode
if (process.env.NODE_ENV === 'test') {
console.error(`❌ Zod validation failed:`, error);
}
throw error;
}
}
/**
* Enhanced safeParse method that logs validation attempts during tests
* @param data - The data to validate against the schema
* @returns Safe parse result with success flag and data or error
*/
function enhancedSafeParse<T>(
this: z.ZodType<T>,
data: unknown
): z.SafeParseReturnType<unknown, T> {
const result = originalSafeParse.call(this, data);
validationCount++;
if (process.env.NODE_ENV === 'test') {
if (result.success) {
console.log(`✅ Zod safe validation passed (${validationCount} total)`);
} else {
console.warn(`⚠️ Zod safe validation failed but was handled gracefully`);
validationErrors.push(`Zod safe validation failed: ${result.error.message}`);
}
}
return result;
}
/**
* Install automatic Zod validation hooks for testing
*/
export function installZodTestHooks(): void {
// Replace Zod's parse methods with enhanced versions
z.ZodType.prototype.parse = enhancedParse;
z.ZodType.prototype.safeParse = enhancedSafeParse;
// Enable global auto-validation flag
(globalThis as any).__AUTO_VALIDATE__ = true;
console.log('🛡️ Automatic Zod validation hooks installed for test run');
}
/**
* Remove automatic Zod validation hooks
*/
export function uninstallZodTestHooks(): void {
// Restore original methods
z.ZodType.prototype.parse = originalParse;
z.ZodType.prototype.safeParse = originalSafeParse;
// Disable global auto-validation flag
(globalThis as any).__AUTO_VALIDATE__ = false;
console.log('🔧 Zod validation hooks removed');
}
/**
* Get validation statistics for test reporting
* @returns Object containing validation count and errors
*/
export function getValidationStats(): {
count: number;
errors: string[];
} {
return {
count: validationCount,
errors: [...validationErrors],
};
}
/**
* Reset validation statistics
*/
export function resetValidationStats(): void {
validationCount = 0;
validationErrors = [];
}
// Automatic setup for Vitest
beforeAll(() => {
installZodTestHooks();
resetValidationStats();
});
afterAll(() => {
const stats = getValidationStats();
console.log(`📊 Test run completed with ${stats.count} Zod validations`);
if (stats.errors.length > 0) {
console.warn(`⚠️ ${stats.errors.length} validation errors encountered during tests`);
}
uninstallZodTestHooks();
});

View File

@@ -1,6 +1,6 @@
/**
* Hypermodern Zod validation setup - automatic runtime validation without explicit .parse() calls
* Follows the same philosophy as typeguard for Python
* Zod validation setup with automatic test hooks
* Schemas are automatically validated during test runs via test-setup.ts
*/
import { z } from 'zod';
@@ -28,99 +28,3 @@ export const UserSchema = z.object({
// Inferred types (no manual type definitions needed)
export type Playlist = z.infer<typeof PlaylistSchema>;
export type User = z.infer<typeof UserSchema>;
/**
* Global schema registry for automatic validation
*/
class SchemaRegistry {
private static schemas = new Map<string, z.ZodTypeAny>([
['Playlist', PlaylistSchema as z.ZodTypeAny],
['User', UserSchema as z.ZodTypeAny],
]);
static register<T extends z.ZodTypeAny>(name: string, schema: T): void {
this.schemas.set(name, schema as z.ZodTypeAny);
}
static get(name: string): z.ZodTypeAny | undefined {
return this.schemas.get(name);
}
static validate<T>(typeName: string, data: unknown): T {
const schema = this.schemas.get(typeName);
if (!schema) {
throw new Error(`No schema registered for type: ${typeName}`);
}
return schema.parse(data) as T;
}
static safeValidate<T>(
typeName: string,
data: unknown
): { success: true; data: T } | { success: false; error: z.ZodError } {
const schema = this.schemas.get(typeName);
if (!schema) {
return {
success: false,
error: new z.ZodError([
{
code: 'custom',
message: `No schema registered for type: ${typeName}`,
path: [],
},
]),
};
}
const result = schema.safeParse(data);
return result.success
? { success: true, data: result.data as T }
: { success: false, error: result.error };
}
}
/**
* Automatic validation wrapper - works like typeguard for Python
* In development/test: validates automatically
* In production: can be stripped by build process
*/
export class AutoValidator {
static validate<T>(typeName: string, data: unknown): T {
// Check if auto-validation is enabled (development/test mode)
if (typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__) {
return SchemaRegistry.validate<T>(typeName, data);
}
// In production, validation is skipped for performance
return data as T;
}
static safeValidate<T>(
typeName: string,
data: unknown
): { success: true; data: T } | { success: false; error: z.ZodError } {
if (typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__) {
return SchemaRegistry.safeValidate<T>(typeName, data);
}
// In production, assume data is valid
return { success: true, data: data as T };
}
static register<T extends z.ZodTypeAny>(name: string, schema: T): void {
SchemaRegistry.register(name, schema);
}
}
// Enable automatic validation in development/test environments
declare global {
var __AUTO_VALIDATE__: boolean;
}
// Check environment and log status
const isDev = typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__;
if (isDev) {
console.log('🛡️ Hypermodern Zod validation enabled (development/test mode)');
} else {
console.log('🏃‍♂️ Production mode - Zod validation optimized away');
}

View File

@@ -1,41 +1,6 @@
/**
* Example unit tests with automatic Zod validation
* Uses hypermodern approach where validation happens automatically
*/
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import App from '@/App.vue'
import { AutoValidator, type Playlist, type User } from '@/validation'
/**
* Creates a playlist - validation happens automatically in development/test mode
* @param name - Playlist name
* @param description - Optional description
* @returns Playlist object (automatically validated)
*/
function createPlaylist(name: string, description?: string): Playlist {
const rawData = {
id: crypto.randomUUID(),
name,
description,
tracks: []
}
// In hypermodern setup, this would be handled by build-time transform
// For now, we use our auto-validator
return AutoValidator.validate<Playlist>('Playlist', rawData)
}
/**
* Simulates API response handling with automatic validation
* @param apiResponse - Raw API data
* @returns Validated user object
*/
function handleUserApiResponse(apiResponse: unknown): User {
// Validation happens automatically - no explicit .parse() needed
return AutoValidator.validate<User>('User', apiResponse)
}
describe('App.vue', () => {
it('renders properly', () => {
@@ -43,81 +8,3 @@ describe('App.vue', () => {
expect(wrapper.text()).toContain('Plex Playlist')
})
})
describe('Automatic Zod validation (hypermodern approach)', () => {
it('validates playlist creation automatically', () => {
const playlist = createPlaylist('Test Playlist', 'Test description')
expect(playlist.name).toBe('Test Playlist')
expect(playlist.description).toBe('Test description')
expect(playlist.tracks).toEqual([])
expect(playlist.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
})
it('creates playlist without description automatically', () => {
const playlist = createPlaylist('Simple Playlist')
expect(playlist.name).toBe('Simple Playlist')
expect(playlist.description).toBeUndefined()
expect(playlist.tracks).toEqual([])
})
it('automatically validates API responses', () => {
const apiResponse = {
id: 123,
username: 'testuser',
email: 'test@example.com',
preferences: {
theme: 'dark',
notifications: false
}
}
const user = handleUserApiResponse(apiResponse)
expect(user.username).toBe('testuser')
expect(user.preferences.theme).toBe('dark')
})
it('automatically catches validation errors', () => {
const invalidPlaylistData = {
id: 'not-a-uuid', // Invalid UUID
name: '', // Empty name
tracks: 'invalid' // Should be array
}
expect(() => {
AutoValidator.validate<Playlist>('Playlist', invalidPlaylistData)
}).toThrow()
})
it('provides safe validation with error handling', () => {
const malformedData = {
id: 'not-a-uuid',
description: 'Missing name field'
}
const result = AutoValidator.safeValidate<Playlist>('Playlist', malformedData)
expect(result.success).toBe(false)
if (!result.success) {
expect(result.error.issues).toBeDefined()
expect(result.error.issues.length).toBeGreaterThan(0)
}
})
it('handles successful safe validation', () => {
const validData = {
id: crypto.randomUUID(),
name: 'Valid Playlist',
tracks: ['track1', 'track2']
}
const result = AutoValidator.safeValidate<Playlist>('Playlist', validData)
expect(result.success).toBe(true)
if (result.success) {
expect(result.data.name).toBe('Valid Playlist')
expect(result.data.tracks).toEqual(['track1', 'track2'])
}
})
})

View File

@@ -2,33 +2,10 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
// Custom plugin for automatic Zod validation in development
function zodAutoValidation() {
return {
name: 'zod-auto-validation',
transform(code: string, id: string) {
// Only apply in development/test environments
if (process.env.NODE_ENV === 'production') {
return null
}
// Add automatic validation for functions with Zod schemas
if (id.includes('src/') && id.endsWith('.ts')) {
// This would normally be a more sophisticated transform
// For now, we rely on our AutoValidator pattern
return null
}
return null
}
}
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
zodAutoValidation()
vue()
],
resolve: {
alias: {

View File

@@ -12,6 +12,7 @@ export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test-setup.ts'], // Automatically install Zod validation hooks
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
@@ -23,7 +24,15 @@ export default defineConfig({
'coverage/',
'tests/',
'playwright.config.ts'
]
],
thresholds: {
global: {
lines: 85,
functions: 85,
branches: 85,
statements: 85
}
}
}
}
})