mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
BREAKING CHANGES: - Remove review mode entirely - now handled via slash commands in agent mode - Remove all deprecated backward compatibility fields (mode, anthropic_model, override_prompt, direct_prompt) - Simplify mode detection: prompt overrides everything, then @claude mentions trigger tag mode, default is agent mode - Remove slash command resolution from GitHub Action - Claude Code handles natively - Remove variable substitution - prompts passed through as-is Architecture changes: - Only two modes now: tag (for @claude mentions) and agent (everything else) - Agent mode is the default for all events including PRs - Users configure behavior via prompts/slash commands (e.g. /review) - GitHub Action is now a thin wrapper that passes prompts to Claude Code - Mode names changed: 'experimental-review' → removed entirely This aligns with the philosophy that the GitHub Action should do minimal work and delegate to Claude Code for all intelligent behavior.
168 lines
4.7 KiB
TypeScript
168 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: {
|
|
prompt: "",
|
|
triggerPhrase: "@claude",
|
|
assigneeTrigger: "",
|
|
labelTrigger: "",
|
|
allowedTools: [],
|
|
disallowedTools: [],
|
|
customInstructions: "",
|
|
branchPrefix: "claude/",
|
|
useStickyComment: false,
|
|
additionalPermissions: new Map(),
|
|
useCommitSigning: false,
|
|
},
|
|
});
|
|
|
|
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",
|
|
});
|
|
});
|
|
});
|