Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
160 lines
6.1 KiB
JavaScript
160 lines
6.1 KiB
JavaScript
// API v3: Variables API Tests (ADR-026)
|
|
// Tests for GET/POST /api/v3/spaces/:id/variables
|
|
import { describe, test, expect, beforeEach, afterEach, beforeAll } from 'vitest';
|
|
import request from 'supertest';
|
|
import express from 'express';
|
|
import { dbAll, dbGet, dbRun, destroyAdapter, resetAdapter } from '../../database/connection.js';
|
|
import spacesRouter from '../../routes/v3/spaces.js';
|
|
// Mock auth middleware for tests
|
|
const mockAuthMiddleware = (req, res, next) => {
|
|
req.user = { id: 1, email: 'test@example.com', role: 'user' };
|
|
next();
|
|
};
|
|
|
|
// Create test app
|
|
function createTestApp() {
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use('/api/v3/spaces', mockAuthMiddleware, spacesRouter);
|
|
return app;
|
|
}
|
|
|
|
describe('API v3: Variables API (ADR-026)', () => {
|
|
let app;
|
|
let testSpaceId;
|
|
let variablesTableId;
|
|
|
|
beforeAll(() => {
|
|
app = createTestApp();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
process.env.TEST_MODE = 'true';
|
|
process.env.SKIP_DEV_USER = 'true';
|
|
await resetAdapter();
|
|
|
|
// Create test user first (unique email to avoid collision)
|
|
const testEmail = `var-test-${Date.now()}@test.com`;
|
|
const userResult = await dbRun(
|
|
`INSERT INTO users (email, password_hash, name, encryption_key_encrypted, email_verified) VALUES (?, ?, ?, ?, ?)`,
|
|
[testEmail, 'hash', 'Test User', 'encrypted_key', 1]
|
|
);
|
|
const testUserId = userResult.lastInsertRowid;
|
|
|
|
// Create test space (correct schema from init-v2.js)
|
|
await dbRun(
|
|
`INSERT INTO spaces (name, owner_id, type) VALUES (?, ?, ?)`,
|
|
['Test Space Vars', testUserId, 'business']
|
|
);
|
|
const space = await dbGet(`SELECT id FROM spaces WHERE name = 'Test Space Vars' AND owner_id = ?`, [testUserId]);
|
|
testSpaceId = space.id;
|
|
|
|
// Create system_data project (correct schema - no position column)
|
|
await dbRun(
|
|
`INSERT INTO projects (space_id, name, type, owner_id) VALUES (?, ?, ?, ?)`,
|
|
[testSpaceId, 'System Data', 'system_data', testUserId]
|
|
);
|
|
const project = await dbGet(`SELECT id FROM projects WHERE space_id = ? AND type = 'system_data'`, [testSpaceId]);
|
|
|
|
// Create Variables table (using universal_tables not workspace_tables)
|
|
await dbRun(
|
|
`INSERT INTO universal_tables (project_id, name, table_type) VALUES (?, ?, ?)`,
|
|
[project.id, 'Variables', 'config']
|
|
);
|
|
const table = await dbGet(`SELECT id FROM universal_tables WHERE project_id = ? AND name = 'Variables'`, [project.id]);
|
|
variablesTableId = table.id;
|
|
|
|
// Create required columns for Variables table (correct schema from init-v2.js)
|
|
const columns = [
|
|
{ column_name: 'name', type: 'text', config: JSON.stringify({ required: true }) },
|
|
{ column_name: 'scope_type', type: 'select', config: JSON.stringify({ options: ['space', 'table', 'dashboard'] }) },
|
|
{ column_name: 'scope_id', type: 'number', config: JSON.stringify({}) },
|
|
{ column_name: 'formula', type: 'textarea', config: JSON.stringify({}) },
|
|
{ column_name: 'cached_value', type: 'text', config: JSON.stringify({ readonly: true }) },
|
|
{ column_name: 'cached_at', type: 'datetime', config: JSON.stringify({ readonly: true }) },
|
|
{ column_name: 'dependencies', type: 'json', config: JSON.stringify({}) }
|
|
];
|
|
|
|
for (let i = 0; i < columns.length; i++) {
|
|
const col = columns[i];
|
|
await dbRun(
|
|
`INSERT INTO table_columns (table_id, column_name, type, config, order_index) VALUES (?, ?, ?, ?, ?)`,
|
|
[variablesTableId, col.column_name, col.type, col.config, i]
|
|
);
|
|
}
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await destroyAdapter();
|
|
});
|
|
|
|
describe('GET /api/v3/spaces/:spaceId/variables', () => {
|
|
test('should return empty list when no variables exist', async () => {
|
|
const res = await request(app).get(`/api/v3/spaces/${testSpaceId}/variables`);
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.success).toBe(true);
|
|
expect(res.body.data.variables).toEqual([]);
|
|
expect(res.body.data.tableId).toBe(variablesTableId);
|
|
});
|
|
|
|
test('should return variables from Variables table', async () => {
|
|
// Add a variable row with JSON data (table_rows uses data column)
|
|
const variableData = JSON.stringify({
|
|
name: '$tax_rate',
|
|
formula: '0.20',
|
|
scope_type: 'space'
|
|
});
|
|
|
|
await dbRun(
|
|
`INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, ?)`,
|
|
[variablesTableId, 'var_001', variableData]
|
|
);
|
|
|
|
const res = await request(app).get(`/api/v3/spaces/${testSpaceId}/variables`);
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.success).toBe(true);
|
|
expect(res.body.data.variables).toHaveLength(1);
|
|
expect(res.body.data.variables[0].name).toBe('$tax_rate');
|
|
expect(res.body.data.variables[0].formula).toBe('0.20');
|
|
expect(res.body.data.variables[0].scope).toBe('space'); // API uses 'scope' not 'scope_type'
|
|
});
|
|
|
|
test('should return 404 for non-existent space', async () => {
|
|
const res = await request(app).get('/api/v3/spaces/99999/variables');
|
|
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/v3/spaces/:spaceId/variables/recalculate', () => {
|
|
test('should recalculate all variables', async () => {
|
|
// Add a simple constant variable using JSON data format
|
|
const variableData = JSON.stringify({
|
|
name: '$constant',
|
|
formula: '42'
|
|
});
|
|
|
|
await dbRun(
|
|
`INSERT INTO table_rows (table_id, base_id, data) VALUES (?, ?, ?)`,
|
|
[variablesTableId, 'var_001', variableData]
|
|
);
|
|
|
|
const res = await request(app).post(`/api/v3/spaces/${testSpaceId}/variables/recalculate`);
|
|
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.success).toBe(true);
|
|
expect(res.body.data.calculated).toBeGreaterThanOrEqual(1);
|
|
expect(res.body.data.errors).toEqual([]);
|
|
});
|
|
|
|
test('should return 404 for non-existent space', async () => {
|
|
const res = await request(app).post('/api/v3/spaces/99999/variables/recalculate');
|
|
|
|
expect(res.status).toBe(404);
|
|
expect(res.body.success).toBe(false);
|
|
});
|
|
});
|
|
});
|