godcrm/backend/services/__tests__/ModuleService.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

380 lines
12 KiB
JavaScript

/**
* ModuleService Tests - TDD (ADR-065)
* Testing module CRUD operations (separate modules table)
*/
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import {
getModulesBySpace,
getModuleById,
createModule,
updateModule,
deleteModule
} from '../ModuleService.js';
import { createWidget, getWidgetById } from '../WidgetService.js';
import { dbGet, dbRun, destroyAdapter, resetAdapter, toBool } from '../../database/connection.js';
// Helper functions (matching existing test patterns)
async function createTestUser() {
const uniqueEmail = `test-moduleservice-${Date.now()}@hltrn.cc`;
const result = await dbRun(
'INSERT INTO users (email, password_hash, name, encryption_key_encrypted, email_verified) VALUES (?, ?, ?, ?, ?)',
[uniqueEmail, 'hash', 'Test User', 'encrypted_key', 1]
);
return result.lastInsertRowid;
}
async function createTestSpace(ownerId) {
const result = await dbRun(
'INSERT INTO spaces (owner_id, name, type) VALUES (?, ?, ?)',
[ownerId, 'Test Space', 'business']
);
return result.lastInsertRowid;
}
async function createTestProject(ownerId, spaceId) {
const result = await dbRun(
'INSERT INTO projects (owner_id, space_id, name, type) VALUES (?, ?, ?, ?)',
[ownerId, spaceId, 'Test Project', 'business']
);
return result.lastInsertRowid;
}
async function createTestDashboard(projectId) {
const result = await dbRun(
'INSERT INTO dashboards (project_id, name, is_default) VALUES (?, ?, ?)',
[projectId, 'Test Dashboard', toBool(true)]
);
return result.lastInsertRowid;
}
async function createTestWidget(dashboardId, presetName = 'table_view') {
return await createWidget({
dashboard_id: dashboardId,
widget_type: 'preset',
preset_name: presetName,
title: `Widget ${presetName}`,
config: {},
position: { x: 0, y: 0, w: 6, h: 4 }
});
}
describe('ModuleService - ADR-065', () => {
let userId;
let spaceId;
let projectId;
let dashboardId;
beforeEach(async () => {
process.env.TEST_MODE = 'true';
process.env.SKIP_DEV_USER = 'true';
await resetAdapter();
userId = await createTestUser();
spaceId = await createTestSpace(userId);
projectId = await createTestProject(userId, spaceId);
dashboardId = await createTestDashboard(projectId);
});
afterEach(async () => {
await destroyAdapter();
});
// ============================================================
// createModule
// ============================================================
describe('createModule', () => {
test('should register widget as module', async () => {
const widget = await createTestWidget(dashboardId);
const mod = await createModule({
widget_id: widget.id,
space_id: spaceId,
sidebar_order: 1,
sidebar_icon: 'flask',
access_level: 'member'
});
expect(mod).toBeDefined();
expect(mod.module_id).toBeDefined();
expect(mod.widget_id).toBe(widget.id);
expect(mod.space_id).toBe(spaceId);
expect(mod.sidebar_order).toBe(1);
expect(mod.sidebar_icon).toBe('flask');
expect(mod.access_level).toBe('member');
expect(mod.widget).toBeDefined();
expect(mod.widget.title).toBe('Widget table_view');
});
test('should use default values for optional fields', async () => {
const widget = await createTestWidget(dashboardId);
const mod = await createModule({
widget_id: widget.id,
space_id: spaceId
});
expect(mod.sidebar_order).toBe(0);
expect(mod.sidebar_icon).toBeNull();
expect(mod.access_level).toBe('member');
expect(mod.is_pinned).toBeFalsy();
expect(mod.is_default).toBeFalsy();
});
test('should reject missing widget_id', async () => {
await expect(createModule({
space_id: spaceId
})).rejects.toThrow('widget_id is required');
});
test('should reject missing space_id', async () => {
const widget = await createTestWidget(dashboardId);
await expect(createModule({
widget_id: widget.id
})).rejects.toThrow('space_id is required');
});
test('should reject non-existent widget', async () => {
await expect(createModule({
widget_id: 99999,
space_id: spaceId
})).rejects.toThrow('Widget not found');
});
test('should reject non-existent space', async () => {
const widget = await createTestWidget(dashboardId);
await expect(createModule({
widget_id: widget.id,
space_id: 99999
})).rejects.toThrow('Space not found');
});
test('should reject invalid access_level', async () => {
const widget = await createTestWidget(dashboardId);
await expect(createModule({
widget_id: widget.id,
space_id: spaceId,
access_level: 'superadmin'
})).rejects.toThrow('access_level must be one of');
});
test('should reject duplicate module for same widget', async () => {
const widget = await createTestWidget(dashboardId);
await createModule({
widget_id: widget.id,
space_id: spaceId
});
await expect(createModule({
widget_id: widget.id,
space_id: spaceId
})).rejects.toThrow('already registered as a module');
});
});
// ============================================================
// getModuleById
// ============================================================
describe('getModuleById', () => {
test('should return module with widget data', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId,
sidebar_order: 3,
access_level: 'admin'
});
const mod = await getModuleById(created.module_id);
expect(mod).toBeDefined();
expect(mod.module_id).toBe(created.module_id);
expect(mod.sidebar_order).toBe(3);
expect(mod.access_level).toBe('admin');
expect(mod.widget).toBeDefined();
expect(mod.widget.id).toBe(widget.id);
expect(mod.widget.preset_name).toBe('table_view');
});
test('should return null for non-existent module', async () => {
const mod = await getModuleById(99999);
expect(mod).toBeNull();
});
});
// ============================================================
// getModulesBySpace
// ============================================================
describe('getModulesBySpace', () => {
test('should return all modules for space', async () => {
const w1 = await createTestWidget(dashboardId, 'table_view');
const w2 = await createTestWidget(dashboardId, 'kanban_board');
await createModule({ widget_id: w1.id, space_id: spaceId, sidebar_order: 1 });
await createModule({ widget_id: w2.id, space_id: spaceId, sidebar_order: 2 });
const modules = await getModulesBySpace(spaceId);
expect(modules).toHaveLength(2);
expect(modules[0].sidebar_order).toBe(1);
expect(modules[1].sidebar_order).toBe(2);
});
test('should return empty array for space with no modules', async () => {
const modules = await getModulesBySpace(spaceId);
expect(modules).toEqual([]);
});
test('should sort pinned modules first', async () => {
const w1 = await createTestWidget(dashboardId, 'table_view');
const w2 = await createTestWidget(dashboardId, 'kanban_board');
await createModule({ widget_id: w1.id, space_id: spaceId, sidebar_order: 1, is_pinned: false });
await createModule({ widget_id: w2.id, space_id: spaceId, sidebar_order: 2, is_pinned: true });
const modules = await getModulesBySpace(spaceId);
// Pinned first
expect(modules[0].widget.preset_name).toBe('kanban_board');
expect(modules[1].widget.preset_name).toBe('table_view');
});
test('should not return modules from other spaces', async () => {
const otherSpaceId = (await dbRun(
'INSERT INTO spaces (owner_id, name, type) VALUES (?, ?, ?)',
[userId, 'Other Space', 'business']
)).lastInsertRowid;
const widget = await createTestWidget(dashboardId);
await createModule({ widget_id: widget.id, space_id: otherSpaceId });
const modules = await getModulesBySpace(spaceId);
expect(modules).toHaveLength(0);
});
});
// ============================================================
// updateModule
// ============================================================
describe('updateModule', () => {
test('should update sidebar_order', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId,
sidebar_order: 0
});
const updated = await updateModule(created.module_id, { sidebar_order: 5 });
expect(updated.sidebar_order).toBe(5);
});
test('should update access_level', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId,
access_level: 'member'
});
const updated = await updateModule(created.module_id, { access_level: 'admin' });
expect(updated.access_level).toBe('admin');
});
test('should update sidebar_icon', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId
});
const updated = await updateModule(created.module_id, { sidebar_icon: 'star' });
expect(updated.sidebar_icon).toBe('star');
});
test('should update is_pinned', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId,
is_pinned: false
});
const updated = await updateModule(created.module_id, { is_pinned: true });
expect(updated.is_pinned).toBeTruthy();
});
test('should reject invalid access_level', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId
});
await expect(updateModule(created.module_id, {
access_level: 'invalid'
})).rejects.toThrow('access_level must be one of');
});
test('should reject update for non-existent module', async () => {
await expect(updateModule(99999, { sidebar_order: 1 }))
.rejects.toThrow('Module not found');
});
test('should return unchanged module when no valid fields provided', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId,
sidebar_order: 3
});
const updated = await updateModule(created.module_id, { unknown_field: 'value' });
expect(updated.sidebar_order).toBe(3);
});
});
// ============================================================
// deleteModule
// ============================================================
describe('deleteModule', () => {
test('should delete module record', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId
});
await deleteModule(created.module_id);
const mod = await getModuleById(created.module_id);
expect(mod).toBeNull();
});
test('should not delete the widget itself', async () => {
const widget = await createTestWidget(dashboardId);
const created = await createModule({
widget_id: widget.id,
space_id: spaceId
});
await deleteModule(created.module_id);
const w = await getWidgetById(widget.id);
expect(w).toBeDefined();
expect(w.id).toBe(widget.id);
});
test('should reject delete for non-existent module', async () => {
await expect(deleteModule(99999)).rejects.toThrow('Module not found');
});
});
});