bp_wb/backend/api/pages.test.ts

234 lines
7 KiB
TypeScript

// WB-006: Page Config Storage - Tests First (TDD)
import { describe, it, expect, beforeEach } from '@jest/globals';
import { initPagesAPI, createPagesRouter } from './pages';
import express from 'express';
import request from 'supertest';
describe('Page Config Storage API', () => {
let app: express.Application;
let mockBosa: any;
beforeEach(() => {
// Mock BOSA SDK with proper query builder chain
const mockData: any[] = [];
let nextId = 1;
mockBosa = {
init: async () => Promise.resolve(),
log: {
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
},
db: {
query: (table: string) => {
const createQueryBuilder = (conditions: any[] = []) => {
const builder: any = {
where: (col: string, op: string, val: any) => {
return createQueryBuilder([...conditions, { col, op, val }]);
},
first: async () => {
let results = [...mockData];
conditions.forEach((cond) => {
results = results.filter((item) => item[cond.col] === cond.val);
});
return results[0] || null;
},
get: async () => {
let results = [...mockData];
conditions.forEach((cond) => {
results = results.filter((item) => item[cond.col] === cond.val);
});
return results;
},
update: async (data: any) => {
let results = [...mockData];
conditions.forEach((cond) => {
results = results.filter((item) => item[cond.col] === cond.val);
});
results.forEach((item) => {
Object.assign(item, data);
});
},
delete: async () => {
let indices: number[] = [];
mockData.forEach((item, idx) => {
let matches = true;
conditions.forEach((cond) => {
if (item[cond.col] !== cond.val) matches = false;
});
if (matches) indices.push(idx);
});
indices.reverse().forEach((idx) => mockData.splice(idx, 1));
return indices.length;
},
insert: async (data: any) => {
const id = nextId++;
const newItem = { ...data, id };
mockData.push(newItem);
return id;
},
};
return builder;
};
return createQueryBuilder();
},
},
};
// Clear mock data before each test
mockData.length = 0;
nextId = 1;
initPagesAPI(mockBosa);
app = express();
app.use(express.json());
app.use('/api/pages', createPagesRouter());
});
describe('POST /api/pages', () => {
it('should create a new page with valid config', async () => {
const response = await request(app)
.post('/api/pages')
.send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('id');
expect(response.body.app_name).toBe('test-app');
expect(response.body.route_path).toBe('/home');
});
it('should reject invalid page config', async () => {
const response = await request(app).post('/api/pages').send({
app_name: 'test-app',
// missing route_path and page_config
});
expect(response.status).toBe(400);
});
it('should enforce unique app_name and route_path combination', async () => {
await request(app).post('/api/pages').send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
const response = await request(app).post('/api/pages').send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
expect(response.status).toBe(409);
});
});
describe('GET /api/pages/:id', () => {
it('should retrieve page by id', async () => {
const createRes = await request(app).post('/api/pages').send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
const response = await request(app).get(
`/api/pages/${createRes.body.id}`
);
expect(response.status).toBe(200);
expect(response.body.id).toBe(createRes.body.id);
});
it('should return 404 for non-existent page', async () => {
const response = await request(app).get('/api/pages/999');
expect(response.status).toBe(404);
});
});
describe('PUT /api/pages/:id', () => {
it('should update existing page config', async () => {
const createRes = await request(app).post('/api/pages').send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
const response = await request(app)
.put(`/api/pages/${createRes.body.id}`)
.send({
page_config: { components: [{ type: 'Button' }] },
});
expect(response.status).toBe(200);
expect(response.body.page_config.components).toHaveLength(1);
});
it('should increment version on update', async () => {
const createRes = await request(app).post('/api/pages').send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
const response = await request(app)
.put(`/api/pages/${createRes.body.id}`)
.send({
page_config: { components: [] },
});
expect(response.body.version).toBe(2);
});
});
describe('DELETE /api/pages/:id', () => {
it('should delete page by id', async () => {
const createRes = await request(app).post('/api/pages').send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
const response = await request(app).delete(
`/api/pages/${createRes.body.id}`
);
expect(response.status).toBe(204);
});
});
describe('GET /api/pages', () => {
it('should list all pages', async () => {
await request(app).post('/api/pages').send({
app_name: 'test-app',
route_path: '/home',
page_config: { components: [] },
});
const response = await request(app).get('/api/pages');
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
});
it('should filter pages by app_name', async () => {
await request(app).post('/api/pages').send({
app_name: 'app1',
route_path: '/home',
page_config: { components: [] },
});
const response = await request(app).get('/api/pages?app_name=app1');
expect(response.status).toBe(200);
expect(response.body.every((p: any) => p.app_name === 'app1')).toBe(
true
);
});
});
});