177 lines
5.8 KiB
TypeScript
177 lines
5.8 KiB
TypeScript
/**
|
||
* Projects API Tests – CRUD (List / Get / Create / Update / Delete)
|
||
*/
|
||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||
import type { FastifyInstance } from 'fastify';
|
||
import { SqliteAdapter } from '../src/database/SqliteAdapter.js';
|
||
import { createServer } from '../src/server.js';
|
||
|
||
describe('Projects API', () => {
|
||
let app: FastifyInstance;
|
||
let db: SqliteAdapter;
|
||
let createdProjectId: string | null = null;
|
||
|
||
beforeAll(async () => {
|
||
db = new SqliteAdapter(':memory:');
|
||
await db.init();
|
||
app = await createServer({ db, port: 0 });
|
||
await app.ready();
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await app.close();
|
||
db.close();
|
||
});
|
||
|
||
// ─── Create ─────────────────────────────────────────
|
||
|
||
describe('POST /api/projects', () => {
|
||
it('should create a project with 201', async () => {
|
||
const response = await app.inject({
|
||
method: 'POST',
|
||
url: '/api/projects',
|
||
payload: {
|
||
name: 'Test Project',
|
||
description: 'A test project',
|
||
},
|
||
});
|
||
expect(response.statusCode).toBe(201);
|
||
const body = JSON.parse(response.body);
|
||
expect(body.id).toBeDefined();
|
||
expect(body.name).toBe('Test Project');
|
||
expect(body.description).toBe('A test project');
|
||
createdProjectId = body.id;
|
||
});
|
||
|
||
it('should reject project without name with 400', async () => {
|
||
const response = await app.inject({
|
||
method: 'POST',
|
||
url: '/api/projects',
|
||
payload: { description: 'No name' },
|
||
});
|
||
expect(response.statusCode).toBe(400);
|
||
});
|
||
|
||
it('should create project with default values', async () => {
|
||
const response = await app.inject({
|
||
method: 'POST',
|
||
url: '/api/projects',
|
||
payload: { name: 'Minimal Project' },
|
||
});
|
||
expect(response.statusCode).toBe(201);
|
||
const body = JSON.parse(response.body);
|
||
expect(body.name).toBe('Minimal Project');
|
||
expect(body.description).toBeNull();
|
||
});
|
||
});
|
||
|
||
// ─── List ───────────────────────────────────────────
|
||
|
||
describe('GET /api/projects', () => {
|
||
it('should list all projects', async () => {
|
||
const response = await app.inject({
|
||
method: 'GET',
|
||
url: '/api/projects',
|
||
});
|
||
expect(response.statusCode).toBe(200);
|
||
const body = JSON.parse(response.body);
|
||
expect(Array.isArray(body)).toBe(true);
|
||
expect(body.length).toBeGreaterThanOrEqual(2);
|
||
});
|
||
});
|
||
|
||
// ─── Get ────────────────────────────────────────────
|
||
|
||
describe('GET /api/projects/:id', () => {
|
||
it('should get a single project', async () => {
|
||
const response = await app.inject({
|
||
method: 'GET',
|
||
url: `/api/projects/${createdProjectId}`,
|
||
});
|
||
expect(response.statusCode).toBe(200);
|
||
const body = JSON.parse(response.body);
|
||
expect(body.id).toBe(createdProjectId);
|
||
expect(body.name).toBe('Test Project');
|
||
});
|
||
|
||
it('should return 404 for non-existent project', async () => {
|
||
const response = await app.inject({
|
||
method: 'GET',
|
||
url: '/api/projects/non-existent-id',
|
||
});
|
||
expect(response.statusCode).toBe(404);
|
||
});
|
||
});
|
||
|
||
// ─── Update ─────────────────────────────────────────
|
||
|
||
describe('PATCH /api/projects/:id', () => {
|
||
it('should update project name', async () => {
|
||
const response = await app.inject({
|
||
method: 'PATCH',
|
||
url: `/api/projects/${createdProjectId}`,
|
||
payload: { name: 'Updated Project Name' },
|
||
});
|
||
expect(response.statusCode).toBe(200);
|
||
const body = JSON.parse(response.body);
|
||
expect(body.name).toBe('Updated Project Name');
|
||
expect(body.id).toBe(createdProjectId);
|
||
});
|
||
|
||
it('should update project description', async () => {
|
||
const response = await app.inject({
|
||
method: 'PATCH',
|
||
url: `/api/projects/${createdProjectId}`,
|
||
payload: { description: 'Updated description' },
|
||
});
|
||
expect(response.statusCode).toBe(200);
|
||
const body = JSON.parse(response.body);
|
||
expect(body.description).toBe('Updated description');
|
||
});
|
||
|
||
it('should return 404 for non-existent project update', async () => {
|
||
const response = await app.inject({
|
||
method: 'PATCH',
|
||
url: '/api/projects/non-existent-id',
|
||
payload: { name: 'New Name' },
|
||
});
|
||
expect(response.statusCode).toBe(404);
|
||
});
|
||
});
|
||
|
||
// ─── Delete ─────────────────────────────────────────
|
||
|
||
describe('DELETE /api/projects/:id', () => {
|
||
it('should delete a project', async () => {
|
||
// First create a project to delete
|
||
const createRes = await app.inject({
|
||
method: 'POST',
|
||
url: '/api/projects',
|
||
payload: { name: 'To Be Deleted' },
|
||
});
|
||
const projectId = JSON.parse(createRes.body).id;
|
||
|
||
const response = await app.inject({
|
||
method: 'DELETE',
|
||
url: `/api/projects/${projectId}`,
|
||
});
|
||
expect(response.statusCode).toBe(204);
|
||
|
||
// Verify it's gone
|
||
const getRes = await app.inject({
|
||
method: 'GET',
|
||
url: `/api/projects/${projectId}`,
|
||
});
|
||
expect(getRes.statusCode).toBe(404);
|
||
});
|
||
|
||
it('should return 404 for non-existent project delete', async () => {
|
||
const response = await app.inject({
|
||
method: 'DELETE',
|
||
url: '/api/projects/non-existent-id',
|
||
});
|
||
expect(response.statusCode).toBe(404);
|
||
});
|
||
});
|
||
});
|