2026-03-27 18:28:25 -07:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2026 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
2026-03-28 17:27:51 -04:00
|
|
|
import { UpdateTopicTool } from './topicTool.js';
|
|
|
|
|
import { TopicState } from '../config/topicState.js';
|
2026-03-27 18:28:25 -07:00
|
|
|
import { MessageBus } from '../confirmation-bus/message-bus.js';
|
|
|
|
|
import type { PolicyEngine } from '../policy/policy-engine.js';
|
|
|
|
|
import {
|
|
|
|
|
UPDATE_TOPIC_TOOL_NAME,
|
|
|
|
|
TOPIC_PARAM_TITLE,
|
|
|
|
|
TOPIC_PARAM_SUMMARY,
|
|
|
|
|
} from './definitions/base-declarations.js';
|
|
|
|
|
import type { Config } from '../config/config.js';
|
|
|
|
|
|
|
|
|
|
describe('TopicState', () => {
|
|
|
|
|
let state: TopicState;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
state = new TopicState();
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-30 11:39:50 -07:00
|
|
|
it('should store and retrieve topic title', () => {
|
2026-03-27 18:28:25 -07:00
|
|
|
expect(state.getTopic()).toBeUndefined();
|
2026-03-30 11:39:50 -07:00
|
|
|
const success = state.setTopic('Test Topic');
|
2026-03-27 18:28:25 -07:00
|
|
|
expect(success).toBe(true);
|
|
|
|
|
expect(state.getTopic()).toBe('Test Topic');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should sanitize newlines and carriage returns', () => {
|
2026-03-30 11:39:50 -07:00
|
|
|
state.setTopic('Topic\nWith\r\nLines');
|
2026-03-27 18:28:25 -07:00
|
|
|
expect(state.getTopic()).toBe('Topic With Lines');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should trim whitespace', () => {
|
2026-03-30 11:39:50 -07:00
|
|
|
state.setTopic(' Spaced Topic ');
|
2026-03-27 18:28:25 -07:00
|
|
|
expect(state.getTopic()).toBe('Spaced Topic');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should reject empty or whitespace-only inputs', () => {
|
2026-03-30 11:39:50 -07:00
|
|
|
expect(state.setTopic('')).toBe(false);
|
2026-03-27 18:28:25 -07:00
|
|
|
});
|
|
|
|
|
|
2026-03-30 11:39:50 -07:00
|
|
|
it('should reset topic', () => {
|
|
|
|
|
state.setTopic('Test Topic');
|
2026-03-27 18:28:25 -07:00
|
|
|
state.reset();
|
|
|
|
|
expect(state.getTopic()).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('UpdateTopicTool', () => {
|
|
|
|
|
let tool: UpdateTopicTool;
|
|
|
|
|
let mockMessageBus: MessageBus;
|
|
|
|
|
let mockConfig: Config;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mockMessageBus = new MessageBus(vi.mocked({} as PolicyEngine));
|
|
|
|
|
// Mock enough of Config to satisfy the tool
|
|
|
|
|
mockConfig = {
|
|
|
|
|
topicState: new TopicState(),
|
|
|
|
|
} as unknown as Config;
|
|
|
|
|
tool = new UpdateTopicTool(mockConfig, mockMessageBus);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should have correct name and display name', () => {
|
|
|
|
|
expect(tool.name).toBe(UPDATE_TOPIC_TOOL_NAME);
|
|
|
|
|
expect(tool.displayName).toBe('Update Topic Context');
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-30 11:39:50 -07:00
|
|
|
it('should update TopicState on execute', async () => {
|
2026-03-27 18:28:25 -07:00
|
|
|
const invocation = tool.build({
|
|
|
|
|
[TOPIC_PARAM_TITLE]: 'New Chapter',
|
|
|
|
|
[TOPIC_PARAM_SUMMARY]: 'The goal is to implement X. Previously we did Y.',
|
|
|
|
|
});
|
|
|
|
|
const result = await invocation.execute(new AbortController().signal);
|
|
|
|
|
|
|
|
|
|
expect(result.llmContent).toContain('Current topic: "New Chapter"');
|
|
|
|
|
expect(result.llmContent).toContain(
|
|
|
|
|
'Topic summary: The goal is to implement X. Previously we did Y.',
|
|
|
|
|
);
|
|
|
|
|
expect(mockConfig.topicState.getTopic()).toBe('New Chapter');
|
|
|
|
|
expect(result.returnDisplay).toContain('## 📂 Topic: **New Chapter**');
|
|
|
|
|
expect(result.returnDisplay).toContain('**Summary:**');
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-30 11:39:50 -07:00
|
|
|
it('should render only topic title for same topic updates', async () => {
|
2026-03-27 18:28:25 -07:00
|
|
|
mockConfig.topicState.setTopic('New Chapter');
|
|
|
|
|
|
|
|
|
|
const invocation = tool.build({
|
|
|
|
|
[TOPIC_PARAM_TITLE]: 'New Chapter',
|
|
|
|
|
});
|
|
|
|
|
const result = await invocation.execute(new AbortController().signal);
|
|
|
|
|
|
2026-03-30 11:39:50 -07:00
|
|
|
expect(result.returnDisplay).toBe('## 📂 Topic: **New Chapter**');
|
|
|
|
|
expect(result.llmContent).toBe('Current topic: "New Chapter"');
|
2026-03-27 18:28:25 -07:00
|
|
|
});
|
|
|
|
|
});
|