mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
* actions server * tmp * Replace view_actions_results with additional_permissions input - Changed input from boolean view_actions_results to a more flexible additional_permissions format - Uses newline-separated colon format similar to claude_env (e.g., "actions: read") - Maintains permission checking to warn users when their token lacks required permissions - Updated all tests to use the new format This allows for future extensibility while currently supporting only "actions: read" permission. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Update GitHub Actions MCP server with RUNNER_TEMP and status filtering - Use RUNNER_TEMP environment variable for log storage directory (defaults to /tmp) - Add status parameter to get_ci_status tool to filter workflow runs - Supported statuses: completed, action_required, cancelled, failure, neutral, skipped, stale, success, timed_out, in_progress, queued, requested, waiting, pending - Pass RUNNER_TEMP from install-mcp-server.ts to the MCP server environment 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Add GitHub Actions MCP tools to allowed tools when actions:read is granted - Automatically include github_ci MCP server tools in allowed tools list when actions:read permission is granted - Added mcp__github_ci__get_ci_status, mcp__github_ci__get_workflow_run_details, mcp__github_ci__download_job_log - Simplified permission checking to avoid duplicate parsing logic - Added tests for the new functionality This ensures Claude can use the Actions tools when the server is enabled. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Refactor additional permissions parsing to parseGitHubContext - Moved additional permissions parsing from individual functions to centralized parseGitHubContext - Added parseAdditionalPermissions function to handle newline-separated colon format - Removed redundant additionalPermissions parameter from prepareMcpConfig - Updated tests to use permissions from context instead of passing as parameter - Added comprehensive tests for parseAdditionalPermissions function This centralizes all input parsing logic in one place for better maintainability. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove unnecessary hasActionsReadPermission parameter from createPrompt - Removed hasActionsReadPermission parameter since createPrompt has access to context - Calculate hasActionsReadPermission directly from context.inputs.additionalPermissions inside createPrompt - Simplified prepare.ts by removing intermediate permission check This completes the refactoring to centralize all permission handling through the context object. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Add documentation for additional_permissions feature - Document the new additional_permissions input that replaces view_actions_results - Add dedicated section explaining CI/CD integration with actions:read permission - Include example workflow showing how to grant GitHub token permissions - Update main workflow example to show optional additional_permissions usage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * roadmap --------- Co-authored-by: Claude <noreply@anthropic.com>
167 lines
4.7 KiB
TypeScript
167 lines
4.7 KiB
TypeScript
import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test";
|
|
import * as core from "@actions/core";
|
|
import { checkWritePermissions } from "../src/github/validation/permissions";
|
|
import type { ParsedGitHubContext } from "../src/github/context";
|
|
|
|
describe("checkWritePermissions", () => {
|
|
let coreInfoSpy: any;
|
|
let coreWarningSpy: any;
|
|
let coreErrorSpy: any;
|
|
|
|
beforeEach(() => {
|
|
// Spy on core methods
|
|
coreInfoSpy = spyOn(core, "info").mockImplementation(() => {});
|
|
coreWarningSpy = spyOn(core, "warning").mockImplementation(() => {});
|
|
coreErrorSpy = spyOn(core, "error").mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
coreInfoSpy.mockRestore();
|
|
coreWarningSpy.mockRestore();
|
|
coreErrorSpy.mockRestore();
|
|
});
|
|
|
|
const createMockOctokit = (permission: string) => {
|
|
return {
|
|
repos: {
|
|
getCollaboratorPermissionLevel: async () => ({
|
|
data: { permission },
|
|
}),
|
|
},
|
|
} as any;
|
|
};
|
|
|
|
const createContext = (): ParsedGitHubContext => ({
|
|
runId: "1234567890",
|
|
eventName: "issue_comment",
|
|
eventAction: "created",
|
|
repository: {
|
|
full_name: "test-owner/test-repo",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
},
|
|
actor: "test-user",
|
|
payload: {
|
|
action: "created",
|
|
issue: {
|
|
number: 1,
|
|
title: "Test Issue",
|
|
body: "Test body",
|
|
user: { login: "test-user" },
|
|
},
|
|
comment: {
|
|
id: 123,
|
|
body: "@claude test",
|
|
user: { login: "test-user" },
|
|
html_url:
|
|
"https://github.com/test-owner/test-repo/issues/1#issuecomment-123",
|
|
},
|
|
} as any,
|
|
entityNumber: 1,
|
|
isPR: false,
|
|
inputs: {
|
|
triggerPhrase: "@claude",
|
|
assigneeTrigger: "",
|
|
labelTrigger: "",
|
|
allowedTools: [],
|
|
disallowedTools: [],
|
|
customInstructions: "",
|
|
directPrompt: "",
|
|
branchPrefix: "claude/",
|
|
useStickyComment: false,
|
|
additionalPermissions: new Map(),
|
|
},
|
|
});
|
|
|
|
test("should return true for admin permissions", async () => {
|
|
const mockOctokit = createMockOctokit("admin");
|
|
const context = createContext();
|
|
|
|
const result = await checkWritePermissions(mockOctokit, context);
|
|
|
|
expect(result).toBe(true);
|
|
expect(coreInfoSpy).toHaveBeenCalledWith(
|
|
"Checking permissions for actor: test-user",
|
|
);
|
|
expect(coreInfoSpy).toHaveBeenCalledWith(
|
|
"Permission level retrieved: admin",
|
|
);
|
|
expect(coreInfoSpy).toHaveBeenCalledWith("Actor has write access: admin");
|
|
});
|
|
|
|
test("should return true for write permissions", async () => {
|
|
const mockOctokit = createMockOctokit("write");
|
|
const context = createContext();
|
|
|
|
const result = await checkWritePermissions(mockOctokit, context);
|
|
|
|
expect(result).toBe(true);
|
|
expect(coreInfoSpy).toHaveBeenCalledWith("Actor has write access: write");
|
|
});
|
|
|
|
test("should return false for read permissions", async () => {
|
|
const mockOctokit = createMockOctokit("read");
|
|
const context = createContext();
|
|
|
|
const result = await checkWritePermissions(mockOctokit, context);
|
|
|
|
expect(result).toBe(false);
|
|
expect(coreWarningSpy).toHaveBeenCalledWith(
|
|
"Actor has insufficient permissions: read",
|
|
);
|
|
});
|
|
|
|
test("should return false for none permissions", async () => {
|
|
const mockOctokit = createMockOctokit("none");
|
|
const context = createContext();
|
|
|
|
const result = await checkWritePermissions(mockOctokit, context);
|
|
|
|
expect(result).toBe(false);
|
|
expect(coreWarningSpy).toHaveBeenCalledWith(
|
|
"Actor has insufficient permissions: none",
|
|
);
|
|
});
|
|
|
|
test("should throw error when permission check fails", async () => {
|
|
const error = new Error("API error");
|
|
const mockOctokit = {
|
|
repos: {
|
|
getCollaboratorPermissionLevel: async () => {
|
|
throw error;
|
|
},
|
|
},
|
|
} as any;
|
|
const context = createContext();
|
|
|
|
await expect(checkWritePermissions(mockOctokit, context)).rejects.toThrow(
|
|
"Failed to check permissions for test-user: Error: API error",
|
|
);
|
|
|
|
expect(coreErrorSpy).toHaveBeenCalledWith(
|
|
"Failed to check permissions: Error: API error",
|
|
);
|
|
});
|
|
|
|
test("should call API with correct parameters", async () => {
|
|
let capturedParams: any;
|
|
const mockOctokit = {
|
|
repos: {
|
|
getCollaboratorPermissionLevel: async (params: any) => {
|
|
capturedParams = params;
|
|
return { data: { permission: "write" } };
|
|
},
|
|
},
|
|
} as any;
|
|
const context = createContext();
|
|
|
|
await checkWritePermissions(mockOctokit, context);
|
|
|
|
expect(capturedParams).toEqual({
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
username: "test-user",
|
|
});
|
|
});
|
|
});
|