From 663b50710ec81a0697bc25801bf7ae9d9ca16ef7 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 24 Jul 2026 04:21:44 +0000 Subject: [PATCH] test: batch 3.1 - api-server test suite - add health endpoint tests (3 tests) - add auth middleware tests (4 tests) - add task routes tests (9 tests) - add vitest.config.ts with 80% coverage threshold - total: 16 tests passing part of v1.0.2 test coverage (phase 3 batch 3.1) --- packages/api-server/test/auth.test.ts | 76 ++++++++++ packages/api-server/test/health.test.ts | 73 ++++++++++ packages/api-server/test/tasks.test.ts | 180 ++++++++++++++++++++++++ packages/api-server/vitest.config.ts | 19 +++ 4 files changed, 348 insertions(+) create mode 100644 packages/api-server/test/auth.test.ts create mode 100644 packages/api-server/test/health.test.ts create mode 100644 packages/api-server/test/tasks.test.ts create mode 100644 packages/api-server/vitest.config.ts diff --git a/packages/api-server/test/auth.test.ts b/packages/api-server/test/auth.test.ts new file mode 100644 index 00000000..78ab74a3 --- /dev/null +++ b/packages/api-server/test/auth.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { createApiServer } from '../src/index.js'; +import type { FastifyInstance } from 'fastify'; + +describe('Auth Middleware', () => { + let server: FastifyInstance; + + beforeAll(async () => { + server = await createApiServer({ + port: 3000, + host: 'localhost', + apiKey: 'test-api-key', + allowedOrigins: ['*'], + rateLimitMax: 100, + rateLimitWindow: 60000, + }); + await server.ready(); + }); + + afterAll(async () => { + await server.close(); + }); + + describe('Authorization Header', () => { + it('should reject requests without authorization header', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/health', + }); + + expect(response.statusCode).toBe(401); + const body = JSON.parse(response.body); + expect(body.error).toBe('Missing or invalid authorization header'); + }); + + it('should reject requests with invalid authorization header format', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/health', + headers: { + authorization: 'InvalidFormat', + }, + }); + + expect(response.statusCode).toBe(401); + const body = JSON.parse(response.body); + expect(body.error).toBe('Missing or invalid authorization header'); + }); + + it('should reject requests with wrong API key', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/health', + headers: { + authorization: 'Bearer wrong-api-key', + }, + }); + + expect(response.statusCode).toBe(403); + const body = JSON.parse(response.body); + expect(body.error).toBe('Invalid API key'); + }); + + it('should accept requests with valid API key', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/health', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + }); + }); +}); diff --git a/packages/api-server/test/health.test.ts b/packages/api-server/test/health.test.ts new file mode 100644 index 00000000..c13bb127 --- /dev/null +++ b/packages/api-server/test/health.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { createApiServer } from '../src/index.js'; +import type { FastifyInstance } from 'fastify'; + +describe('Health Endpoints', () => { + let server: FastifyInstance; + + beforeAll(async () => { + server = await createApiServer({ + port: 3000, + host: 'localhost', + apiKey: 'test-api-key', + allowedOrigins: ['*'], + rateLimitMax: 100, + rateLimitWindow: 60000, + }); + await server.ready(); + }); + + afterAll(async () => { + await server.close(); + }); + + describe('GET /api/v1/health', () => { + it('should return healthy status', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/health', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body); + expect(body.status).toBe('healthy'); + expect(body.timestamp).toBeDefined(); + }); + }); + + describe('GET /api/v1/health/live', () => { + it('should return alive status', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/health/live', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body); + expect(body.status).toBe('alive'); + }); + }); + + describe('GET /api/v1/health/ready', () => { + it('should return ready status', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/health/ready', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body); + expect(body.status).toBe('ready'); + expect(body.ready).toBe(true); + }); + }); +}); diff --git a/packages/api-server/test/tasks.test.ts b/packages/api-server/test/tasks.test.ts new file mode 100644 index 00000000..b37bd01a --- /dev/null +++ b/packages/api-server/test/tasks.test.ts @@ -0,0 +1,180 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { createApiServer } from '../src/index.js'; +import type { FastifyInstance } from 'fastify'; + +describe('Task Routes', () => { + let server: FastifyInstance; + + beforeAll(async () => { + server = await createApiServer({ + port: 3000, + host: 'localhost', + apiKey: 'test-api-key', + allowedOrigins: ['*'], + rateLimitMax: 100, + rateLimitWindow: 60000, + }); + await server.ready(); + }); + + afterAll(async () => { + await server.close(); + }); + + describe('POST /api/v1/tasks', () => { + it('should create a task with valid body', async () => { + const response = await server.inject({ + method: 'POST', + url: '/api/v1/tasks', + headers: { + authorization: 'Bearer test-api-key', + 'content-type': 'application/json', + }, + payload: JSON.stringify({ + goal: 'Test task', + priority: 'high', + }), + }); + + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body); + expect(body.id).toBeDefined(); + expect(body.goal).toBe('Test task'); + expect(body.status).toBe('CREATED'); + expect(body.priority).toBe('high'); + expect(body.createdAt).toBeDefined(); + }); + + it('should create a task with default priority', async () => { + const response = await server.inject({ + method: 'POST', + url: '/api/v1/tasks', + headers: { + authorization: 'Bearer test-api-key', + 'content-type': 'application/json', + }, + payload: JSON.stringify({ + goal: 'Test task with default priority', + }), + }); + + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body); + expect(body.priority).toBe('medium'); + }); + + it('should create a task with parentTaskId and dependsOn', async () => { + const response = await server.inject({ + method: 'POST', + url: '/api/v1/tasks', + headers: { + authorization: 'Bearer test-api-key', + 'content-type': 'application/json', + }, + payload: JSON.stringify({ + goal: 'Child task', + parentTaskId: 'task-123', + dependsOn: ['task-456', 'task-789'], + }), + }); + + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body); + expect(body.id).toBeDefined(); + expect(body.goal).toBe('Child task'); + expect(body.status).toBe('CREATED'); + }); + + it('should reject request without goal', async () => { + const response = await server.inject({ + method: 'POST', + url: '/api/v1/tasks', + headers: { + authorization: 'Bearer test-api-key', + 'content-type': 'application/json', + }, + payload: JSON.stringify({ + priority: 'high', + }), + }); + + expect(response.statusCode).toBe(400); + }); + }); + + describe('GET /api/v1/tasks', () => { + it('should return empty array when no tasks', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/tasks', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body); + expect(Array.isArray(body)).toBe(true); + expect(body.length).toBe(0); + }); + + it('should accept status filter', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/tasks?status=CREATED', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + }); + + it('should accept priority filter', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/tasks?priority=high', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + }); + }); + + describe('GET /api/v1/tasks/:id', () => { + it('should return a task by id', async () => { + const response = await server.inject({ + method: 'GET', + url: '/api/v1/tasks/task-123', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body); + expect(body.id).toBe('task-123'); + expect(body.goal).toBe('Sample task'); + expect(body.status).toBe('CREATED'); + }); + }); + + describe('POST /api/v1/tasks/:id/cancel', () => { + it('should cancel a task', async () => { + const response = await server.inject({ + method: 'POST', + url: '/api/v1/tasks/task-123/cancel', + headers: { + authorization: 'Bearer test-api-key', + }, + }); + + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body); + expect(body.id).toBe('task-123'); + expect(body.status).toBe('CANCELLED'); + }); + }); +}); diff --git a/packages/api-server/vitest.config.ts b/packages/api-server/vitest.config.ts new file mode 100644 index 00000000..4da1d802 --- /dev/null +++ b/packages/api-server/vitest.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + thresholds: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80, + }, + }, + }, + }, +});