basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';8import * as fs from 'node:fs/promises';9import * as os from 'node:os';10import * as path from 'node:path';11import { TaskCreateTool } from './task-create.js';12import type { ApprovalMode, Config } from '../config/config.js';13import { runWithTeammateIdentity } from '../agents/team/identity.js';14import { listTasks } from '../agents/team/tasks.js';15 16const DEFAULT_MODE = 'default' as ApprovalMode;17const PLAN_MODE = 'plan' as ApprovalMode;18 19vi.mock('../config/storage.js', () => {20 let mockDir = '/tmp/test';21 return {22 Storage: {23 getGlobalQwenDir: () => mockDir,24 },25 __setMockGlobalDir: (d: string) => {26 mockDir = d;27 },28 };29});30 31// eslint-disable-next-line @typescript-eslint/no-explicit-any32const { __setMockGlobalDir } = (await import('../config/storage.js')) as any;33 34let tmpDir: string;35 36function makeConfig(teamName = 'test-team', approvalMode = DEFAULT_MODE) {37 return {38 getTeamContext: () => ({ teamName }),39 getApprovalMode: () => approvalMode,40 } as unknown as Config;41}42 43function makeConfigNoTeam() {44 return {45 getTeamContext: () => null,46 getApprovalMode: () => DEFAULT_MODE,47 } as unknown as Config;48}49 50beforeEach(async () => {51 tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'task-create-test-'));52 __setMockGlobalDir(tmpDir);53});54 55afterEach(async () => {56 await fs.rm(tmpDir, { recursive: true, force: true });57});58 59describe('TaskCreateTool', () => {60 let tool: TaskCreateTool;61 62 beforeEach(() => {63 tool = new TaskCreateTool(makeConfig());64 });65 66 it('has the correct name', () => {67 expect(tool.name).toBe('task_create');68 });69 70 it('creates a task with real file I/O', async () => {71 const invocation = tool.build({72 subject: 'Fix bug',73 description: 'Fix the login bug',74 });75 const result = await invocation.execute(new AbortController().signal);76 expect(result.error).toBeUndefined();77 expect(result.llmContent).toContain('Fix bug');78 expect(result.llmContent).toMatch(/#\d+/);79 });80 81 it('accepts optional metadata', async () => {82 const invocation = tool.build({83 subject: 'Deploy',84 description: 'Deploy to prod',85 activeForm: 'Deploying',86 metadata: { priority: 'high' },87 });88 const result = await invocation.execute(new AbortController().signal);89 expect(result.error).toBeUndefined();90 });91 92 it('returns error when no team is active', async () => {93 const noTeamTool = new TaskCreateTool(makeConfigNoTeam());94 const invocation = noTeamTool.build({95 subject: 'Test',96 description: 'Test desc',97 });98 const result = await invocation.execute(new AbortController().signal);99 expect(result.error).toBeDefined();100 expect(result.llmContent).toContain('No active team');101 });102 103 it('blocks plan-required teammates before leader approval', async () => {104 const planTool = new TaskCreateTool(makeConfig('test-team', PLAN_MODE));105 const invocation = planTool.build({106 subject: 'Bypass approval',107 description: 'Another teammate could execute this.',108 });109 110 const result = await runWithTeammateIdentity(111 {112 agentName: 'planner',113 teamName: 'test-team',114 agentId: 'planner@test-team',115 isTeamLead: false,116 planModeRequired: true,117 },118 () => invocation.execute(new AbortController().signal),119 );120 121 expect(result.error).toBeDefined();122 expect(result.llmContent).toContain('waiting for leader approval');123 await expect(listTasks('test-team')).resolves.toEqual([]);124 });125 126 it('validates required params', () => {127 expect(() => tool.build({} as never)).toThrow();128 expect(() => tool.build({ subject: 'x' } as never)).toThrow();129 });130 131 // ─── Permission surface ───────────────────────────────────132 // A regression back to 'allow' (or to the base '' classifier133 // sentinel) silently re-opens the task-injection path: the AUTO134 // classifier would rule on task_create({}) and always allow.135 136 it("defaults to 'ask' permission", async () => {137 const invocation = tool.build({138 subject: 'Injected',139 description: 'do something sneaky',140 });141 await expect(invocation.getDefaultPermission()).resolves.toBe('ask');142 });143 144 it('projects subject and description to the AUTO classifier', () => {145 const projected = tool.toAutoClassifierInput({146 subject: 'Fix bug',147 description: 'the instruction text',148 });149 expect(projected).toEqual({150 subject: 'Fix bug',151 description: 'the instruction text',152 });153 });154 155 it('shows the description in the confirmation prompt', async () => {156 const invocation = tool.build({157 subject: 'Fix bug',158 description: 'The full instruction text a teammate will execute.',159 });160 const details = await invocation.getConfirmationDetails(161 new AbortController().signal,162 );163 expect(details.type).toBe('info');164 expect((details as { prompt: string }).prompt).toContain(165 'The full instruction text a teammate will execute.',166 );167 });168});169 