import { describe, test, expect, beforeEach, spyOn, afterEach, mock, } from "bun:test"; import type { Octokits } from "../../api/client"; import type { FetchDataResult } from "../../data/fetcher"; import type { ParsedGitHubContext } from "../../context"; import type { GitHubPullRequest, GitHubIssue } from "../../types"; // Mock the entire branch module to avoid executing shell commands const mockSetupBranch = mock(); // Mock bun shell to prevent actual git commands mock.module("bun", () => ({ $: new Proxy( {}, { get: () => async () => ({ text: async () => "" }), }, ), })); // Mock @actions/core mock.module("@actions/core", () => ({ setOutput: mock(), info: mock(), warning: mock(), error: mock(), })); describe("setupBranch", () => { let mockOctokits: Octokits; let mockContext: ParsedGitHubContext; let mockGithubData: FetchDataResult; beforeEach(() => { mock.restore(); // Mock the Octokits object with both rest and graphql mockOctokits = { rest: { repos: { get: mock(() => Promise.resolve({ data: { default_branch: "main" }, }), ), }, git: { getRef: mock(() => Promise.resolve({ data: { object: { sha: "abc123def456" }, }, }), ), }, }, graphql: mock(), } as any; // Create a base context mockContext = { runId: "12345", eventName: "pull_request", repository: { owner: "test-owner", repo: "test-repo", full_name: "test-owner/test-repo", }, actor: "test-user", entityNumber: 42, isPR: true, inputs: { prompt: "", triggerPhrase: "@claude", assigneeTrigger: "", labelTrigger: "", baseBranch: "", branchPrefix: "claude/", useStickyComment: false, useCommitSigning: false, allowedBots: "", trackProgress: true, }, payload: {} as any, }; // Create mock GitHub data for a PR mockGithubData = { contextData: { headRefName: "feature/test-branch", baseRefName: "main", state: "OPEN", commits: { totalCount: 5, }, } as GitHubPullRequest, comments: [], changedFiles: [], changedFilesWithSHA: [], reviewData: null, imageUrlMap: new Map(), }; }); describe("Branch operation test structure", () => { test("should handle PR context correctly", () => { // Verify PR context structure expect(mockContext.isPR).toBe(true); expect(mockContext.entityNumber).toBe(42); expect(mockGithubData.contextData).toHaveProperty("headRefName"); expect(mockGithubData.contextData).toHaveProperty("baseRefName"); }); test("should handle issue context correctly", () => { // Convert to issue context mockContext.isPR = false; mockContext.eventName = "issues"; mockGithubData.contextData = { title: "Test Issue", body: "Issue description", } as GitHubIssue; // Verify issue context structure expect(mockContext.isPR).toBe(false); expect(mockContext.eventName).toBe("issues"); expect(mockGithubData.contextData).toHaveProperty("title"); expect(mockGithubData.contextData).toHaveProperty("body"); }); test("should verify branch naming conventions", () => { const timestamp = new Date(); const formattedTimestamp = `${timestamp.getFullYear()}${String(timestamp.getMonth() + 1).padStart(2, "0")}${String(timestamp.getDate()).padStart(2, "0")}-${String(timestamp.getHours()).padStart(2, "0")}${String(timestamp.getMinutes()).padStart(2, "0")}`; // Test PR branch name const prBranchName = `${mockContext.inputs.branchPrefix}pr-${mockContext.entityNumber}-${formattedTimestamp}`; expect(prBranchName).toMatch(/^claude\/pr-42-\d{8}-\d{4}$/); // Test issue branch name const issueBranchName = `${mockContext.inputs.branchPrefix}issue-${mockContext.entityNumber}-${formattedTimestamp}`; expect(issueBranchName).toMatch(/^claude\/issue-42-\d{8}-\d{4}$/); // Verify Kubernetes compatibility (lowercase, max 50 chars) const kubeName = prBranchName.toLowerCase().substring(0, 50); expect(kubeName).toMatch(/^[a-z0-9\/-]+$/); expect(kubeName.length).toBeLessThanOrEqual(50); }); test("should handle different PR states", () => { const prData = mockGithubData.contextData as GitHubPullRequest; // Test open PR prData.state = "OPEN"; expect(prData.state).toBe("OPEN"); // Test closed PR prData.state = "CLOSED"; expect(prData.state).toBe("CLOSED"); // Test merged PR prData.state = "MERGED"; expect(prData.state).toBe("MERGED"); }); test("should handle commit signing configuration", () => { // Without commit signing expect(mockContext.inputs.useCommitSigning).toBe(false); // With commit signing mockContext.inputs.useCommitSigning = true; expect(mockContext.inputs.useCommitSigning).toBe(true); }); test("should handle custom base branch", () => { // Default (no base branch) expect(mockContext.inputs.baseBranch).toBe(""); // Custom base branch mockContext.inputs.baseBranch = "develop"; expect(mockContext.inputs.baseBranch).toBe("develop"); }); test("should verify Octokits structure", () => { expect(mockOctokits).toHaveProperty("rest"); expect(mockOctokits).toHaveProperty("graphql"); expect(mockOctokits.rest).toHaveProperty("repos"); expect(mockOctokits.rest).toHaveProperty("git"); expect(mockOctokits.rest.repos).toHaveProperty("get"); expect(mockOctokits.rest.git).toHaveProperty("getRef"); }); test("should verify FetchDataResult structure", () => { expect(mockGithubData).toHaveProperty("contextData"); expect(mockGithubData).toHaveProperty("comments"); expect(mockGithubData).toHaveProperty("changedFiles"); expect(mockGithubData).toHaveProperty("changedFilesWithSHA"); expect(mockGithubData).toHaveProperty("reviewData"); expect(mockGithubData).toHaveProperty("imageUrlMap"); }); test("should handle PR with varying commit counts", () => { const prData = mockGithubData.contextData as GitHubPullRequest; // Few commits prData.commits.totalCount = 5; const fetchDepthSmall = Math.max(prData.commits.totalCount, 20); expect(fetchDepthSmall).toBe(20); // Many commits prData.commits.totalCount = 150; const fetchDepthLarge = Math.max(prData.commits.totalCount, 20); expect(fetchDepthLarge).toBe(150); }); test("should verify branch prefix customization", () => { // Default prefix expect(mockContext.inputs.branchPrefix).toBe("claude/"); // Custom prefix mockContext.inputs.branchPrefix = "bot/"; expect(mockContext.inputs.branchPrefix).toBe("bot/"); // Another custom prefix mockContext.inputs.branchPrefix = "ai-assistant/"; expect(mockContext.inputs.branchPrefix).toBe("ai-assistant/"); }); }); });