Skip to main content
Glama
v2.3.test.js9.8 kB
const request = require('supertest'); const { createApp } = require('./server.js'); describe('v2.3 Plan Editing API', () => { let app; let db; beforeAll(async () => { const { app: localApp, db: localDb } = await createApp({ skipMigration: true }); app = localApp; db = localDb; // Insert a test plan await new Promise((resolve, reject) => { db.run( `INSERT INTO plans (title, description, status, changelog, timestamp, created_at, last_modified_by, last_modified_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, ['Test Plan', 'Test Description', 'proposed', '[]', new Date().toISOString(), Date.now(), 'agent', Date.now()], function(err) { if (err) reject(err); else resolve(this.lastID); } ); }); }); afterAll(async () => { if (db) { await new Promise((resolve, reject) => { db.run('DELETE FROM plans', (err) => { if (err) reject(err); else resolve(); }); }); db.close(); } }); describe('PUT /plans/:id', () => { it('should update title partially and set last_modified_by to human', async () => { const planId = 1; const response = await request(app) .put(`/plans/${planId}`) .send({ title: 'Updated Title' }) .set('Content-Type', 'application/json') .expect(200); expect(response.body.title).toBe('Updated Title'); expect(response.body.description).toBe('Test Description'); expect(response.body.last_modified_by).toBe('human'); expect(response.body.last_modified_at).toBeGreaterThanOrEqual(Date.now() - 1000); expect(response.body.id).toBe(planId); }); it('should update description partially', async () => { const planId = 1; const response = await request(app) .put(`/plans/${planId}`) .send({ description: 'Updated Description' }) .set('Content-Type', 'application/json') .expect(200); expect(response.body.description).toBe('Updated Description'); expect(response.body.last_modified_by).toBe('human'); }); it('should update both title and description', async () => { const planId = 1; const response = await request(app) .put(`/plans/${planId}`) .send({ title: 'New Title', description: 'New Description' }) .set('Content-Type', 'application/json') .expect(200); expect(response.body.title).toBe('New Title'); expect(response.body.description).toBe('New Description'); expect(response.body.last_modified_by).toBe('human'); }); it('should return 400 for empty title', async () => { const response = await request(app) .put('/plans/1') .send({ title: '' }) .set('Content-Type', 'application/json') .expect(400); expect(response.body.error).toBe('Title cannot be empty if provided'); }); it('should return 400 if no fields provided', async () => { const response = await request(app) .put('/plans/1') .send({}) .set('Content-Type', 'application/json') .expect(400); expect(response.body.error).toBe('At least one field must be provided'); }); it('should return 404 for non-existent plan', async () => { const response = await request(app) .put('/plans/999') .send({ title: 'Test' }) .set('Content-Type', 'application/json') .expect(404); expect(response.body.error).toBe('Plan not found'); }); it('should return full updated plan including all fields', async () => { const planId = 1; const response = await request(app) .put(`/plans/${planId}`) .send({ title: 'Full Update' }) .set('Content-Type', 'application/json') .expect(200); expect(response.body).toHaveProperty('id'); expect(response.body).toHaveProperty('title'); expect(response.body).toHaveProperty('description'); expect(response.body).toHaveProperty('status'); expect(response.body).toHaveProperty('timestamp'); expect(response.body).toHaveProperty('created_at'); expect(response.body).toHaveProperty('last_modified_at'); expect(response.body).toHaveProperty('last_modified_by'); expect(response.body).toHaveProperty('changelog'); }); }); describe('Integration with GET /plans/:id', () => { it('should reflect PUT changes in GET', async () => { const planId = 1; await request(app) .put(`/plans/${planId}`) .send({ title: 'Integrated Title' }) .set('Content-Type', 'application/json') .expect(200); const getResponse = await request(app) .get(`/plans/${planId}`) .expect(200); expect(getResponse.body.title).toBe('Integrated Title'); expect(getResponse.body.last_modified_by).toBe('human'); }); }); describe('Agent updates via PATCH', () => { it('PATCH /plans/:id should set last_modified_by to agent', async () => { const planId = 1; const response = await request(app) .patch(`/plans/${planId}`) .send({ status: 'in_progress' }) .set('Content-Type', 'application/json') .expect(200); expect(response.body.status).toBe('in_progress'); const getResponse = await request(app) .get(`/plans/${planId}`) .expect(200); expect(getResponse.body.last_modified_by).toBe('agent'); expect(getResponse.body.last_modified_at).toBeGreaterThanOrEqual(Date.now() - 1000); }); it('PATCH /plans/:id/changelog should set last_modified_by to agent', async () => { const planId = 1; const response = await request(app) .patch(`/plans/${planId}/changelog`) .send({ change: 'Agent update' }) .set('Content-Type', 'application/json') .expect(200); expect(response.body.changelog).toHaveLength(1); expect(response.body.changelog[0].change).toBe('Agent update'); const getResponse = await request(app) .get(`/plans/${planId}`) .expect(200); expect(getResponse.body.last_modified_by).toBe('agent'); }); }); describe('Backfill for existing data', () => { beforeAll(async () => { // Insert a plan without new fields (simulating pre-v2.3) await new Promise((resolve, reject) => { db.run( `INSERT INTO plans (title, description, status, changelog, timestamp, created_at) VALUES (?, ?, ?, ?, ?, ?)`, ['Old Plan', 'Old Description', 'proposed', '[]', new Date().toISOString(), Date.now()], function(err) { if (err) reject(err); else resolve(this.lastID); } ); }); // Simulate backfill migration await new Promise((res, rej) => { db.run("UPDATE plans SET last_modified_by = 'agent' WHERE last_modified_by IS NULL", (err) => { if (err) rej(err); else res(); }); }); await new Promise((res, rej) => { db.run("UPDATE plans SET last_modified_at = created_at WHERE last_modified_at IS NULL", (err) => { if (err) rej(err); else res(); }); }); }); it('should have backfilled last_modified_by and last_modified_at after migration', async () => { const response = await request(app) .get('/plans') .expect(200); console.log(`Test backfill: GET /plans response body:`, response.body); const oldPlan = response.body.find(p => p.title === 'Old Plan'); expect(oldPlan).toBeDefined(); expect(oldPlan.last_modified_by).toBe('agent'); expect(oldPlan.last_modified_at).toBeDefined(); expect(oldPlan.last_modified_at).toEqual(oldPlan.created_at); }); }); describe('Compatibility with filters', () => { it('GET /plans with ?status should include updated plans', async () => { // Create a new proposed plan for this test const newPlanRes = await request(app) .post('/plans') .send({ title: 'To Update', description: 'Desc' }) .set('Content-Type', 'application/json') .expect(201); const newPlanId = newPlanRes.body.id; await request(app) .put(`/plans/${newPlanId}`) .send({ title: 'Filtered Title' }) .set('Content-Type', 'application/json') .expect(200); const response = await request(app) .get('/plans?status=proposed') .expect(200); expect(response.body.length).toBeGreaterThan(0); const updatedPlan = response.body.find(p => p.title === 'Filtered Title'); expect(updatedPlan).toBeDefined(); expect(updatedPlan.last_modified_by).toBe('human'); }); it('GET /plans with ?since should include updated plans', async () => { const since = Date.now() - 10000; // 10s ago const response = await request(app) .get(`/plans?since=${since}`) .expect(200); expect(response.body.length).toBeGreaterThan(0); expect(response.body.some(p => p.last_modified_by === 'human')).toBe(true); }); }); describe('POST /plans sets agent fields', () => { it('should set last_modified_by and last_modified_at to agent on create', async () => { const response = await request(app) .post('/plans') .send({ title: 'New Plan', description: 'New Description' }) .set('Content-Type', 'application/json') .expect(201); expect(response.body.id).toBeDefined(); const getResponse = await request(app) .get(`/plans/${response.body.id}`) .expect(200); expect(getResponse.body.last_modified_by).toBe('agent'); expect(getResponse.body.last_modified_at).toEqual(getResponse.body.created_at); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/suttonwilliamd/tpc-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server