godcrm/backend/services/public/__tests__/resolveLandingProject.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

305 lines
11 KiB
JavaScript

/**
* ADR-0060-A P7/A2 — resolveLandingProject helper.
*
* 3-tier fallback contract:
* 1. spaces.main_project_id if set + project.is_public + not System Data
* 2. first public project ordered by (order_index, id), excluding System Data
* 3. null
*
* Hits localhost `godcrm_test` via ADR-0009 isolation (booted by
* backend/test/setup.js → vitest.config.ts).
*/
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
import {
dbRun,
destroyAdapter,
resetAdapter
} from '../../../database/connection.js';
import { resolveLandingProject } from '../resolveLandingProject.js';
async function createUser() {
// Minimal columns — the test DB doesn't carry every column from PROD.
const r = await dbRun(
`INSERT INTO users (email, password_hash, name, encryption_key_encrypted)
VALUES (?, ?, ?, ?)`,
[
`t-rlp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@hltrn.cc`,
'hash',
'Test',
'k'
]
);
return r.lastInsertRowid;
}
async function createSpace(ownerId, slug) {
const r = await dbRun(
`INSERT INTO spaces (owner_id, name, type, visibility, public_slug)
VALUES (?, ?, ?, ?, ?)`,
[ownerId, 'RLP Space', 'business', 'external', slug]
);
return r.lastInsertRowid;
}
async function createProject(ownerId, spaceId, {
name = 'Proj',
type = 'business',
isPublic = true,
orderIndex = 0
} = {}) {
const r = await dbRun(
`INSERT INTO projects (owner_id, space_id, name, type, is_public, order_index)
VALUES (?, ?, ?, ?, ?, ?)`,
[ownerId, spaceId, name, type, isPublic, orderIndex]
);
return r.lastInsertRowid;
}
async function createDashboard(projectId, {
name = 'D',
isPublic = true,
orderIndex = 0
} = {}) {
const r = await dbRun(
`INSERT INTO dashboards (project_id, name, is_public, order_index)
VALUES (?, ?, ?, ?)`,
[projectId, name, isPublic, orderIndex]
);
return r.lastInsertRowid;
}
describe('resolveLandingProject (ADR-0060-A P7/A2)', () => {
let userId;
let slug;
beforeEach(async () => {
process.env.TEST_MODE = 'true';
process.env.SKIP_DEV_USER = 'true';
await resetAdapter();
// Schema-evolution shims (mirror public.test.js pattern).
for (const col of [
'visibility TEXT',
'public_slug TEXT',
'public_password_hash TEXT',
'main_project_id INTEGER'
]) {
try { await dbRun(`ALTER TABLE spaces ADD COLUMN ${col}`); } catch { /* exists */ }
}
for (const col of [
'is_public BOOLEAN NOT NULL DEFAULT TRUE',
'order_index INTEGER DEFAULT 0',
'type TEXT'
]) {
try { await dbRun(`ALTER TABLE projects ADD COLUMN ${col}`); } catch { /* exists */ }
}
for (const col of [
'is_public BOOLEAN NOT NULL DEFAULT TRUE',
'order_index INTEGER DEFAULT 0'
]) {
try { await dbRun(`ALTER TABLE dashboards ADD COLUMN ${col}`); } catch { /* exists */ }
}
userId = await createUser();
slug = `rlp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
});
afterEach(async () => {
await destroyAdapter();
});
test('Tier 3: empty space → nulls', async () => {
const spaceId = await createSpace(userId, slug);
const result = await resolveLandingProject(spaceId);
expect(result).toEqual({
main_project_id: null,
main_dashboard_id: null
});
});
test('Tier 3: space with only private projects → nulls', async () => {
const spaceId = await createSpace(userId, slug);
await createProject(userId, spaceId, { isPublic: false });
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBeNull();
expect(result.main_dashboard_id).toBeNull();
});
test('Tier 2: first public project picked when main_project_id is NULL', async () => {
const spaceId = await createSpace(userId, slug);
const projectId = await createProject(userId, spaceId, { isPublic: true });
const dashboardId = await createDashboard(projectId);
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(projectId);
expect(result.main_dashboard_id).toBe(dashboardId);
});
test('Tier 2: deterministic order by order_index then id', async () => {
const spaceId = await createSpace(userId, slug);
const pLater = await createProject(userId, spaceId, { name: 'Z', orderIndex: 10 });
const pFirst = await createProject(userId, spaceId, { name: 'A', orderIndex: 1 });
await createDashboard(pLater);
await createDashboard(pFirst);
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(pFirst);
});
test('Tier 2: System Data project skipped at fallback level', async () => {
const spaceId = await createSpace(userId, slug);
// System Data sits at order_index 0 — without the filter it would be picked.
await createProject(userId, spaceId, {
name: 'System Data',
type: 'system_data',
orderIndex: 0
});
const eligibleId = await createProject(userId, spaceId, {
name: 'Real',
orderIndex: 5
});
const dashId = await createDashboard(eligibleId);
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(eligibleId);
expect(result.main_dashboard_id).toBe(dashId);
});
test('Tier 1: main_project_id honoured when eligible', async () => {
const spaceId = await createSpace(userId, slug);
const pFirst = await createProject(userId, spaceId, { name: 'A', orderIndex: 1 });
const pHome = await createProject(userId, spaceId, { name: 'Home', orderIndex: 5 });
await createDashboard(pFirst);
const homeDashId = await createDashboard(pHome);
await dbRun(`UPDATE spaces SET main_project_id = ? WHERE id = ?`, [pHome, spaceId]);
const result = await resolveLandingProject(spaceId);
// Tier-1 wins even though pFirst comes earlier by order_index.
expect(result.main_project_id).toBe(pHome);
expect(result.main_dashboard_id).toBe(homeDashId);
});
test('Tier 1 → Tier 2 fallback when main_project_id points at non-public project', async () => {
const spaceId = await createSpace(userId, slug);
const pPriv = await createProject(userId, spaceId, { name: 'Priv', isPublic: false });
const pPub = await createProject(userId, spaceId, { name: 'Pub', orderIndex: 1 });
const dashPub = await createDashboard(pPub);
await dbRun(`UPDATE spaces SET main_project_id = ? WHERE id = ?`, [pPriv, spaceId]);
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(pPub);
expect(result.main_dashboard_id).toBe(dashPub);
});
test('Tier 1 → Tier 2 fallback when main_project_id points at System Data (type=system_data)', async () => {
const spaceId = await createSpace(userId, slug);
const sysData = await createProject(userId, spaceId, {
name: 'System Data',
type: 'system_data'
});
const eligible = await createProject(userId, spaceId, {
name: 'Eligible',
orderIndex: 5
});
const eligDash = await createDashboard(eligible);
await dbRun(`UPDATE spaces SET main_project_id = ? WHERE id = ?`, [sysData, spaceId]);
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(eligible);
expect(result.main_dashboard_id).toBe(eligDash);
});
test('Tier 1 → Tier 2 fallback when main_project_id points at legacy System Data (type=system)', async () => {
const spaceId = await createSpace(userId, slug);
const legacySys = await createProject(userId, spaceId, {
name: 'whatever',
type: 'system'
});
const eligible = await createProject(userId, spaceId, {
name: 'Eligible',
orderIndex: 5
});
await createDashboard(eligible);
await dbRun(`UPDATE spaces SET main_project_id = ? WHERE id = ?`, [legacySys, spaceId]);
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(eligible);
});
test('Tier 1 → Tier 2 fallback when main_project_id points at name=System Data (name-only matcher)', async () => {
const spaceId = await createSpace(userId, slug);
// Name-only match: type is a non-system value, name carries the signal.
const namedSys = await createProject(userId, spaceId, {
name: 'System Data',
type: 'business'
});
const eligible = await createProject(userId, spaceId, {
name: 'Eligible',
orderIndex: 5
});
await createDashboard(eligible);
await dbRun(`UPDATE spaces SET main_project_id = ? WHERE id = ?`, [namedSys, spaceId]);
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(eligible);
});
test('Tier 1 → Tier 2 fallback when main_project_id points at a project in a DIFFERENT space', async () => {
const spaceA = await createSpace(userId, `${slug}-a`);
const spaceB = await createSpace(userId, `${slug}-b`);
const projInB = await createProject(userId, spaceB, { name: 'Foreign' });
const projInA = await createProject(userId, spaceA, { name: 'Local', orderIndex: 5 });
const dashA = await createDashboard(projInA);
// Cross-space pointer — must NOT be honoured.
await dbRun(`UPDATE spaces SET main_project_id = ? WHERE id = ?`, [projInB, spaceA]);
const result = await resolveLandingProject(spaceA);
expect(result.main_project_id).toBe(projInA);
expect(result.main_dashboard_id).toBe(dashA);
});
test('main_dashboard_id is null when the resolved project has no public dashboards', async () => {
const spaceId = await createSpace(userId, slug);
const projectId = await createProject(userId, spaceId);
// Only a private dashboard.
await createDashboard(projectId, { isPublic: false });
const result = await resolveLandingProject(spaceId);
expect(result.main_project_id).toBe(projectId);
expect(result.main_dashboard_id).toBeNull();
});
test('main_dashboard_id picks the first by (order_index, id) when multiple are public', async () => {
const spaceId = await createSpace(userId, slug);
const projectId = await createProject(userId, spaceId);
const dLater = await createDashboard(projectId, { name: 'Z', orderIndex: 10 });
const dFirst = await createDashboard(projectId, { name: 'A', orderIndex: 1 });
const result = await resolveLandingProject(spaceId);
expect(result.main_dashboard_id).toBe(dFirst);
expect(result.main_dashboard_id).not.toBe(dLater);
});
test('non-existent spaceId → nulls (no throw)', async () => {
const result = await resolveLandingProject(999999999);
expect(result).toEqual({
main_project_id: null,
main_dashboard_id: null
});
});
test('null spaceId → nulls (defensive)', async () => {
const result = await resolveLandingProject(null);
expect(result).toEqual({
main_project_id: null,
main_dashboard_id: null
});
});
});