diff --git a/backend/tests/integration/cart.integration.test.ts b/backend/tests/integration/cart.integration.test.ts new file mode 100644 index 0000000..19a69c8 --- /dev/null +++ b/backend/tests/integration/cart.integration.test.ts @@ -0,0 +1,118 @@ +import request from 'supertest'; +import app from '../../src/app'; +import { pool } from '../../src/db'; +import { resetDb, closeDb } from './setup/testDb'; + +beforeEach(async () => { + await resetDb(); +}); + +afterAll(async () => { + await pool.end(); + await closeDb(); +}); + +async function registerAndGetAgent(email: string) { + const agent = request.agent(app); + await agent.post('/api/customers/register').send({ email, password: 'supersecret123' }); + return agent; +} + +describe('cart', () => { + it('adding an item to cart marks it reserved', async () => { + const { rows } = await pool.query(`INSERT INTO items (name, price_cents) VALUES ('Test Item', 1000) RETURNING id`); + const itemId = rows[0].id; + const agent = await registerAndGetAgent('cart1@example.com'); + + const addRes = await agent.post(`/api/cart/items/${itemId}`); + expect(addRes.status).toBe(201); + + const itemCheck = await request(app).get(`/api/items/${itemId}`); + expect(itemCheck.body.status).toBe('reserved'); + + const cartRes = await agent.get('/api/cart'); + expect(cartRes.body.items).toHaveLength(1); + expect(cartRes.body.items[0].item_id).toBe(itemId); + }); + + it('refuses to add an item that is already reserved', async () => { + const { rows } = await pool.query(`INSERT INTO items (name, price_cents) VALUES ('Test Item', 1000) RETURNING id`); + const itemId = rows[0].id; + const agentA = await registerAndGetAgent('cartA@example.com'); + const agentB = await registerAndGetAgent('cartB@example.com'); + + await agentA.post(`/api/cart/items/${itemId}`); + const res = await agentB.post(`/api/cart/items/${itemId}`); + expect(res.status).toBe(409); + }); + + it('removing an item from cart releases it back to available', async () => { + const { rows } = await pool.query(`INSERT INTO items (name, price_cents) VALUES ('Test Item', 1000) RETURNING id`); + const itemId = rows[0].id; + const agent = await registerAndGetAgent('cart2@example.com'); + + await agent.post(`/api/cart/items/${itemId}`); + const removeRes = await agent.delete(`/api/cart/items/${itemId}`); + expect(removeRes.status).toBe(204); + + const itemCheck = await request(app).get(`/api/items/${itemId}`); + expect(itemCheck.body.status).toBe('available'); + }); +}); + +describe('cart demo checkout', () => { + async function addShippingAddress(agent: any) { + const res = await agent.post('/api/customers/me/addresses').send({ + fullName: 'Jane Doe', + addressLine1: '123 Main St', + city: 'Springfield', + state: 'IL', + postalCode: '62701' + }); + return res.body.address.id; + } + + it('completes a multi-item cart purchase and marks all items sold', async () => { + const { rows } = await pool.query( + `INSERT INTO items (name, price_cents) VALUES ('Item A', 1000), ('Item B', 2000) RETURNING id` + ); + const [itemA, itemB] = rows; + const agent = await registerAndGetAgent('checkout1@example.com'); + + await agent.post(`/api/cart/items/${itemA.id}`); + await agent.post(`/api/cart/items/${itemB.id}`); + const addressId = await addShippingAddress(agent); + + const res = await agent.post('/api/checkout/cart/demo/purchase').send({ shippingAddressId: addressId }); + expect(res.status).toBe(200); + expect(res.body.status).toBe('completed'); + + const checkA = await request(app).get(`/api/items/${itemA.id}`); + const checkB = await request(app).get(`/api/items/${itemB.id}`); + expect(checkA.body.status).toBe('sold'); + expect(checkB.body.status).toBe('sold'); + + const cartRes = await agent.get('/api/cart'); + expect(cartRes.body.items).toHaveLength(0); + + const ordersRes = await agent.get('/api/customers/me/orders'); + expect(ordersRes.body).toHaveLength(2); + }); + + it('refuses checkout without a shipping address', async () => { + const { rows } = await pool.query(`INSERT INTO items (name, price_cents) VALUES ('Item A', 1000) RETURNING id`); + const agent = await registerAndGetAgent('checkout2@example.com'); + await agent.post(`/api/cart/items/${rows[0].id}`); + + const res = await agent.post('/api/checkout/cart/demo/purchase').send({}); + expect(res.status).toBe(400); + }); + + it('refuses checkout with an empty cart', async () => { + const agent = await registerAndGetAgent('checkout3@example.com'); + const addressId = await addShippingAddress(agent); + + const res = await agent.post('/api/checkout/cart/demo/purchase').send({ shippingAddressId: addressId }); + expect(res.status).toBe(400); + }); +}); \ No newline at end of file diff --git a/backend/tests/integration/items.integration.test.ts b/backend/tests/integration/items.integration.test.ts deleted file mode 100755 index 4b2583e..0000000 --- a/backend/tests/integration/items.integration.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import request from 'supertest'; -import app from '../../src/app'; -import { pool } from '../../src/db'; -import { resetDb, closeDb } from './setup/testDb'; - -beforeEach(async () => { - await resetDb(); -}); - -afterAll(async () => { - await pool.end(); - await closeDb(); -}); - -describe('GET /api/items', () => { - it('returns an empty array when there are no items', async () => { - const res = await request(app).get('/api/items'); - expect(res.status).toBe(200); - expect(res.body).toEqual([]); - }); - - it('returns items with an empty images array when none are attached', async () => { - await pool.query( - `INSERT INTO items (name, description, price_cents) VALUES ('Vintage Lamp', 'A one-of-a-kind lamp.', 4500)` - ); - const res = await request(app).get('/api/items'); - expect(res.status).toBe(200); - expect(res.body).toHaveLength(1); - expect(res.body[0]).toMatchObject({ - name: 'Vintage Lamp', - price_cents: 4500, - status: 'available', - images: [] - }); - }); - - it('returns 404 for a nonexistent item', async () => { - const res = await request(app).get('/api/items/99999'); - expect(res.status).toBe(404); - }); -}); - -describe('demo checkout', () => { - it('marks an available item as sold', async () => { - const { rows } = await pool.query( - `INSERT INTO items (name, price_cents) VALUES ('Test Item', 1000) RETURNING id` - ); - const itemId = rows[0].id; - - const res = await request(app).post(`/api/checkout/demo/${itemId}/purchase`); - expect(res.status).toBe(200); - expect(res.body.status).toBe('sold'); - - const check = await request(app).get(`/api/items/${itemId}`); - expect(check.body.status).toBe('sold'); - }); - - it('refuses to sell an item that is already sold', async () => { - const { rows } = await pool.query( - `INSERT INTO items (name, price_cents, status) VALUES ('Already Sold', 1000, 'sold') RETURNING id` - ); - const itemId = rows[0].id; - const res = await request(app).post(`/api/checkout/demo/${itemId}/purchase`); - expect(res.status).toBe(409); - }); -});