Untangle Spike architecture with bridge pattern #9

Merged
probablycorey merged 3 commits from probablycorey/ai-project-structure into main 2026-03-09 23:03:30 +00:00
2 changed files with 46 additions and 17 deletions
Showing only changes of commit a71bf2d492 - Show all commits

View File

@ -228,10 +228,30 @@ export async function commentOnPR(repo: string, prNumber: number, body: string,
}
export async function mergePR(repo: string, prNumber: number, user: User) {
return giteaFetch(`/repos/${repo}/pulls/${prNumber}/merge`, user, {
method: "POST",
body: JSON.stringify({ Do: "merge", merge_message_field: "Test merge" }),
})
// Gitea returns 404/405 while computing mergeability after PR creation
const path = `/repos/${repo}/pulls/${prNumber}/merge`
for (let attempt = 0; attempt < 10; attempt++) {
const response = await fetch(`${giteaUrl}/api/v1${path}`, {
method: "POST",
headers: {
Authorization: `token ${user.token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ Do: "merge" }),
})
if (response.ok) return
if (response.status === 404 || response.status === 405) {
await Bun.sleep(1000)
continue
}
const body = await response.text().catch(() => "")
throw new Error(`Gitea ${response.status}: POST ${path}\n${body}`)
}
throw new Error(`Gitea merge failed after 10 retries for ${path}`)
}
export async function createReview(repo: string, prNumber: number, user: User, filePath: string) {

View File

@ -4,7 +4,6 @@ setDefaultTimeout(30_000)
import { ensure } from "@workshop/shared/utils"
import {
type User,
type TestPR,
setupWebhooks,
expectPullRequestWebhook,
expectIssueCommentWebhook,
@ -15,6 +14,10 @@ import {
mergePR,
} from "./helpers"
function testBranch(label: string) {
return `test-${crypto.randomUUID().slice(0, 8)}-${label}`
}
function getEnv(name: string): string {
const value = process.env[name]
ensure(value, `Missing env var: ${name}. See .env.example`)
@ -37,20 +40,19 @@ afterAll(() => teardown())
// --- Tests for Corey's repo (Spike opens PR, Corey comments/reviews/merges) ---
describe(`${coreyRepo}`, () => {
let pr: TestPR
const branch = `test-${Date.now()}-corey`
test("opening a PR sends pull_request webhook with correct author and repo", async () => {
pr = await expectPullRequestWebhook({ action: "opened", branch }, async (webhook) => {
const result = await openTestPR(coreyRepo, spike, branch)
const branch = testBranch("open")
await expectPullRequestWebhook({ action: "opened", branch }, async (webhook) => {
await openTestPR(coreyRepo, spike, branch)
const payload = await webhook
expect(payload.pull_request.user.login).toBe(spike.username)
expect(payload.repository.full_name).toBe(coreyRepo)
return result
})
})
test("commenting sends issue_comment webhook with correct user", async () => {
const branch = testBranch("comment")
const pr = await openTestPR(coreyRepo, spike, branch)
await expectIssueCommentWebhook({ action: "created", username: corey.username, prNumber: pr.number }, async (webhook) => {
await commentOnPR(coreyRepo, pr.number, "Looks good!", corey)
const payload = await webhook
@ -59,6 +61,8 @@ describe(`${coreyRepo}`, () => {
})
test("review with line comment sends pull_request_comment webhook", async () => {
const branch = testBranch("review")
const pr = await openTestPR(coreyRepo, spike, branch)
await expectPullRequestCommentWebhook({ action: "reviewed", branch }, async (webhook) => {
await createReview(coreyRepo, pr.number, corey, pr.filename)
const payload = await webhook
@ -68,6 +72,8 @@ describe(`${coreyRepo}`, () => {
})
test("merging sends pull_request closed webhook", async () => {
const branch = testBranch("merge")
const pr = await openTestPR(coreyRepo, spike, branch)
await expectPullRequestWebhook({ action: "closed", branch }, async (webhook) => {
await mergePR(coreyRepo, pr.number, corey)
const payload = await webhook
@ -79,20 +85,19 @@ describe(`${coreyRepo}`, () => {
// --- Tests for Spike's repo (Corey opens PR, Spike comments/reviews/merges) ---
describe(`${spikeRepo}`, () => {
let pr: TestPR
const branch = `test-${Date.now()}-spike`
test("opening a PR sends pull_request webhook with correct author and repo", async () => {
pr = await expectPullRequestWebhook({ action: "opened", branch }, async (webhook) => {
const result = await openTestPR(spikeRepo, corey, branch)
const branch = testBranch("open")
await expectPullRequestWebhook({ action: "opened", branch }, async (webhook) => {
await openTestPR(spikeRepo, corey, branch)
const payload = await webhook
expect(payload.pull_request.user.login).toBe(corey.username)
expect(payload.repository.full_name).toBe(spikeRepo)
return result
})
})
test("commenting sends issue_comment webhook with correct user", async () => {
const branch = testBranch("comment")
const pr = await openTestPR(spikeRepo, corey, branch)
await expectIssueCommentWebhook({ action: "created", username: spike.username, prNumber: pr.number }, async (webhook) => {
await commentOnPR(spikeRepo, pr.number, "On it!", spike)
const payload = await webhook
@ -101,6 +106,8 @@ describe(`${spikeRepo}`, () => {
})
test("review with line comment sends pull_request_comment webhook", async () => {
const branch = testBranch("review")
const pr = await openTestPR(spikeRepo, corey, branch)
await expectPullRequestCommentWebhook({ action: "reviewed", branch }, async (webhook) => {
await createReview(spikeRepo, pr.number, spike, pr.filename)
const payload = await webhook
@ -110,6 +117,8 @@ describe(`${spikeRepo}`, () => {
})
test("merging sends pull_request closed webhook", async () => {
const branch = testBranch("merge")
const pr = await openTestPR(spikeRepo, corey, branch)
await expectPullRequestWebhook({ action: "closed", branch }, async (webhook) => {
await mergePR(spikeRepo, pr.number, spike)
const payload = await webhook