import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { getConfig } from '@/config/config.js'; import type { Server } from '@/config/types.js'; import type { Pet } from '@/features/demo/demo.db-schema.js'; import { getServer } from '@/server.js'; let server: Server; beforeAll(async () => { const config = getConfig(); server = getServer(config); await server.ready(); }); afterAll(async () => { await server.close(); }); // Helper to create valid metadata const createMetadata = () => ({ login_at: new Date().toISOString(), ip: '192.168.1.1', agent: 'Mozilla/5.0', plan: 'premium' as const, }); // Helper to create a person async function createPerson(first_name: string, gender: 'man' | 'woman' | 'other' = 'man') { const response = await server.inject({ method: 'POST', url: '/demo/persons', payload: { first_name, gender, metadata: createMetadata(), }, }); return response.json(); } describe('Pet CRUD', () => { it('should create a pet', async () => { const owner = await createPerson('PetOwner'); const response = await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Buddy', species: 'dog', owner_id: owner.id }, }); expect(response.statusCode).toBe(201); const body = response.json(); expect(body).toMatchObject({ name: 'Buddy', species: 'dog', owner_id: owner.id, }); expect(body.id).toBeDefined(); }); it('should get a pet by ID', async () => { const owner = await createPerson('Owner'); // Create a pet const createResponse = await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Mittens', species: 'cat', owner_id: owner.id }, }); const created = createResponse.json(); // Get the pet const response = await server.inject({ method: 'GET', url: `/demo/pets/${created.id}`, }); expect(response.statusCode).toBe(200); const body = response.json(); expect(body).toMatchObject({ id: created.id, name: 'Mittens', species: 'cat', owner_id: owner.id, }); }); it('should return 404 for non-existent pet', async () => { const response = await server.inject({ method: 'GET', url: '/demo/pets/999999', }); expect(response.statusCode).toBe(404); expect(response.json()).toEqual({ message: 'Pet not found' }); }); it('should list pets', async () => { const owner = await createPerson('MultiPetOwner'); // Create multiple pets await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Spot', species: 'dog', owner_id: owner.id }, }); await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Whiskers', species: 'cat', owner_id: owner.id }, }); const response = await server.inject({ method: 'GET', url: '/demo/pets', }); expect(response.statusCode).toBe(200); const body = response.json(); expect(Array.isArray(body)).toBe(true); expect(body.length).toBeGreaterThanOrEqual(2); }); it('should filter pets by species', async () => { const owner = await createPerson('SpeciesOwner'); // Create pets with different species await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Fido', species: 'dog', owner_id: owner.id }, }); await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Kitty', species: 'cat', owner_id: owner.id }, }); const response = await server.inject({ method: 'GET', url: '/demo/pets?species=dog', }); expect(response.statusCode).toBe(200); const body = response.json(); expect(body.every((p: Pet) => p.species === 'dog')).toBe(true); }); it('should filter pets by owner_id', async () => { const owner1 = await createPerson('Owner1'); const owner2 = await createPerson('Owner2', 'woman'); // Create pets for different owners await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Pet1', species: 'dog', owner_id: owner1.id }, }); await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Pet2', species: 'cat', owner_id: owner2.id }, }); const response = await server.inject({ method: 'GET', url: `/demo/pets?owner_id=${owner1.id}`, }); expect(response.statusCode).toBe(200); const body = response.json(); expect(body.every((p: Pet) => p.owner_id === owner1.id)).toBe(true); }); it('should update a pet', async () => { const owner = await createPerson('UpdateOwner'); // Create a pet const createResponse = await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'OldName', species: 'dog', owner_id: owner.id }, }); const created = createResponse.json(); // Update the pet const updateResponse = await server.inject({ method: 'PATCH', url: `/demo/pets/${created.id}`, payload: { name: 'NewName' }, }); expect(updateResponse.statusCode).toBe(204); // Verify the update const getResponse = await server.inject({ method: 'GET', url: `/demo/pets/${created.id}`, }); const updated = getResponse.json(); expect(updated.name).toBe('NewName'); expect(updated.species).toBe('dog'); // Should remain unchanged }); it('should return 404 when updating non-existent pet', async () => { const response = await server.inject({ method: 'PATCH', url: '/demo/pets/999999', payload: { name: 'Does Not Exist' }, }); expect(response.statusCode).toBe(404); }); it('should delete a pet', async () => { const owner = await createPerson('DeleteOwner'); // Create a pet const createResponse = await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'ToDelete', species: 'cat', owner_id: owner.id }, }); const created = createResponse.json(); // Delete the pet const deleteResponse = await server.inject({ method: 'DELETE', url: `/demo/pets/${created.id}`, }); expect(deleteResponse.statusCode).toBe(204); // Verify deletion const getResponse = await server.inject({ method: 'GET', url: `/demo/pets/${created.id}`, }); expect(getResponse.statusCode).toBe(404); }); it('should return 404 when deleting non-existent pet', async () => { const response = await server.inject({ method: 'DELETE', url: '/demo/pets/999999', }); expect(response.statusCode).toBe(404); }); }); describe('Pet with Owner', () => { it('should get pet with owner details', async () => { const owner = await createPerson('DetailedOwner', 'other'); // Create a pet const petResponse = await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Max', species: 'dog', owner_id: owner.id }, }); const pet = petResponse.json(); // Get pet with owner const response = await server.inject({ method: 'GET', url: `/demo/pets/${pet.id}/with-owner`, }); expect(response.statusCode).toBe(200); const body = response.json(); expect(body.pet.id).toBe(pet.id); expect(body.pet.name).toBe('Max'); expect(body.owner).toBeDefined(); expect(body.owner.id).toBe(owner.id); expect(body.owner.first_name).toBe('DetailedOwner'); expect(body.owner.gender).toBe('other'); }); }); describe('Cascade Delete', () => { it('should delete all pets when owner is deleted', async () => { const owner = await createPerson('CascadeOwner'); // Create multiple pets const pet1Response = await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Pet1', species: 'dog', owner_id: owner.id }, }); const pet2Response = await server.inject({ method: 'POST', url: '/demo/pets', payload: { name: 'Pet2', species: 'cat', owner_id: owner.id }, }); const pet1 = pet1Response.json(); const pet2 = pet2Response.json(); // Delete the owner const deleteResponse = await server.inject({ method: 'DELETE', url: `/demo/persons/${owner.id}`, }); expect(deleteResponse.statusCode).toBe(204); // Verify pets are also deleted const pet1Check = await server.inject({ method: 'GET', url: `/demo/pets/${pet1.id}`, }); const pet2Check = await server.inject({ method: 'GET', url: `/demo/pets/${pet2.id}`, }); expect(pet1Check.statusCode).toBe(404); expect(pet2Check.statusCode).toBe(404); }); });