Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 58s
159 lines
6.1 KiB
TypeScript
159 lines
6.1 KiB
TypeScript
// src/tests/integration/recipe.integration.test.ts
|
|
import { describe, it, expect, beforeAll, afterAll, vi, afterEach } from 'vitest';
|
|
import supertest from 'supertest';
|
|
import { createAndLoginUser } from '../utils/testHelpers';
|
|
import { cleanupDb } from '../utils/cleanup';
|
|
import type { UserProfile, Recipe } from '../../types';
|
|
import { getPool } from '../../services/db/connection.db';
|
|
|
|
import { aiService } from '../../services/aiService.server';
|
|
|
|
/**
|
|
* @vitest-environment node
|
|
*/
|
|
|
|
describe('Recipe API Routes Integration Tests', () => {
|
|
let request: ReturnType<typeof supertest>;
|
|
let testUser: UserProfile;
|
|
let authToken: string;
|
|
let testRecipe: Recipe;
|
|
const createdUserIds: string[] = [];
|
|
const createdRecipeIds: number[] = [];
|
|
|
|
beforeAll(async () => {
|
|
vi.stubEnv('FRONTEND_URL', 'https://example.com');
|
|
const app = (await import('../../../server')).default;
|
|
request = supertest(app);
|
|
|
|
// Create a user to own the recipe and perform authenticated actions
|
|
const { user, token } = await createAndLoginUser({
|
|
email: `recipe-user-${Date.now()}@example.com`,
|
|
fullName: 'Recipe Test User',
|
|
request,
|
|
});
|
|
testUser = user;
|
|
authToken = token;
|
|
createdUserIds.push(user.user.user_id);
|
|
|
|
// Mock the AI service method using spyOn to preserve other exports like DuplicateFlyerError
|
|
vi.spyOn(aiService, 'generateRecipeSuggestion').mockResolvedValue('Default Mock Suggestion');
|
|
|
|
// Create a recipe owned by the test user
|
|
const recipeRes = await getPool().query(
|
|
`INSERT INTO public.recipes (name, instructions, user_id, status, description)
|
|
VALUES ('Integration Test Recipe', '1. Do this. 2. Do that.', $1, 'public', 'A test recipe description.')
|
|
RETURNING *`,
|
|
[testUser.user.user_id],
|
|
);
|
|
testRecipe = recipeRes.rows[0];
|
|
createdRecipeIds.push(testRecipe.recipe_id);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
// Reset the mock to its default state for the next test
|
|
vi.mocked(aiService.generateRecipeSuggestion).mockResolvedValue('Default Mock Suggestion');
|
|
});
|
|
|
|
afterAll(async () => {
|
|
vi.unstubAllEnvs();
|
|
// Clean up all created resources
|
|
await cleanupDb({
|
|
userIds: createdUserIds,
|
|
recipeIds: createdRecipeIds,
|
|
});
|
|
});
|
|
|
|
describe('GET /api/recipes/:recipeId', () => {
|
|
it('should fetch a single public recipe by its ID', async () => {
|
|
const response = await request.get(`/api/recipes/${testRecipe.recipe_id}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toBeDefined();
|
|
expect(response.body.recipe_id).toBe(testRecipe.recipe_id);
|
|
expect(response.body.name).toBe('Integration Test Recipe');
|
|
});
|
|
|
|
it('should return 404 for a non-existent recipe ID', async () => {
|
|
const response = await request.get('/api/recipes/999999');
|
|
expect(response.status).toBe(404);
|
|
});
|
|
});
|
|
|
|
it('should allow an authenticated user to create a new recipe', async () => {
|
|
const newRecipeData = {
|
|
name: 'My New Awesome Recipe',
|
|
instructions: '1. Be awesome. 2. Make recipe.',
|
|
description: 'A recipe created during an integration test.',
|
|
};
|
|
|
|
const response = await request
|
|
.post('/api/users/recipes')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(newRecipeData);
|
|
|
|
// Assert the response from the POST request
|
|
expect(response.status).toBe(201);
|
|
const createdRecipe: Recipe = response.body;
|
|
expect(createdRecipe).toBeDefined();
|
|
expect(createdRecipe.recipe_id).toBeTypeOf('number');
|
|
expect(createdRecipe.name).toBe(newRecipeData.name);
|
|
expect(createdRecipe.user_id).toBe(testUser.user.user_id);
|
|
|
|
// Add the new recipe ID to the cleanup array to ensure it's deleted after tests
|
|
createdRecipeIds.push(createdRecipe.recipe_id);
|
|
|
|
// Verify the recipe can be fetched from the public endpoint
|
|
const verifyResponse = await request.get(`/api/recipes/${createdRecipe.recipe_id}`);
|
|
expect(verifyResponse.status).toBe(200);
|
|
expect(verifyResponse.body.name).toBe(newRecipeData.name);
|
|
});
|
|
it('should allow an authenticated user to update their own recipe', async () => {
|
|
const recipeUpdates = {
|
|
name: 'Updated Integration Test Recipe',
|
|
instructions: '1. Do the new thing. 2. Do the other new thing.',
|
|
};
|
|
|
|
const response = await request
|
|
.put(`/api/users/recipes/${testRecipe.recipe_id}`) // Authenticated recipe update endpoint
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(recipeUpdates);
|
|
|
|
// Assert the response from the PUT request
|
|
expect(response.status).toBe(200);
|
|
const updatedRecipe: Recipe = response.body;
|
|
expect(updatedRecipe.name).toBe(recipeUpdates.name);
|
|
expect(updatedRecipe.instructions).toBe(recipeUpdates.instructions);
|
|
|
|
// Verify the changes were persisted by fetching the recipe again
|
|
const verifyResponse = await request.get(`/api/recipes/${testRecipe.recipe_id}`);
|
|
expect(verifyResponse.status).toBe(200);
|
|
expect(verifyResponse.body.name).toBe(recipeUpdates.name);
|
|
});
|
|
it.todo("should prevent a user from updating another user's recipe");
|
|
it.todo('should allow an authenticated user to delete their own recipe');
|
|
it.todo("should prevent a user from deleting another user's recipe");
|
|
it.todo('should allow an authenticated user to post a comment on a recipe');
|
|
it.todo('should allow an authenticated user to fork a recipe');
|
|
|
|
describe('POST /api/recipes/suggest', () => {
|
|
it('should return a recipe suggestion based on ingredients', async () => {
|
|
const ingredients = ['chicken', 'rice', 'broccoli'];
|
|
const mockSuggestion = 'Chicken and Broccoli Stir-fry with Rice';
|
|
vi.mocked(aiService.generateRecipeSuggestion).mockResolvedValue(mockSuggestion);
|
|
|
|
const response = await request
|
|
.post('/api/recipes/suggest')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ ingredients });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body).toEqual({ suggestion: mockSuggestion });
|
|
expect(aiService.generateRecipeSuggestion).toHaveBeenCalledWith(
|
|
ingredients,
|
|
expect.anything(),
|
|
);
|
|
});
|
|
});
|
|
});
|