Files
claude-code-action/test/create-prompt.test.ts
Ashwin Bhat 87facd7051 feat: add use_commit_signing input with default false (#238)
* feat: add use_commit_signing input with default false

- Add new input 'use_commit_signing' to action.yml (defaults to false)
- Separate comment update functionality into standalone github-comment-server.ts
- Update MCP server configuration to conditionally load servers based on signing preference
- When commit signing is disabled, use specific Bash git commands (e.g., Bash(git add:*))
- When commit signing is enabled, use github-file-ops-server for atomic commits with signing
- Always include github-comment-server for comment updates regardless of signing mode
- Update prompt generation to provide appropriate instructions based on signing preference
- Add comprehensive test coverage for new functionality

This change simplifies the default setup for users who don't need commit signing,
while maintaining the option to enable it for those who require GitHub's commit
signature verification.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: auto-commit uncommitted changes when commit signing is disabled

- Check for uncommitted changes after Claude finishes (non-signing mode only)
- Automatically commit and push any uncommitted work to preserve Claude's changes
- Update tests to avoid actual git operations during test runs
- Pass use_commit_signing flag to branch cleanup logic

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-09 16:28:36 -07:00

986 lines
32 KiB
TypeScript

#!/usr/bin/env bun
import { describe, test, expect } from "bun:test";
import {
generatePrompt,
getEventTypeAndContext,
buildAllowedToolsString,
buildDisallowedToolsString,
} from "../src/create-prompt";
import type { PreparedContext } from "../src/create-prompt";
describe("generatePrompt", () => {
const mockGitHubData = {
contextData: {
title: "Test PR",
body: "This is a test PR",
author: { login: "testuser" },
state: "OPEN",
createdAt: "2023-01-01T00:00:00Z",
additions: 15,
deletions: 5,
baseRefName: "main",
headRefName: "feature-branch",
headRefOid: "abc123",
commits: {
totalCount: 2,
nodes: [
{
commit: {
oid: "commit1",
message: "Add feature",
author: {
name: "John Doe",
email: "john@example.com",
},
},
},
],
},
files: {
nodes: [
{
path: "src/file1.ts",
additions: 10,
deletions: 5,
changeType: "MODIFIED",
},
],
},
comments: {
nodes: [
{
id: "comment1",
databaseId: "123456",
body: "First comment",
author: { login: "user1" },
createdAt: "2023-01-01T01:00:00Z",
},
],
},
reviews: {
nodes: [
{
id: "review1",
author: { login: "reviewer1" },
body: "LGTM",
state: "APPROVED",
submittedAt: "2023-01-01T02:00:00Z",
comments: {
nodes: [],
},
},
],
},
},
comments: [
{
id: "comment1",
databaseId: "123456",
body: "First comment",
author: { login: "user1" },
createdAt: "2023-01-01T01:00:00Z",
},
{
id: "comment2",
databaseId: "123457",
body: "@claude help me",
author: { login: "user2" },
createdAt: "2023-01-01T01:30:00Z",
},
],
changedFiles: [],
changedFilesWithSHA: [
{
path: "src/file1.ts",
additions: 10,
deletions: 5,
changeType: "MODIFIED",
sha: "abc123",
},
],
reviewData: {
nodes: [
{
id: "review1",
databaseId: "400001",
author: { login: "reviewer1" },
body: "LGTM",
state: "APPROVED",
submittedAt: "2023-01-01T02:00:00Z",
comments: {
nodes: [],
},
},
],
},
imageUrlMap: new Map<string, string>(),
};
test("should generate prompt for issue_comment event", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: false,
baseBranch: "main",
claudeBranch: "claude/issue-67890-20240101_120000",
issueNumber: "67890",
commentBody: "@claude please fix this",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("You are Claude, an AI assistant");
expect(prompt).toContain("<event_type>GENERAL_COMMENT</event_type>");
expect(prompt).toContain("<is_pr>false</is_pr>");
expect(prompt).toContain(
"<trigger_context>issue comment with '@claude'</trigger_context>",
);
expect(prompt).toContain("<repository>owner/repo</repository>");
expect(prompt).toContain("<claude_comment_id>12345</claude_comment_id>");
expect(prompt).toContain("<trigger_username>Unknown</trigger_username>");
expect(prompt).toContain("[user1 at 2023-01-01T01:00:00Z]: First comment"); // from formatted comments
expect(prompt).not.toContain("filename\tstatus\tadditions\tdeletions\tsha"); // since it's not a PR
});
test("should generate prompt for pull_request_review event", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "pull_request_review",
isPR: true,
prNumber: "456",
commentBody: "@claude please fix this bug",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("<event_type>PR_REVIEW</event_type>");
expect(prompt).toContain("<is_pr>true</is_pr>");
expect(prompt).toContain("<pr_number>456</pr_number>");
expect(prompt).toContain("- src/file1.ts (MODIFIED) +10/-5 SHA: abc123"); // from formatted changed files
expect(prompt).toContain(
"[Review by reviewer1 at 2023-01-01T02:00:00Z]: APPROVED",
); // from review comments
});
test("should generate prompt for issue opened event", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issues",
eventAction: "opened",
isPR: false,
issueNumber: "789",
baseBranch: "main",
claudeBranch: "claude/issue-789-20240101_120000",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("<event_type>ISSUE_CREATED</event_type>");
expect(prompt).toContain(
"<trigger_context>new issue with '@claude' in body</trigger_context>",
);
expect(prompt).toContain(
"[Create a PR](https://github.com/owner/repo/compare/main",
);
expect(prompt).toContain("The target-branch should be 'main'");
});
test("should generate prompt for issue assigned event", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issues",
eventAction: "assigned",
isPR: false,
issueNumber: "999",
baseBranch: "develop",
claudeBranch: "claude/issue-999-20240101_120000",
assigneeTrigger: "claude-bot",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("<event_type>ISSUE_ASSIGNED</event_type>");
expect(prompt).toContain(
"<trigger_context>issue assigned to 'claude-bot'</trigger_context>",
);
expect(prompt).toContain(
"[Create a PR](https://github.com/owner/repo/compare/develop",
);
});
test("should generate prompt for issue labeled event", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issues",
eventAction: "labeled",
isPR: false,
issueNumber: "888",
baseBranch: "main",
claudeBranch: "claude/issue-888-20240101_120000",
labelTrigger: "claude-task",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("<event_type>ISSUE_LABELED</event_type>");
expect(prompt).toContain(
"<trigger_context>issue labeled with 'claude-task'</trigger_context>",
);
expect(prompt).toContain(
"[Create a PR](https://github.com/owner/repo/compare/main",
);
});
test("should include direct prompt when provided", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
directPrompt: "Fix the bug in the login form",
eventData: {
eventName: "issues",
eventAction: "opened",
isPR: false,
issueNumber: "789",
baseBranch: "main",
claudeBranch: "claude/issue-789-20240101_120000",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("<direct_prompt>");
expect(prompt).toContain("Fix the bug in the login form");
expect(prompt).toContain("</direct_prompt>");
expect(prompt).toContain(
"DIRECT INSTRUCTION: A direct instruction was provided and is shown in the <direct_prompt> tag above",
);
});
test("should generate prompt for pull_request event", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "pull_request",
eventAction: "opened",
isPR: true,
prNumber: "999",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("<event_type>PULL_REQUEST</event_type>");
expect(prompt).toContain("<is_pr>true</is_pr>");
expect(prompt).toContain("<pr_number>999</pr_number>");
expect(prompt).toContain("pull request opened");
});
test("should include custom instructions when provided", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
customInstructions: "Always use TypeScript",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: false,
issueNumber: "123",
baseBranch: "main",
claudeBranch: "claude/issue-67890-20240101_120000",
commentBody: "@claude please fix this",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("CUSTOM INSTRUCTIONS:\nAlways use TypeScript");
});
test("should include trigger username when provided", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
triggerUsername: "johndoe",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: false,
issueNumber: "123",
baseBranch: "main",
claudeBranch: "claude/issue-67890-20240101_120000",
commentBody: "@claude please fix this",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
expect(prompt).toContain("<trigger_username>johndoe</trigger_username>");
// With commit signing disabled, co-author info appears in git commit instructions
expect(prompt).toContain(
"Co-authored-by: johndoe <johndoe@users.noreply.github.com>",
);
});
test("should include PR-specific instructions only for PR events", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "pull_request_review",
isPR: true,
prNumber: "456",
commentBody: "@claude please fix this",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain PR-specific instructions (git commands when not using signing)
expect(prompt).toContain("git push");
expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR",
);
// Should NOT contain Issue-specific instructions
expect(prompt).not.toContain("You are already on the correct branch (");
expect(prompt).not.toContain(
"IMPORTANT: You are already on the correct branch (",
);
expect(prompt).not.toContain("Create a PR](https://github.com/");
});
test("should include Issue-specific instructions only for Issue events", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issues",
eventAction: "opened",
isPR: false,
issueNumber: "789",
baseBranch: "main",
claudeBranch: "claude/issue-789-20240101_120000",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain Issue-specific instructions
expect(prompt).toContain(
"You are already on the correct branch (claude/issue-789-20240101_120000)",
);
expect(prompt).toContain(
"IMPORTANT: You are already on the correct branch (claude/issue-789-20240101_120000)",
);
expect(prompt).toContain("Create a PR](https://github.com/");
expect(prompt).toContain(
"If you created anything in your branch, your comment must include the PR URL",
);
// Should NOT contain PR-specific instructions
expect(prompt).not.toContain(
"Push directly using mcp__github_file_ops__commit_files to the existing branch",
);
expect(prompt).not.toContain(
"Always push to the existing branch when triggered on a PR",
);
});
test("should use actual branch name for issue comments", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: false,
issueNumber: "123",
baseBranch: "main",
claudeBranch: "claude/issue-123-20240101_120000",
commentBody: "@claude please fix this",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain the actual branch name with timestamp
expect(prompt).toContain(
"You are already on the correct branch (claude/issue-123-20240101_120000)",
);
expect(prompt).toContain(
"IMPORTANT: You are already on the correct branch (claude/issue-123-20240101_120000)",
);
expect(prompt).toContain(
"The branch-name is the current branch: claude/issue-123-20240101_120000",
);
});
test("should handle closed PR with new branch", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: true,
prNumber: "456",
commentBody: "@claude please fix this",
claudeBranch: "claude/pr-456-20240101_120000",
baseBranch: "main",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain branch-specific instructions like issues
expect(prompt).toContain(
"You are already on the correct branch (claude/pr-456-20240101_120000)",
);
expect(prompt).toContain(
"Create a PR](https://github.com/owner/repo/compare/main",
);
expect(prompt).toContain(
"The branch-name is the current branch: claude/pr-456-20240101_120000",
);
expect(prompt).toContain("Reference to the original PR");
expect(prompt).toContain(
"If you created anything in your branch, your comment must include the PR URL",
);
// Should NOT contain open PR instructions
expect(prompt).not.toContain(
"Push directly using mcp__github_file_ops__commit_files to the existing branch",
);
});
test("should handle open PR without new branch", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: true,
prNumber: "456",
commentBody: "@claude please fix this",
// No claudeBranch or baseBranch for open PRs
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain open PR instructions (git commands when not using signing)
expect(prompt).toContain("git push");
expect(prompt).toContain(
"Always push to the existing branch when triggered on a PR",
);
// Should NOT contain new branch instructions
expect(prompt).not.toContain("Create a PR](https://github.com/");
expect(prompt).not.toContain("You are already on the correct branch");
expect(prompt).not.toContain(
"If you created anything in your branch, your comment must include the PR URL",
);
});
test("should handle PR review on closed PR with new branch", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "pull_request_review",
isPR: true,
prNumber: "789",
commentBody: "@claude please update this",
claudeBranch: "claude/pr-789-20240101_123000",
baseBranch: "develop",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain new branch instructions
expect(prompt).toContain(
"You are already on the correct branch (claude/pr-789-20240101_123000)",
);
expect(prompt).toContain(
"Create a PR](https://github.com/owner/repo/compare/develop",
);
expect(prompt).toContain("Reference to the original PR");
});
test("should handle PR review comment on closed PR with new branch", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "pull_request_review_comment",
isPR: true,
prNumber: "999",
commentId: "review-comment-123",
commentBody: "@claude fix this issue",
claudeBranch: "claude/pr-999-20240101_140000",
baseBranch: "main",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain new branch instructions
expect(prompt).toContain(
"You are already on the correct branch (claude/pr-999-20240101_140000)",
);
expect(prompt).toContain("Create a PR](https://github.com/");
expect(prompt).toContain("Reference to the original PR");
expect(prompt).toContain(
"If you created anything in your branch, your comment must include the PR URL",
);
});
test("should handle pull_request event on closed PR with new branch", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "pull_request",
eventAction: "closed",
isPR: true,
prNumber: "555",
claudeBranch: "claude/pr-555-20240101_150000",
baseBranch: "main",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should contain new branch instructions
expect(prompt).toContain(
"You are already on the correct branch (claude/pr-555-20240101_150000)",
);
expect(prompt).toContain("Create a PR](https://github.com/");
expect(prompt).toContain("Reference to the original PR");
});
test("should include git commands when useCommitSigning is false", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: true,
prNumber: "123",
commentBody: "@claude fix the bug",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, false);
// Should have git command instructions
expect(prompt).toContain("Use git commands via the Bash tool");
expect(prompt).toContain("git add");
expect(prompt).toContain("git commit");
expect(prompt).toContain("git push");
// Should use the minimal comment tool
expect(prompt).toContain("mcp__github_comment__update_claude_comment");
// Should not have commit signing tool references
expect(prompt).not.toContain("mcp__github_file_ops__commit_files");
});
test("should include commit signing tools when useCommitSigning is true", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issue_comment",
commentId: "67890",
isPR: true,
prNumber: "123",
commentBody: "@claude fix the bug",
},
};
const prompt = generatePrompt(envVars, mockGitHubData, true);
// Should have commit signing tool instructions
expect(prompt).toContain("mcp__github_file_ops__commit_files");
expect(prompt).toContain("mcp__github_file_ops__delete_files");
// Comment tool should always be from comment server, not file ops
expect(prompt).toContain("mcp__github_comment__update_claude_comment");
// Should not have git command instructions
expect(prompt).not.toContain("Use git commands via the Bash tool");
});
});
describe("getEventTypeAndContext", () => {
test("should return correct type and context for pull_request_review_comment", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "pull_request_review_comment",
isPR: true,
prNumber: "123",
commentBody: "@claude please fix this",
},
};
const result = getEventTypeAndContext(envVars);
expect(result.eventType).toBe("REVIEW_COMMENT");
expect(result.triggerContext).toBe("PR review comment with '@claude'");
});
test("should return correct type and context for issue assigned", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issues",
eventAction: "assigned",
isPR: false,
issueNumber: "999",
baseBranch: "main",
claudeBranch: "claude/issue-999-20240101_120000",
assigneeTrigger: "claude-bot",
},
};
const result = getEventTypeAndContext(envVars);
expect(result.eventType).toBe("ISSUE_ASSIGNED");
expect(result.triggerContext).toBe("issue assigned to 'claude-bot'");
});
test("should return correct type and context for issue labeled", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
eventData: {
eventName: "issues",
eventAction: "labeled",
isPR: false,
issueNumber: "888",
baseBranch: "main",
claudeBranch: "claude/issue-888-20240101_120000",
labelTrigger: "claude-task",
},
};
const result = getEventTypeAndContext(envVars);
expect(result.eventType).toBe("ISSUE_LABELED");
expect(result.triggerContext).toBe("issue labeled with 'claude-task'");
});
test("should return correct type and context for issue assigned without assigneeTrigger", () => {
const envVars: PreparedContext = {
repository: "owner/repo",
claudeCommentId: "12345",
triggerPhrase: "@claude",
directPrompt: "Please assess this issue",
eventData: {
eventName: "issues",
eventAction: "assigned",
isPR: false,
issueNumber: "999",
baseBranch: "main",
claudeBranch: "claude/issue-999-20240101_120000",
// No assigneeTrigger when using directPrompt
},
};
const result = getEventTypeAndContext(envVars);
expect(result.eventType).toBe("ISSUE_ASSIGNED");
expect(result.triggerContext).toBe("issue assigned event");
});
});
describe("buildAllowedToolsString", () => {
test("should return correct tools for regular events (default no signing)", () => {
const result = buildAllowedToolsString();
// The base tools should be in the result
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).toContain("Write");
// Default is no commit signing, so should have specific Bash git commands
expect(result).toContain("Bash(git add:*)");
expect(result).toContain("Bash(git commit:*)");
expect(result).toContain("Bash(git push:*)");
expect(result).toContain("mcp__github_comment__update_claude_comment");
// Should not have commit signing tools
expect(result).not.toContain("mcp__github_file_ops__commit_files");
expect(result).not.toContain("mcp__github_file_ops__delete_files");
});
test("should return correct tools with default parameters", () => {
const result = buildAllowedToolsString([], false, false);
// The base tools should be in the result
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).toContain("Write");
// Should have specific Bash git commands for non-signing mode
expect(result).toContain("Bash(git add:*)");
expect(result).toContain("Bash(git commit:*)");
expect(result).toContain("mcp__github_comment__update_claude_comment");
// Should not have commit signing tools
expect(result).not.toContain("mcp__github_file_ops__commit_files");
expect(result).not.toContain("mcp__github_file_ops__delete_files");
});
test("should append custom tools when provided", () => {
const customTools = ["Tool1", "Tool2", "Tool3"];
const result = buildAllowedToolsString(customTools);
// Base tools should be present
expect(result).toContain("Edit");
expect(result).toContain("Glob");
// Custom tools should be appended
expect(result).toContain("Tool1");
expect(result).toContain("Tool2");
expect(result).toContain("Tool3");
// Verify format with comma separation
const basePlusCustom = result.split(",");
expect(basePlusCustom.length).toBeGreaterThan(10); // At least the base tools plus custom
expect(basePlusCustom).toContain("Tool1");
expect(basePlusCustom).toContain("Tool2");
expect(basePlusCustom).toContain("Tool3");
});
test("should include GitHub Actions tools when includeActionsTools is true", () => {
const result = buildAllowedToolsString([], true);
// Base tools should be present
expect(result).toContain("Edit");
expect(result).toContain("Glob");
// GitHub Actions tools should be included
expect(result).toContain("mcp__github_ci__get_ci_status");
expect(result).toContain("mcp__github_ci__get_workflow_run_details");
expect(result).toContain("mcp__github_ci__download_job_log");
});
test("should include both custom and Actions tools when both provided", () => {
const customTools = ["Tool1", "Tool2"];
const result = buildAllowedToolsString(customTools, true);
// Base tools should be present
expect(result).toContain("Edit");
// Custom tools should be included
expect(result).toContain("Tool1");
expect(result).toContain("Tool2");
// GitHub Actions tools should be included
expect(result).toContain("mcp__github_ci__get_ci_status");
expect(result).toContain("mcp__github_ci__get_workflow_run_details");
expect(result).toContain("mcp__github_ci__download_job_log");
});
test("should include commit signing tools when useCommitSigning is true", () => {
const result = buildAllowedToolsString([], false, true);
// Base tools should be present
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).toContain("Write");
// Commit signing tools should be included
expect(result).toContain("mcp__github_file_ops__commit_files");
expect(result).toContain("mcp__github_file_ops__delete_files");
// Comment tool should always be from github_comment server
expect(result).toContain("mcp__github_comment__update_claude_comment");
// Bash should NOT be included when using commit signing (except in comment tool name)
expect(result).not.toContain("Bash(");
});
test("should include specific Bash git commands when useCommitSigning is false", () => {
const result = buildAllowedToolsString([], false, false);
// Base tools should be present
expect(result).toContain("Edit");
expect(result).toContain("Glob");
expect(result).toContain("Grep");
expect(result).toContain("LS");
expect(result).toContain("Read");
expect(result).toContain("Write");
// Specific Bash git commands should be included
expect(result).toContain("Bash(git add:*)");
expect(result).toContain("Bash(git commit:*)");
expect(result).toContain("Bash(git push:*)");
expect(result).toContain("Bash(git status:*)");
expect(result).toContain("Bash(git diff:*)");
expect(result).toContain("Bash(git log:*)");
expect(result).toContain("Bash(git rm:*)");
expect(result).toContain("Bash(git config user.name:*)");
expect(result).toContain("Bash(git config user.email:*)");
// Comment tool from minimal server should be included
expect(result).toContain("mcp__github_comment__update_claude_comment");
// Commit signing tools should NOT be included
expect(result).not.toContain("mcp__github_file_ops__commit_files");
expect(result).not.toContain("mcp__github_file_ops__delete_files");
});
test("should handle all combinations of options", () => {
const customTools = ["CustomTool1", "CustomTool2"];
const result = buildAllowedToolsString(customTools, true, false);
// Base tools should be present
expect(result).toContain("Edit");
expect(result).toContain("Bash(git add:*)");
// Custom tools should be included
expect(result).toContain("CustomTool1");
expect(result).toContain("CustomTool2");
// GitHub Actions tools should be included
expect(result).toContain("mcp__github_ci__get_ci_status");
// Comment tool from minimal server should be included
expect(result).toContain("mcp__github_comment__update_claude_comment");
// Commit signing tools should NOT be included
expect(result).not.toContain("mcp__github_file_ops__commit_files");
});
});
describe("buildDisallowedToolsString", () => {
test("should return base disallowed tools when no custom tools provided", () => {
const result = buildDisallowedToolsString();
// The base disallowed tools should be in the result
expect(result).toContain("WebSearch");
expect(result).toContain("WebFetch");
});
test("should append custom disallowed tools when provided", () => {
const customDisallowedTools = ["BadTool1", "BadTool2"];
const result = buildDisallowedToolsString(customDisallowedTools);
// Base disallowed tools should be present
expect(result).toContain("WebSearch");
// Custom disallowed tools should be appended
expect(result).toContain("BadTool1");
expect(result).toContain("BadTool2");
// Verify format with comma separation
const parts = result.split(",");
expect(parts).toContain("WebSearch");
expect(parts).toContain("BadTool1");
expect(parts).toContain("BadTool2");
});
test("should remove hardcoded disallowed tools if they are in allowed tools", () => {
const customDisallowedTools = ["BadTool1", "BadTool2"];
const allowedTools = ["WebSearch", "SomeOtherTool"];
const result = buildDisallowedToolsString(
customDisallowedTools,
allowedTools,
);
// WebSearch should be removed from disallowed since it's in allowed
expect(result).not.toContain("WebSearch");
// WebFetch should still be disallowed since it's not in allowed
expect(result).toContain("WebFetch");
// Custom disallowed tools should still be present
expect(result).toContain("BadTool1");
expect(result).toContain("BadTool2");
});
test("should remove all hardcoded disallowed tools if they are all in allowed tools", () => {
const allowedTools = ["WebSearch", "WebFetch", "SomeOtherTool"];
const result = buildDisallowedToolsString(undefined, allowedTools);
// Both hardcoded disallowed tools should be removed
expect(result).not.toContain("WebSearch");
expect(result).not.toContain("WebFetch");
// Result should be empty since no custom disallowed tools provided
expect(result).toBe("");
});
test("should handle custom disallowed tools when all hardcoded tools are overridden", () => {
const customDisallowedTools = ["BadTool1", "BadTool2"];
const allowedTools = ["WebSearch", "WebFetch"];
const result = buildDisallowedToolsString(
customDisallowedTools,
allowedTools,
);
// Hardcoded tools should be removed
expect(result).not.toContain("WebSearch");
expect(result).not.toContain("WebFetch");
// Only custom disallowed tools should remain
expect(result).toBe("BadTool1,BadTool2");
});
});