diff --git a/src/github/validation/actor.ts b/src/github/validation/actor.ts index be00168..ed7f5b4 100644 --- a/src/github/validation/actor.ts +++ b/src/github/validation/actor.ts @@ -7,11 +7,21 @@ import type { Octokit } from "@octokit/rest"; import type { GitHubContext } from "../context"; +import { detectPlatform } from "../../platform/detector"; export async function checkHumanActor( octokit: Octokit, githubContext: GitHubContext, ) { + // Forgejo doesn't have GitHub-style bot/app accounts, and the user API + // requires read:user scope which may not be available. Skip entirely. + if (detectPlatform() === "forgejo") { + console.log( + `Skipping human actor check on Forgejo for: ${githubContext.actor}`, + ); + return; + } + // Fetch user information from GitHub API const { data: userData } = await octokit.users.getByUsername({ username: githubContext.actor, @@ -21,9 +31,6 @@ export async function checkHumanActor( console.log(`Actor type: ${actorType}`); - // On Forgejo, the user API doesn't return a `type` field (undefined). - // Treat undefined/empty type as a human user — Forgejo doesn't have - // GitHub-style bot/app accounts. if (actorType && actorType !== "User") { const allowedBots = githubContext.inputs.allowedBots; diff --git a/test/actor.test.ts b/test/actor.test.ts index 613fdac..946347a 100644 --- a/test/actor.test.ts +++ b/test/actor.test.ts @@ -1,6 +1,6 @@ #!/usr/bin/env bun -import { describe, test, expect } from "bun:test"; +import { describe, test, expect, beforeEach, afterEach } from "bun:test"; import { checkHumanActor } from "../src/github/validation/actor"; import type { Octokit } from "@octokit/rest"; import { createMockContext } from "./mockContext"; @@ -94,8 +94,8 @@ describe("checkHumanActor", () => { ); }); - test("should treat undefined actor type as human (Forgejo compatibility)", async () => { - // Forgejo's user API doesn't return a `type` field + test("should treat undefined actor type as human (GitHub compatibility)", async () => { + // Edge case: GitHub API returns no `type` field const mockOctokit = { users: { getByUsername: async () => ({ @@ -104,10 +104,68 @@ describe("checkHumanActor", () => { }, } as unknown as Octokit; const context = createMockContext(); - context.actor = "forgejo-user"; + context.actor = "unknown-user"; await expect( checkHumanActor(mockOctokit, context), ).resolves.toBeUndefined(); }); + + describe("Forgejo platform", () => { + let savedPlatform: string | undefined; + let savedServerUrl: string | undefined; + + beforeEach(() => { + savedPlatform = process.env.PLATFORM; + savedServerUrl = process.env.GITHUB_SERVER_URL; + process.env.PLATFORM = "forgejo"; + }); + + afterEach(() => { + if (savedPlatform !== undefined) process.env.PLATFORM = savedPlatform; + else delete process.env.PLATFORM; + if (savedServerUrl !== undefined) + process.env.GITHUB_SERVER_URL = savedServerUrl; + else delete process.env.GITHUB_SERVER_URL; + }); + + test("should skip actor check entirely on Forgejo", async () => { + let apiCalled = false; + const mockOctokit = { + users: { + getByUsername: async () => { + apiCalled = true; + return { data: {} }; + }, + }, + } as unknown as Octokit; + const context = createMockContext(); + context.actor = "forgejo-user"; + + await expect( + checkHumanActor(mockOctokit, context), + ).resolves.toBeUndefined(); + expect(apiCalled).toBe(false); + }); + + test("should skip actor check for bot-like names on Forgejo", async () => { + let apiCalled = false; + const mockOctokit = { + users: { + getByUsername: async () => { + apiCalled = true; + return { data: { type: "Bot" } }; + }, + }, + } as unknown as Octokit; + const context = createMockContext(); + context.actor = "some-bot[bot]"; + context.inputs.allowedBots = ""; + + await expect( + checkHumanActor(mockOctokit, context), + ).resolves.toBeUndefined(); + expect(apiCalled).toBe(false); + }); + }); });