|
|
|
@@ -0,0 +1,452 @@
|
|
|
|
|
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 createCategory(name: string, parentId: number | null = null): Promise<number> {
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name, parent_id: parentId });
|
|
|
|
|
expect(res.status).toBe(201);
|
|
|
|
|
return res.body.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createTag(name: string): Promise<number> {
|
|
|
|
|
const res = await request(app).post('/api/admin/tags').send({ name });
|
|
|
|
|
expect(res.status).toBe(201);
|
|
|
|
|
return res.body.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createItem(
|
|
|
|
|
name: string,
|
|
|
|
|
priceCents: number,
|
|
|
|
|
categoryId: number | null = null,
|
|
|
|
|
tagIds: number[] = []
|
|
|
|
|
): Promise<number> {
|
|
|
|
|
const { rows } = await pool.query(
|
|
|
|
|
`INSERT INTO items (name, price_cents, category_id) VALUES ($1, $2, $3) RETURNING id`,
|
|
|
|
|
[name, priceCents, categoryId]
|
|
|
|
|
);
|
|
|
|
|
const itemId = rows[0].id;
|
|
|
|
|
for (const tagId of tagIds) {
|
|
|
|
|
await pool.query(`INSERT INTO item_tags (item_id, tag_id) VALUES ($1, $2)`, [itemId, tagId]);
|
|
|
|
|
}
|
|
|
|
|
return itemId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('admin categories', () => {
|
|
|
|
|
it('creates a root category', async () => {
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name: 'Furniture' });
|
|
|
|
|
expect(res.status).toBe(201);
|
|
|
|
|
expect(res.body.name).toBe('Furniture');
|
|
|
|
|
expect(res.body.parent_id).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('creates a nested category', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name: 'Tables', parent_id: furniture });
|
|
|
|
|
expect(res.status).toBe(201);
|
|
|
|
|
expect(res.body.parent_id).toBe(furniture);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects two root categories with the same name', async () => {
|
|
|
|
|
await createCategory('Furniture');
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name: 'furniture' });
|
|
|
|
|
expect(res.status).toBe(409);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects two siblings with the same name', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
await createCategory('Tables', furniture);
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name: 'Tables', parent_id: furniture });
|
|
|
|
|
expect(res.status).toBe(409);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('allows the same name under different parents', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const decor = await createCategory('Decor');
|
|
|
|
|
await createCategory('Vintage', furniture);
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name: 'Vintage', parent_id: decor });
|
|
|
|
|
expect(res.status).toBe(201);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects a category with a blank name', async () => {
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name: ' ' });
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects a parent that does not exist', async () => {
|
|
|
|
|
const res = await request(app).post('/api/admin/categories').send({ name: 'Orphan', parent_id: 9999 });
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('lists categories with the number of items in each', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
await createItem('Chair', 1000, furniture);
|
|
|
|
|
await createItem('Stool', 2000, furniture);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get('/api/admin/categories');
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
const found = res.body.find((c: { id: number }) => c.id === furniture);
|
|
|
|
|
expect(found.item_count).toBe(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renames a category', async () => {
|
|
|
|
|
const id = await createCategory('Furnature');
|
|
|
|
|
const res = await request(app).put(`/api/admin/categories/${id}`).send({ name: 'Furniture' });
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.name).toBe('Furniture');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reparents a category', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const tables = await createCategory('Tables');
|
|
|
|
|
const res = await request(app).put(`/api/admin/categories/${tables}`).send({ parent_id: furniture });
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.parent_id).toBe(furniture);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('refuses to make a category its own parent', async () => {
|
|
|
|
|
const id = await createCategory('Furniture');
|
|
|
|
|
const res = await request(app).put(`/api/admin/categories/${id}`).send({ parent_id: id });
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('refuses to move a category beneath its own descendant', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const tables = await createCategory('Tables', furniture);
|
|
|
|
|
const coffee = await createCategory('Coffee Tables', tables);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).put(`/api/admin/categories/${furniture}`).send({ parent_id: coffee });
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
|
|
|
|
|
// The tree must be untouched after a rejected move.
|
|
|
|
|
const check = await pool.query(`SELECT parent_id FROM categories WHERE id = $1`, [furniture]);
|
|
|
|
|
expect(check.rows[0].parent_id).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('deletes a category and reports what it affected', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const tables = await createCategory('Tables', furniture);
|
|
|
|
|
await createItem('Chair', 1000, furniture);
|
|
|
|
|
await createItem('Coffee table', 2000, tables);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).delete(`/api/admin/categories/${furniture}`);
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.deleted_categories).toBe(2);
|
|
|
|
|
expect(res.body.uncategorized_items).toBe(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('keeps items when their category is deleted, merely uncategorizing them', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const tables = await createCategory('Tables', furniture);
|
|
|
|
|
const itemId = await createItem('Coffee table', 2000, tables);
|
|
|
|
|
|
|
|
|
|
await request(app).delete(`/api/admin/categories/${furniture}`);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items/${itemId}`);
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.category_id).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('admin tags', () => {
|
|
|
|
|
it('creates a tag with an auto-assigned colour', async () => {
|
|
|
|
|
const res = await request(app).post('/api/admin/tags').send({ name: 'vintage' });
|
|
|
|
|
expect(res.status).toBe(201);
|
|
|
|
|
expect(res.body.name).toBe('vintage');
|
|
|
|
|
expect(typeof res.body.color).toBe('string');
|
|
|
|
|
expect(res.body.color.length).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('honours an explicit colour on create', async () => {
|
|
|
|
|
const res = await request(app).post('/api/admin/tags').send({ name: 'vintage', color: 'purple' });
|
|
|
|
|
expect(res.status).toBe(201);
|
|
|
|
|
expect(res.body.color).toBe('purple');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects a colour outside the palette', async () => {
|
|
|
|
|
const res = await request(app).post('/api/admin/tags').send({ name: 'vintage', color: 'chartreuse' });
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects a duplicate tag name regardless of case', async () => {
|
|
|
|
|
await createTag('vintage');
|
|
|
|
|
const res = await request(app).post('/api/admin/tags').send({ name: 'VINTAGE' });
|
|
|
|
|
expect(res.status).toBe(409);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects a blank tag name', async () => {
|
|
|
|
|
const res = await request(app).post('/api/admin/tags').send({ name: ' ' });
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('lists tags with the number of items carrying each', async () => {
|
|
|
|
|
const vintage = await createTag('vintage');
|
|
|
|
|
await createTag('unused');
|
|
|
|
|
await createItem('Chair', 1000, null, [vintage]);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get('/api/admin/tags');
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
const found = res.body.find((t: { id: number }) => t.id === vintage);
|
|
|
|
|
expect(found.item_count).toBe(1);
|
|
|
|
|
const unused = res.body.find((t: { name: string }) => t.name === 'unused');
|
|
|
|
|
expect(unused.item_count).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('overrides a tag colour', async () => {
|
|
|
|
|
const id = await createTag('vintage');
|
|
|
|
|
const res = await request(app).put(`/api/admin/tags/${id}`).send({ color: 'geekblue' });
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.color).toBe('geekblue');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renames a tag', async () => {
|
|
|
|
|
const id = await createTag('vintge');
|
|
|
|
|
const res = await request(app).put(`/api/admin/tags/${id}`).send({ name: 'vintage' });
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.name).toBe('vintage');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('deleting a tag detaches it from items without deleting them', async () => {
|
|
|
|
|
const vintage = await createTag('vintage');
|
|
|
|
|
const itemId = await createItem('Chair', 1000, null, [vintage]);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).delete(`/api/admin/tags/${vintage}`);
|
|
|
|
|
expect(res.status).toBe(204);
|
|
|
|
|
|
|
|
|
|
const item = await request(app).get(`/api/items/${itemId}`);
|
|
|
|
|
expect(item.status).toBe(200);
|
|
|
|
|
expect(item.body.tags).toEqual([]);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('admin item form', () => {
|
|
|
|
|
it('saves a category and creates unknown tags on the fly', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
|
|
|
|
|
const res = await request(app)
|
|
|
|
|
.post('/api/admin/items')
|
|
|
|
|
.field('name', 'Oak table')
|
|
|
|
|
.field('description', '')
|
|
|
|
|
.field('price', '340')
|
|
|
|
|
.field('category_id', String(furniture))
|
|
|
|
|
.field('tags', JSON.stringify(['vintage', 'oak']));
|
|
|
|
|
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.category_id).toBe(furniture);
|
|
|
|
|
expect(res.body.tags.map((t: { name: string }) => t.name).sort()).toEqual(['oak', 'vintage']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reuses an existing tag rather than duplicating it', async () => {
|
|
|
|
|
await createTag('vintage');
|
|
|
|
|
|
|
|
|
|
await request(app)
|
|
|
|
|
.post('/api/admin/items')
|
|
|
|
|
.field('name', 'Oak table')
|
|
|
|
|
.field('description', '')
|
|
|
|
|
.field('price', '340')
|
|
|
|
|
.field('tags', JSON.stringify(['VINTAGE']));
|
|
|
|
|
|
|
|
|
|
const { rows } = await pool.query(`SELECT COUNT(*)::int AS n FROM tags`);
|
|
|
|
|
expect(rows[0].n).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('replaces an item\'s tags on update rather than appending', async () => {
|
|
|
|
|
const create = await request(app)
|
|
|
|
|
.post('/api/admin/items')
|
|
|
|
|
.field('name', 'Oak table')
|
|
|
|
|
.field('description', '')
|
|
|
|
|
.field('price', '340')
|
|
|
|
|
.field('tags', JSON.stringify(['vintage', 'oak']));
|
|
|
|
|
|
|
|
|
|
const res = await request(app)
|
|
|
|
|
.put(`/api/admin/items/${create.body.id}`)
|
|
|
|
|
.field('name', 'Oak table')
|
|
|
|
|
.field('description', '')
|
|
|
|
|
.field('price', '340')
|
|
|
|
|
.field('tags', JSON.stringify(['oak']));
|
|
|
|
|
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.tags.map((t: { name: string }) => t.name)).toEqual(['oak']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('clears the category when an empty category_id is submitted', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const create = await request(app)
|
|
|
|
|
.post('/api/admin/items')
|
|
|
|
|
.field('name', 'Oak table')
|
|
|
|
|
.field('description', '')
|
|
|
|
|
.field('price', '340')
|
|
|
|
|
.field('category_id', String(furniture));
|
|
|
|
|
|
|
|
|
|
const res = await request(app)
|
|
|
|
|
.put(`/api/admin/items/${create.body.id}`)
|
|
|
|
|
.field('name', 'Oak table')
|
|
|
|
|
.field('description', '')
|
|
|
|
|
.field('price', '340')
|
|
|
|
|
.field('category_id', '');
|
|
|
|
|
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.category_id).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns each image exactly once for an item that also has tags', async () => {
|
|
|
|
|
const create = await request(app)
|
|
|
|
|
.post('/api/admin/items')
|
|
|
|
|
.field('name', 'Oak table')
|
|
|
|
|
.field('description', '')
|
|
|
|
|
.field('price', '340')
|
|
|
|
|
.field('tags', JSON.stringify(['vintage', 'oak', 'restored']));
|
|
|
|
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
`INSERT INTO item_images (item_id, image_path, sort_order) VALUES ($1, '/uploads/a.jpg', 0), ($1, '/uploads/b.jpg', 1)`,
|
|
|
|
|
[create.body.id]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items/${create.body.id}`);
|
|
|
|
|
expect(res.body.images).toHaveLength(2);
|
|
|
|
|
expect(res.body.tags).toHaveLength(3);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('GET /api/filters', () => {
|
|
|
|
|
it('returns the category tree, tags, and the catalogue price range', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
await createCategory('Tables', furniture);
|
|
|
|
|
await createTag('vintage');
|
|
|
|
|
await createItem('Cheap', 1200);
|
|
|
|
|
await createItem('Dear', 80000);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get('/api/filters');
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.categories).toHaveLength(2);
|
|
|
|
|
expect(res.body.tags).toHaveLength(1);
|
|
|
|
|
expect(res.body.priceRange).toEqual({ min_cents: 1200, max_cents: 80000 });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns a zeroed price range for an empty catalogue', async () => {
|
|
|
|
|
const res = await request(app).get('/api/filters');
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body.priceRange).toEqual({ min_cents: 0, max_cents: 0 });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('GET /api/items filtering', () => {
|
|
|
|
|
it('returns every item when nothing is filtered', async () => {
|
|
|
|
|
await createItem('A', 1000);
|
|
|
|
|
await createItem('B', 2000);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get('/api/items');
|
|
|
|
|
expect(res.body).toHaveLength(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('matches a category and all of its descendants', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const tables = await createCategory('Tables', furniture);
|
|
|
|
|
const coffee = await createCategory('Coffee Tables', tables);
|
|
|
|
|
const decor = await createCategory('Decor');
|
|
|
|
|
|
|
|
|
|
await createItem('Deep', 1000, coffee);
|
|
|
|
|
await createItem('Mid', 1000, tables);
|
|
|
|
|
await createItem('Top', 1000, furniture);
|
|
|
|
|
await createItem('Elsewhere', 1000, decor);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items?category=${furniture}`);
|
|
|
|
|
expect(res.body.map((i: { name: string }) => i.name).sort()).toEqual(['Deep', 'Mid', 'Top']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('excludes uncategorized items from a category filter', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
await createItem('Filed', 1000, furniture);
|
|
|
|
|
await createItem('Loose', 1000, null);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items?category=${furniture}`);
|
|
|
|
|
expect(res.body.map((i: { name: string }) => i.name)).toEqual(['Filed']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('requires every listed tag, not merely one of them', async () => {
|
|
|
|
|
const vintage = await createTag('vintage');
|
|
|
|
|
const oak = await createTag('oak');
|
|
|
|
|
|
|
|
|
|
await createItem('Both', 1000, null, [vintage, oak]);
|
|
|
|
|
await createItem('Only vintage', 1000, null, [vintage]);
|
|
|
|
|
await createItem('Only oak', 1000, null, [oak]);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items?tags=${vintage},${oak}`);
|
|
|
|
|
expect(res.body.map((i: { name: string }) => i.name)).toEqual(['Both']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('matches items carrying extra tags beyond those selected', async () => {
|
|
|
|
|
const vintage = await createTag('vintage');
|
|
|
|
|
const oak = await createTag('oak');
|
|
|
|
|
const rare = await createTag('rare');
|
|
|
|
|
await createItem('Three tags', 1000, null, [vintage, oak, rare]);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items?tags=${vintage},${oak}`);
|
|
|
|
|
expect(res.body).toHaveLength(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('bounds the price range inclusively', async () => {
|
|
|
|
|
await createItem('Under', 900);
|
|
|
|
|
await createItem('Low edge', 1000);
|
|
|
|
|
await createItem('Middle', 3000);
|
|
|
|
|
await createItem('High edge', 5000);
|
|
|
|
|
await createItem('Over', 5100);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get('/api/items?min_price=1000&max_price=5000');
|
|
|
|
|
expect(res.body.map((i: { name: string }) => i.name).sort()).toEqual(['High edge', 'Low edge', 'Middle']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('combines category, tags, and price with AND', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const tables = await createCategory('Tables', furniture);
|
|
|
|
|
const vintage = await createTag('vintage');
|
|
|
|
|
|
|
|
|
|
await createItem('Match', 3000, tables, [vintage]);
|
|
|
|
|
await createItem('Wrong category', 3000, null, [vintage]);
|
|
|
|
|
await createItem('Wrong tag', 3000, tables, []);
|
|
|
|
|
await createItem('Wrong price', 9000, tables, [vintage]);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items?category=${furniture}&tags=${vintage}&min_price=1000&max_price=5000`);
|
|
|
|
|
expect(res.body.map((i: { name: string }) => i.name)).toEqual(['Match']);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('includes each item\'s tags and category name in the response', async () => {
|
|
|
|
|
const furniture = await createCategory('Furniture');
|
|
|
|
|
const vintage = await createTag('vintage');
|
|
|
|
|
await createItem('Chair', 1000, furniture, [vintage]);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get('/api/items');
|
|
|
|
|
expect(res.body[0].category_name).toBe('Furniture');
|
|
|
|
|
expect(res.body[0].tags[0].name).toBe('vintage');
|
|
|
|
|
expect(res.body[0].tags[0].color).toBeTruthy();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects a malformed category rather than silently returning everything', async () => {
|
|
|
|
|
await createItem('A', 1000);
|
|
|
|
|
const res = await request(app).get('/api/items?category=furniture');
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('rejects an inverted price range', async () => {
|
|
|
|
|
const res = await request(app).get('/api/items?min_price=5000&max_price=1000');
|
|
|
|
|
expect(res.status).toBe(400);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns an empty list for a category that matches nothing', async () => {
|
|
|
|
|
const empty = await createCategory('Empty');
|
|
|
|
|
await createItem('A', 1000);
|
|
|
|
|
|
|
|
|
|
const res = await request(app).get(`/api/items?category=${empty}`);
|
|
|
|
|
expect(res.status).toBe(200);
|
|
|
|
|
expect(res.body).toEqual([]);
|
|
|
|
|
});
|
|
|
|
|
});
|