import request from 'supertest'; import { createApp } from '../../app'; import db from '../../db/connection'; import { createTestUser, cleanupTestData } from '../helpers'; import { signToken } from '../../utils/jwt'; const app = createApp(); let userId: string; let token: string; beforeEach(async () => { await cleanupTestData(); const { user } = await createTestUser(); userId = user.id; token = signToken(userId).token; }); afterAll(async () => { await cleanupTestData(); await db.destroy(); }); describe('GET /v1/pantry', () => { it('returns empty pantry for new user', async () => { const res = await request(app) .get('/v1/pantry') .set('Authorization', `Bearer ${token}`); expect(res.status).toBe(200); expect(res.body.items).toEqual([]); expect(res.body.synced_at).toBeDefined(); }); it('returns 401 without token', async () => { const res = await request(app).get('/v1/pantry'); expect(res.status).toBe(401); }); }); describe('POST /v1/pantry', () => { it('adds a new pantry item', async () => { const res = await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'Flour', quantity: 5 }); expect(res.status).toBe(201); expect(res.body.item.item_name).toBe('Flour'); expect(res.body.item.quantity).toBe(5); }); it('returns 409 for duplicate item (case-insensitive)', async () => { await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'Flour', quantity: 5 }); const res = await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'flour', quantity: 3 }); expect(res.status).toBe(409); expect(res.body.code).toBe('DUPLICATE_ITEM'); }); it('returns 400 for invalid quantity', async () => { const res = await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'Flour', quantity: 0 }); expect(res.status).toBe(400); }); it('returns 400 for fractional quantity', async () => { const res = await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'Flour', quantity: 1.5 }); expect(res.status).toBe(400); }); }); describe('PUT /v1/pantry/:item_id', () => { it('updates pantry item quantity', async () => { const createRes = await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'Butter', quantity: 2 }); const itemId = createRes.body.item.id; const res = await request(app) .put(`/v1/pantry/${itemId}`) .set('Authorization', `Bearer ${token}`) .send({ quantity: 7 }); expect(res.status).toBe(200); expect(res.body.item.quantity).toBe(7); }); it('returns 404 for non-existent item', async () => { const res = await request(app) .put('/v1/pantry/00000000-0000-0000-0000-000000000000') .set('Authorization', `Bearer ${token}`) .send({ quantity: 5 }); expect(res.status).toBe(404); }); it('cannot update another user\'s item', async () => { const { user: otherUser } = await createTestUser({ email: 'other@example.com' }); const otherToken = signToken(otherUser.id).token; const createRes = await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'Sugar', quantity: 3 }); const itemId = createRes.body.item.id; const res = await request(app) .put(`/v1/pantry/${itemId}`) .set('Authorization', `Bearer ${otherToken}`) .send({ quantity: 10 }); expect(res.status).toBe(404); }); }); describe('DELETE /v1/pantry/:item_id', () => { it('deletes a pantry item', async () => { const createRes = await request(app) .post('/v1/pantry') .set('Authorization', `Bearer ${token}`) .send({ item_name: 'Salt', quantity: 1 }); const itemId = createRes.body.item.id; const res = await request(app) .delete(`/v1/pantry/${itemId}`) .set('Authorization', `Bearer ${token}`); expect(res.status).toBe(204); // Verify tombstone created const tombstone = await db('deleted_records') .where({ record_id: itemId, table_name: 'pantry_items' }) .first(); expect(tombstone).toBeDefined(); }); it('returns 404 for non-existent item', async () => { const res = await request(app) .delete('/v1/pantry/00000000-0000-0000-0000-000000000000') .set('Authorization', `Bearer ${token}`); expect(res.status).toBe(404); }); });