Publish curated projectbrain source from 246c0a7f35e32f54fa1bf8a3f720b01bb00dd9af

This commit is contained in:
Public Source Publisher committed 2026-10-07 01:36:52 +00:00
1 parent 759d263081
commit 98f7618af2
11 files changed
+540 -63

No files matched your search

+20 -5
View File
@@ -297,12 +297,27 @@ databaseDescribe("autonomy database write isolation", () => {
expect((await getAutonomyState(slug)).activeLease?.token).toBe(resumed.work!.token);
const head = "b".repeat(40);
let remoteMerged = false;
const fullJob = (runId: number, headSha: string, conclusion = "success") => ({
id: runId * 10,
html_url: `https://gitea.example.com/runs/${runId}/jobs/${runId * 10}`,
run_id: runId,
name: "full",
labels: [],
run_attempt: 1,
head_sha: headSha,
head_branch: "chatgpt/existing",
status: "completed",
conclusion,
runner_id: 1,
steps: [],
created_at: "2026-10-06T21:33:06Z",
});
vi.spyOn(GiteaClient.prototype, "getPullRequest").mockImplementation(async () => ({ number: 1, title: "Feature", html_url: "https://gitea.example.com/pulls/1", state: remoteMerged ? "closed" : "open", merged: remoteMerged, merge_commit_sha: remoteMerged ? "c".repeat(40) : undefined, head: { ref: "chatgpt/existing", sha: head }, base: { ref: "main", sha: "d".repeat(40) }, mergeable: true }));
vi.spyOn(GiteaClient.prototype, "getBranch").mockResolvedValue({ name: "chatgpt/existing", commit: { id: head, message: "Repair" }, protected: false });
vi.spyOn(GiteaClient.prototype, "getContents").mockResolvedValue({ type: "file", path: "src/feature.ts", name: "feature.ts", sha: "file-sha", size: 4, encoding: "base64", content: "dGVzdA==" });
const write = vi.spyOn(GiteaClient.prototype, "updateFile").mockResolvedValue({});
const actionRuns = vi.spyOn(GiteaClient.prototype, "listActionRuns").mockResolvedValue({ total_count: 2, workflow_runs: [
{ id: 2, run_number: 2, display_title: "Managed validation / full", path: ".gitea/workflows/managed-validation.yml", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/2" },
{ id: 2, run_number: 2, run_attempt: 1, display_title: "ci: validate full", path: ".gitea/workflows/managed-validation.yml@refs/pull/1/head", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", pull_requests: [{ number: 1, head: { ref: "chatgpt/existing", sha: head } }], status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/2", jobs: [fullJob(2, head)] },
{ id: 3, run_number: 3, display_title: "CI", path: ".gitea/workflows/ci.yml", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/3" },
] });
vi.spyOn(GiteaClient.prototype, "getBranchProtection").mockResolvedValue({ enable_status_check: true, status_check_contexts: ["CI / test"] });
@@ -325,28 +340,28 @@ databaseDescribe("autonomy database write isolation", () => {
expect(synced.isError, JSON.stringify(synced.content)).not.toBe(true);
actionRuns.mockResolvedValue({ total_count: 1, workflow_runs: [
{ id: 4, run_number: 4, display_title: "Managed validation / full", path: ".gitea/workflows/managed-validation.yml", event: "pull_request", head_sha: "c".repeat(40), head_branch: "chatgpt/existing", status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/4" },
{ id: 4, run_number: 4, run_attempt: 1, display_title: "ci: validate full", path: ".gitea/workflows/managed-validation.yml@refs/pull/1/head", event: "pull_request", head_sha: "c".repeat(40), head_branch: "chatgpt/existing", pull_requests: [{ number: 1, head: { ref: "chatgpt/existing", sha: "c".repeat(40) } }], status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/4", jobs: [fullJob(4, "c".repeat(40))] },
] });
const stalePrRun = await client.callTool({ name: "merge_feature_delivery", arguments: { project_id: slug, delivery_id: delivery.id, expected_head_sha: head, delete_branch: false } });
expect(stalePrRun.isError, JSON.stringify(stalePrRun.content)).toBe(true);
expect(merge).not.toHaveBeenCalled();
actionRuns.mockResolvedValue({ total_count: 1, workflow_runs: [
{ id: 5, run_number: 5, display_title: "Managed validation / full", path: ".gitea/workflows/managed-validation.yml", event: "workflow_dispatch", head_sha: head, head_branch: "chatgpt/existing", status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/5" },
{ id: 5, run_number: 5, run_attempt: 1, display_title: "ci: validate full", path: ".gitea/workflows/managed-validation.yml", event: "workflow_dispatch", head_sha: head, head_branch: "chatgpt/existing", status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/5", jobs: [fullJob(5, head)] },
] });
const dispatchOnly = await client.callTool({ name: "merge_feature_delivery", arguments: { project_id: slug, delivery_id: delivery.id, expected_head_sha: head, delete_branch: false } });
expect(dispatchOnly.isError, JSON.stringify(dispatchOnly.content)).toBe(true);
expect(merge).not.toHaveBeenCalled();
actionRuns.mockResolvedValue({ total_count: 1, workflow_runs: [
{ id: 6, run_number: 6, display_title: "Managed validation / full", path: ".gitea/workflows/managed-validation.yml", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", status: "completed", conclusion: "failure", html_url: "https://gitea.example.com/runs/6" },
{ id: 6, run_number: 6, run_attempt: 1, display_title: "ci: validate full", path: ".gitea/workflows/managed-validation.yml@refs/pull/1/head", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", pull_requests: [{ number: 1, head: { ref: "chatgpt/existing", sha: head } }], status: "completed", conclusion: "failure", html_url: "https://gitea.example.com/runs/6", jobs: [fullJob(6, head, "failure")] },
] });
const failedPrFull = await client.callTool({ name: "merge_feature_delivery", arguments: { project_id: slug, delivery_id: delivery.id, expected_head_sha: head, delete_branch: false } });
expect(failedPrFull.isError, JSON.stringify(failedPrFull.content)).toBe(true);
expect(merge).not.toHaveBeenCalled();
actionRuns.mockResolvedValue({ total_count: 2, workflow_runs: [
{ id: 2, run_number: 2, display_title: "Managed validation / full", path: ".gitea/workflows/managed-validation.yml", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/2" },
{ id: 2, run_number: 2, run_attempt: 1, display_title: "ci: validate full", path: ".gitea/workflows/managed-validation.yml@refs/pull/1/head", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", pull_requests: [{ number: 1, head: { ref: "chatgpt/existing", sha: head } }], status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/2", jobs: [fullJob(2, head)] },
{ id: 3, run_number: 3, display_title: "CI", path: ".gitea/workflows/ci.yml", event: "pull_request", head_sha: head, head_branch: "chatgpt/existing", status: "completed", conclusion: "success", html_url: "https://gitea.example.com/runs/3" },
] });
const merged = await client.callTool({ name: "merge_feature_delivery", arguments: { project_id: slug, delivery_id: delivery.id, expected_head_sha: head, delete_branch: false } });
+4 -1
View File
@@ -533,7 +533,8 @@ describe("ChatGPT feature delivery approval", () => {
it("requires every Gitea Actions workflow for the exact branch head to succeed", () => {
const run = (id: number, conclusion: string, headSha = "current-sha"): GiteaActionRun => ({
id, run_number: id, display_title: "Managed validation / full", path: ".gitea/workflows/managed-validation.yml", event: "workflow_dispatch", head_sha: headSha,
head_branch: "chatgpt/feature", status: "completed", conclusion, html_url: `https://gitea.example.com/runs/${id}`,
run_attempt: 1, head_branch: "chatgpt/feature", status: "completed", conclusion, html_url: `https://gitea.example.com/runs/${id}`,
jobs: [{ id: id * 10, html_url: `https://gitea.example.com/runs/${id}/jobs/${id * 10}`, run_id: id, name: "full", labels: [], run_attempt: 1, head_sha: headSha, head_branch: "chatgpt/feature", status: "completed", conclusion, runner_id: 1, steps: [], created_at: "2026-10-06T21:33:06Z" }],
});
expect(assertSuccessfulActionRuns([run(1, "success"), run(2, "success")], "chatgpt/feature", "current-sha")).toHaveLength(2);
expect(() => assertSuccessfulActionRuns([run(1, "success"), run(2, "failure")], "chatgpt/feature", "current-sha")).toThrow("FEATURE_DELIVERY_FULL_VALIDATION_REQUIRED");
@@ -546,6 +547,7 @@ describe("ChatGPT feature delivery approval", () => {
const run = (overrides: Partial<GiteaActionRun> = {}): GiteaActionRun => ({
id: 10,
run_number: 10,
run_attempt: 1,
display_title: "Managed validation / full",
path: ".gitea/workflows/managed-validation.yml",
event: "pull_request",
@@ -554,6 +556,7 @@ describe("ChatGPT feature delivery approval", () => {
status: "completed",
conclusion: "success",
html_url: "https://gitea.example.com/runs/10",
jobs: [{ id: 100, html_url: "https://gitea.example.com/runs/10/jobs/100", run_id: 10, name: "full", labels: [], run_attempt: 1, head_sha: headSha, head_branch: branch, status: "completed", conclusion: "success", runner_id: 1, steps: [], created_at: "2026-10-06T21:33:06Z" }],
...overrides,
});
+247 -6
View File
@@ -1,15 +1,33 @@
import { describe, expect, it, vi } from "vitest";
import { GiteaClient } from "../src/integrations/gitea/client.js";
import { assertSuccessfulActionRuns, exactHeadActionRuns } from "../src/integrations/gitea/policy.js";
import type { GiteaActionRun } from "../src/integrations/gitea/types.js";
import { assertSuccessfulActionRuns, assertSuccessfulPullRequestFullActionRun, exactHeadActionRuns } from "../src/integrations/gitea/policy.js";
import type { GiteaActionJob, GiteaActionRun } from "../src/integrations/gitea/types.js";
const branch = "codex/native-ci";
const sha = "a".repeat(40);
const context = "CI / build-test (pull_request)";
const fullJob = (runId: number, headSha = sha, overrides: Partial<GiteaActionJob> = {}): GiteaActionJob => ({
id: runId * 10,
html_url: `https://gitea.example/runs/${runId}/jobs/${runId * 10}`,
run_id: runId,
name: "full",
labels: [],
run_attempt: 1,
head_sha: headSha,
head_branch: branch,
status: "completed",
conclusion: "success",
runner_id: 1,
steps: [],
created_at: "2026-10-06T21:33:06Z",
...overrides,
});
const run = (id: number, overrides: Partial<GiteaActionRun> = {}): GiteaActionRun => ({
id, run_number: id, display_title: "Managed validation / full", path: ".gitea/workflows/managed-validation.yml",
run_attempt: 1,
event: "pull_request", head_sha: sha, head_branch: "", status: "completed",
conclusion: "success", html_url: `https://gitea.example/runs/${id}`, ...overrides,
conclusion: "success", html_url: `https://gitea.example/runs/${id}`, jobs: [fullJob(id)], ...overrides,
});
describe("native PR validation discovery", () => {
@@ -24,6 +42,133 @@ describe("native PR validation discovery", () => {
expect(assertSuccessfulActionRuns([matching], branch, sha)).toEqual([matching]);
});
it("accepts Gitea 28 native PR full validation fields and its exact successful full job", () => {
const nativeRun = run(22352, {
run_number: 17,
display_title: "ci: validate QuizMonitor backend tests",
path: "managed-validation.yml@refs/pull/6/head",
head_branch: undefined,
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
jobs: [fullJob(22352, sha, { id: 38976, head_branch: undefined })],
});
expect(assertSuccessfulPullRequestFullActionRun([nativeRun], branch, sha)).toBe(nativeRun);
expect(assertSuccessfulPullRequestFullActionRun([nativeRun], branch, sha, 6)).toBe(nativeRun);
});
it("binds merge validation to the requested PR number despite a blank native head branch", () => {
const prSix = run(29, {
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
});
expect(() => assertSuccessfulPullRequestFullActionRun([prSix], branch, sha, 99))
.toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
expect(() => assertSuccessfulPullRequestFullActionRun([run(30, {
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 99, head: { ref: branch, sha } }],
head_branch: "",
})], branch, sha, 6)).toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
expect(() => assertSuccessfulPullRequestFullActionRun([run(34, {
path: ".gitea/workflows/managed-validation.yml",
head_branch: "",
})], branch, sha, 6)).toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
expect(() => assertSuccessfulPullRequestFullActionRun([run(35, {
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }, { number: 99, head: { ref: branch, sha } }],
head_branch: "",
})], branch, sha, 6)).toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
});
it("rejects a newer failed attempt for the requested PR when Gitea omits head_branch", () => {
const successful = run(31, {
run_number: 17,
run_attempt: 1,
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
jobs: [fullJob(31)],
});
const failed = run(32, {
run_number: 17,
run_attempt: 2,
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
jobs: [fullJob(32, sha, { run_attempt: 2, conclusion: "failure" })],
});
expect(() => assertSuccessfulPullRequestFullActionRun([successful, failed], branch, sha, 6))
.toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
});
it("keeps failed noncanonical PR workflows in the exact-head merge gate", () => {
const full = run(37, {
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
});
const failedQualityWorkflow = run(38, {
path: ".gitea/workflows/security-check.yml@refs/pull/6/head",
conclusion: "failure",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
jobs: undefined,
});
expect(() => assertSuccessfulPullRequestFullActionRun([full, failedQualityWorkflow], branch, sha, 6))
.toThrow("FEATURE_DELIVERY_CI_NOT_SUCCESSFUL");
});
it("rejects malformed path metadata without throwing a generic runtime error", () => {
const valid = run(33, {
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
});
const malformed = run(34, { event: "push", path: null as unknown as string, head_branch: branch });
expect(() => assertSuccessfulPullRequestFullActionRun([valid, malformed], branch, sha, 6))
.toThrow("FEATURE_DELIVERY_CI_NOT_SUCCESSFUL");
});
it.each([
["job id", { id: undefined as unknown as number }],
["run id", { run_id: undefined as unknown as number }],
["attempt", { run_attempt: undefined as unknown as number }],
["head SHA", { head_sha: undefined as unknown as string }],
])("rejects a full job missing its %s identity", (_label, overrides) => {
expect(() => assertSuccessfulPullRequestFullActionRun([run(36, { jobs: [fullJob(36, sha, overrides)] })], branch, sha))
.toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
});
it.each([
["missing", []],
["failed", [fullJob(20, sha, { conclusion: "failure" })]],
["wrong run", [fullJob(21, sha, { run_id: 999 })]],
["wrong attempt", [fullJob(22, sha, { run_attempt: 2 })]],
["wrong head", [fullJob(23, "b".repeat(40))]],
["duplicate", [fullJob(24), fullJob(24, sha, { id: 241 })]],
])("rejects a %s canonical full job", (_label, jobs) => {
expect(() => assertSuccessfulPullRequestFullActionRun([run(20, { jobs })], branch, sha))
.toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
});
it("does not let an older successful attempt authorize a newer failed full attempt", () => {
const successful = run(25, { run_number: 17, run_attempt: 1, jobs: [fullJob(25)] });
const failed = run(26, {
run_number: 17,
run_attempt: 2,
jobs: [fullJob(26, sha, { run_attempt: 2, conclusion: "failure" })],
});
expect(() => assertSuccessfulPullRequestFullActionRun([successful, failed], branch, sha))
.toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
});
it.each(["untrusted/managed-validation.yml", "other.yml"]) ("does not treat %s or its title as canonical", (path) => {
expect(() => assertSuccessfulPullRequestFullActionRun([run(27, {
path,
display_title: "Managed validation / full",
})], branch, sha)).toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
});
it.each(["pending", "queued", "waiting", "in_progress"])("does not authorize %s", (status) => {
expect(() => assertSuccessfulActionRuns([run(1, { status, conclusion: undefined })], branch, sha)).toThrow("FEATURE_DELIVERY_FULL_VALIDATION_REQUIRED");
});
@@ -47,12 +192,108 @@ describe("native PR validation discovery", () => {
], branch, sha)).toThrow("FEATURE_DELIVERY_CI_NOT_SUCCESSFUL");
});
it("queries by exact SHA without the upstream branch filter", async () => {
const fetcher = vi.fn(async () => new Response(JSON.stringify({ total_count: 1, workflow_runs: [run(1)] })));
await new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch).listActionRuns("Jens", "Repo", { branch, headSha: sha });
it("queries by exact SHA and fetches jobs for the exact run attempt", async () => {
const workflowRun = run(1);
const job = fullJob(1);
const fetcher = vi.fn(async (input: string | URL | Request) => {
const url = new URL(String(input));
if (url.pathname.endsWith("/actions/runs")) return new Response(JSON.stringify({ total_count: 1, workflow_runs: [workflowRun] }));
if (url.pathname.endsWith("/actions/runs/1/attempts/1/jobs")) return new Response(JSON.stringify({ total_count: 1, jobs: [job] }));
return new Response("unexpected request", { status: 404 });
});
const result = await new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch).listActionRuns("Jens", "Repo", { branch, headSha: sha });
const url = new URL(String(fetcher.mock.calls[0]![0]));
expect(url.searchParams.get("head_sha")).toBe(sha);
expect(url.searchParams.has("branch")).toBe(false);
expect(result.workflow_runs[0]?.jobs).toEqual([job]);
expect(fetcher).toHaveBeenCalledTimes(2);
expect(new URL(String(fetcher.mock.calls[1]![0])).searchParams.get("limit")).toBe("100");
});
it("only fetches validation jobs for the requested PR when another PR has the same head SHA", async () => {
const unrelated = run(40, {
path: "managed-validation.yml@refs/pull/99/head",
pull_requests: [{ number: 99, head: { ref: branch, sha } }],
head_branch: "",
jobs: undefined,
});
const expected = run(41, {
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
jobs: undefined,
});
const fetcher = vi.fn(async (input: string | URL | Request) => {
const url = new URL(String(input));
if (url.pathname.endsWith("/actions/runs")) return new Response(JSON.stringify({ total_count: 2, workflow_runs: [unrelated, expected] }));
if (url.pathname.endsWith("/actions/runs/41/attempts/1/jobs")) return new Response(JSON.stringify({ total_count: 1, jobs: [fullJob(41)] }));
return new Response("unexpected request", { status: 404 });
});
const result = await new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch)
.listActionRuns("Jens", "Repo", { branch, headSha: sha, pullRequestNumber: 6 });
expect(result.workflow_runs.find((item) => item.id === 41)?.jobs).toHaveLength(1);
expect(result.workflow_runs.find((item) => item.id === 40)?.jobs).toBeUndefined();
expect(fetcher).toHaveBeenCalledTimes(2);
});
it("rejects duplicate job identities across paginated job results", async () => {
const workflowRun = run(42);
const firstPage = Array.from({ length: 100 }, (_, index) => fullJob(42, sha, { id: index + 1 }));
const secondPage = [fullJob(42, sha, { id: 100 })];
const fetcher = vi.fn(async (input: string | URL | Request) => {
const url = new URL(String(input));
if (url.pathname.endsWith("/actions/runs")) return new Response(JSON.stringify({ total_count: 1, workflow_runs: [workflowRun] }));
if (url.pathname.endsWith("/attempts/1/jobs")) {
return new Response(JSON.stringify({ total_count: 101, jobs: url.searchParams.get("page") === "1" ? firstPage : secondPage }));
}
return new Response("unexpected request", { status: 404 });
});
await expect(new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch)
.listActionRuns("Jens", "Repo", { branch, headSha: sha })).rejects.toMatchObject({ code: "GITEA_ACTION_JOBS_INVALID" });
});
it("paginates the exact-head run inventory so a later failed PR run blocks merge", async () => {
const successful = run(43, {
run_number: 17,
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
jobs: undefined,
});
const failed = run(44, {
run_number: 18,
path: "managed-validation.yml@refs/pull/6/head",
pull_requests: [{ number: 6, head: { ref: branch, sha } }],
head_branch: "",
conclusion: "failure",
jobs: undefined,
});
const fetcher = vi.fn(async (input: string | URL | Request) => {
const url = new URL(String(input));
if (url.pathname.endsWith("/actions/runs")) {
return new Response(JSON.stringify({ total_count: 2, workflow_runs: url.searchParams.get("page") === "1" ? [successful] : [failed] }));
}
if (url.pathname.endsWith("/actions/runs/44/attempts/1/jobs")) {
return new Response(JSON.stringify({ total_count: 1, jobs: [fullJob(44, sha, { conclusion: "failure" })] }));
}
return new Response("unexpected request", { status: 404 });
});
const result = await new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch)
.listActionRuns("Jens", "Repo", { branch, headSha: sha, pullRequestNumber: 6 });
expect(result.workflow_runs).toHaveLength(2);
expect(fetcher).toHaveBeenCalledTimes(3);
expect(() => assertSuccessfulPullRequestFullActionRun(result.workflow_runs, branch, sha, 6))
.toThrow("FEATURE_DELIVERY_PULL_REQUEST_FULL_VALIDATION_REQUIRED");
});
it("rejects repeated exact-head run identities across API pages", async () => {
const repeated = run(45);
const fetcher = vi.fn(async (input: string | URL | Request) => {
const url = new URL(String(input));
return new Response(JSON.stringify({ total_count: 2, workflow_runs: [repeated] }));
});
await expect(new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch)
.listActionRuns("Jens", "Repo", { branch, headSha: sha })).rejects.toMatchObject({ code: "GITEA_ACTION_RUNS_INVALID" });
});
it.each([
@@ -15,6 +15,7 @@ const actionRun = (input: {
}): GiteaActionRun => ({
id: input.id,
run_number: input.id,
run_attempt: 1,
display_title: input.workflow ?? "Managed validation / full",
path: `.gitea/workflows/${input.workflow ?? "managed-validation.yml"}`,
event: input.workflow ? "pull_request" : "workflow_dispatch",
@@ -23,6 +24,21 @@ const actionRun = (input: {
status: input.status ?? "completed",
conclusion: input.conclusion,
html_url: `https://gitea.example.com/runs/${input.id}`,
jobs: input.workflow ? [] : [{
id: input.id * 10,
html_url: `https://gitea.example.com/runs/${input.id}/jobs/${input.id * 10}`,
run_id: input.id,
name: "full",
labels: [],
run_attempt: 1,
head_sha: input.headSha ?? "current-sha",
head_branch: "chatgpt/feature",
status: input.status ?? "completed",
conclusion: input.conclusion,
runner_id: 1,
steps: [],
created_at: "2026-10-06T21:33:06Z",
}],
});
const productionEnv = {