import test from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { BadRequestException, ForbiddenException } from '@nestjs/common'; import { GoalsService } from '../src/goals/goals.service.js'; const root = join(import.meta.dirname, '..', '..', '..', '..'); const admin = { actor: 'goal-admin', role: 'org-admin', project: 'default', tenant: 'goal-test' }; test('goal project selector exposes only active allowlisted registry entries', () => { const result = new GoalsService().projects(admin); assert.ok(result.projects.some((project) => project.project_id === 'AINative_OKR_CASAN4')); assert.ok(result.projects.every((project) => project.context_roots.every((contextRoot) => !contextRoot.startsWith('/')))); assert.ok(result.projects.every((project) => project.project_id !== 'CASAN_DEMO_PROJECT_A')); }); test('goal start rejects project ids outside the server registry before model routing', async () => { await assert.rejects( new GoalsService().start({ goal: 'Review the current application architecture safely', projectId: '../../tmp' }, admin), BadRequestException, ); }); test('goal project creation is restricted to organization administrators', () => { assert.throws( () => new GoalsService().createProject({ projectId: 'denied-project', domain: 'Denied Project' }, { ...admin, role: 'viewer' }), ForbiddenException, ); }); test('H1 creates a bounded manifest and routes workspace side effects to approval without writing source', () => { const stateRoot = mkdtempSync(join(tmpdir(), 'casan-goal-context-')); const tenantRoot = join(stateRoot, 'tenants'); const jobDirectory = join(stateRoot, 'state', 'goals', 'goal-test'); mkdirSync(jobDirectory, { recursive: true }); const jobPath = join(jobDirectory, '11111111-1111-1111-1111-111111111111.json'); writeFileSync(jobPath, JSON.stringify({ id: '11111111-1111-1111-1111-111111111111', trace_id: '11111111-1111-1111-1111-111111111111', goal: 'Hãy sửa code OKR để thêm một nút mới ngay bây giờ', status: 'queued', actor: 'goal-admin', tenant: 'goal-test', project: 'AINative_OKR_CASAN4', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), local_provider: 'local-policy', local_model: 'unused', cloud_provider: 'unused', cloud_model: 'unused', stages: [ { id: 'local-worker', status: 'queued', detail: 'Waiting', provider: '', model: '' }, { id: 'cloud-reviewer', status: 'queued', detail: 'Waiting', provider: '', model: '' }, ], })); execFileSync('python3', [join(root, 'packages/casan-harness/scripts/bash/goal-orchestrator.py'), '--job-file', jobPath], { cwd: root, env: { ...process.env, CASAN_STATE_ROOT: stateRoot, CASAN_TENANT_ID: 'goal-test', CASAN_TENANT_STATE_ROOT: tenantRoot }, stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000, }); const job = JSON.parse(readFileSync(jobPath, 'utf8')) as { status: string; context_manifest: { files: number; characters: number }; approval: { action: string } }; assert.equal(job.status, 'requires_approval'); assert.equal(job.approval.action, 'goal.workspace.execute'); assert.ok(job.context_manifest.files > 0); assert.ok(job.context_manifest.files <= 16); assert.ok(job.context_manifest.characters <= 7000); });