gadget/gadget-code/tests/project-api.test.ts
Rob Colbert 2e9571a74e Implement dark industrial theme, Project Manager, and JWT authentication
- Add dark industrial theme with brand color #c20600 and CSS variables
- Create Header component with version display and user dropdown menu
- Create StatusBar with connection indicator and project/session display
- Create ProjectManager page with CRUD, list view, and inspector
- Add JWT Bearer token to API requests for authenticated endpoints
- Add project API endpoints (GET/POST/PUT/DELETE /api/v1/projects)
- Add ProjectService methods: findById, findBySlug, delete
- Add unit tests for project API endpoints
- Add Playwright E2E tests for projects flow
- Update UI design guide with implementation details
- Fix: empty JSON body parsing in web-app.ts middleware
2026-04-28 17:13:49 -04:00

94 lines
3.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
const ROOT_DIR = path.resolve(__dirname, '..');
describe('Project API Endpoints', () => {
describe('Project API Controller', () => {
it('should have project controller registered', () => {
const apiV1Path = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
expect(fs.existsSync(apiV1Path)).toBe(true);
});
it('should have GET projects route', () => {
const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
const content = fs.readFileSync(controllerPath, 'utf-8');
expect(content).toContain('getProjects');
});
it('should have POST projects route', () => {
const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
const content = fs.readFileSync(controllerPath, 'utf-8');
expect(content).toContain('createProject');
});
it('should use requireUser middleware', () => {
const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
const content = fs.readFileSync(controllerPath, 'utf-8');
expect(content).toContain('requireUser()');
});
it('should not expose password fields in responses', () => {
const controllerPath = path.join(ROOT_DIR, 'src', 'controllers', 'api', 'v1', 'project.ts');
const content = fs.readFileSync(controllerPath, 'utf-8');
expect(content).not.toMatch(/passwordSalt/);
});
});
describe('Project Service', () => {
it('should have findById method', () => {
const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts');
const content = fs.readFileSync(servicePath, 'utf-8');
expect(content).toContain('findById');
});
it('should have delete method', () => {
const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts');
const content = fs.readFileSync(servicePath, 'utf-8');
expect(content).toContain('async delete');
});
it('should have getForUser method', () => {
const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts');
const content = fs.readFileSync(servicePath, 'utf-8');
expect(content).toContain('getForUser');
});
it('should populate user with password excluded', () => {
const servicePath = path.join(ROOT_DIR, 'src', 'services', 'project.ts');
const content = fs.readFileSync(servicePath, 'utf-8');
expect(content).toContain('-passwordSalt');
});
});
describe('Frontend API Client', () => {
it('should add Authorization header with token', () => {
const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts');
const content = fs.readFileSync(apiPath, 'utf-8');
expect(content).toContain('Authorization');
expect(content).toContain('Bearer');
});
it('should store token in localStorage', () => {
const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts');
const content = fs.readFileSync(apiPath, 'utf-8');
expect(content).toContain('dtp_auth_token');
});
it('should have getAll method', () => {
const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts');
const content = fs.readFileSync(apiPath, 'utf-8');
expect(content).toContain('getAll');
});
});
describe('User Interface', () => {
it('should not include password fields in User type', () => {
const apiPath = path.join(ROOT_DIR, 'frontend', 'src', 'lib', 'api.ts');
const content = fs.readFileSync(apiPath, 'utf-8');
const userInterfaceMatch = content.match(/export interface User/);
expect(userInterfaceMatch).toBeTruthy();
});
});
});