234 lines
7 KiB
TypeScript
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
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|