openpencil/tools/pr-review-guidance/tests/index.test.ts
2026-06-07 09:15:15 +03:00

144 lines
4.7 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
eventContext,
monitorPRReviewGuidance,
reviewGuidanceCheckName,
reviewGuidanceChecks
} from '../src/index'
function response(body: unknown, init: ResponseInit = {}) {
return new Response(JSON.stringify(body), {
headers: { 'content-type': 'application/json' },
...init
})
}
async function writeEvent(event: unknown) {
const dir = await mkdtemp(join(tmpdir(), 'open-pencil-pr-guidance-'))
const path = join(dir, 'event.json')
await writeFile(path, JSON.stringify(event), 'utf8')
return path
}
describe('reviewGuidanceCheckName', () => {
test('accepts legacy PR Hygiene errors from CodeRabbit tables', () => {
expect(reviewGuidanceCheckName('| PR Hygiene: Template | ❌ Error | Missing template |')).toBe(
'PR Hygiene: Template'
)
})
test('accepts softer PR Description check names', () => {
expect(reviewGuidanceCheckName('| PR Description: Context | Error | Add validation |')).toBe(
'PR Description: Context'
)
})
test('ignores unrelated or passing checks', () => {
expect(reviewGuidanceCheckName('| Security | ❌ Error | Something else |')).toBeNull()
expect(reviewGuidanceCheckName('| PR Description: Context | ✅ Pass | Fine |')).toBeNull()
})
})
describe('reviewGuidanceChecks', () => {
test('extracts only relevant failed checks', () => {
const checks = reviewGuidanceChecks(`
| Check name | Status | Explanation |
|---|---|---|
| PR Description: Template | ❌ Error | Missing template |
| Security | ❌ Error | Not relevant |
| PR Readability: English | Error | Needs English |
`)
expect(checks).toEqual(['PR Description: Template', 'PR Readability: English'])
})
})
describe('eventContext', () => {
test('reads pull request review events', () => {
expect(
eventContext({
pull_request: { number: 42 },
review: { state: 'changes_requested', body: 'review body' }
})
).toEqual({ issueNumber: 42, shouldInspect: true, text: 'changes_requested\nreview body' })
})
test('reads pull request issue comments', () => {
expect(
eventContext({
issue: { number: 42, pull_request: {} },
comment: { body: 'comment body' }
})
).toEqual({ issueNumber: 42, shouldInspect: true, text: 'comment body' })
})
})
describe('monitorPRReviewGuidance', () => {
test('logs a maintainer note and only reads PR data', async () => {
const eventPath = await writeEvent({
sender: { login: 'coderabbitai[bot]' },
issue: { number: 294, pull_request: {} },
comment: {
body: '| Check name | Status | Explanation |\n|---|---|---|\n| PR Description: Template | ❌ Error | Missing template |'
}
})
const requests: string[] = []
const messages: string[] = []
await monitorPRReviewGuidance({
env: {
GITHUB_API_URL: 'https://example.test',
GITHUB_EVENT_PATH: eventPath,
GITHUB_REPOSITORY: 'open-pencil/open-pencil',
GITHUB_REPOSITORY_OWNER: 'open-pencil',
GITHUB_TOKEN: 'token'
},
fetchImpl: (async (input, init) => {
requests.push(`${init?.method ?? 'GET'} ${String(input)}`)
return response({
author_association: 'CONTRIBUTOR',
state: 'open',
title: 'feat(dev-install): add script',
user: { login: 'joeycumines' }
})
}) satisfies typeof fetch,
log: (message) => messages.push(message)
})
expect(requests).toEqual(['GET https://example.test/repos/open-pencil/open-pencil/pulls/294'])
expect(messages.join('\n')).toContain('No automatic label, comment, or close was applied')
})
test('ignores non-CodeRabbit comments', async () => {
const eventPath = await writeEvent({
sender: { login: 'contributor' },
issue: { number: 294, pull_request: {} },
comment: { body: '| PR Description: Template | ❌ Error | Missing template |' }
})
const requests: string[] = []
const messages: string[] = []
await monitorPRReviewGuidance({
env: {
GITHUB_API_URL: 'https://example.test',
GITHUB_EVENT_PATH: eventPath,
GITHUB_REPOSITORY: 'open-pencil/open-pencil',
GITHUB_REPOSITORY_OWNER: 'open-pencil',
GITHUB_TOKEN: 'token'
},
fetchImpl: (async (input, init) => {
requests.push(`${init?.method ?? 'GET'} ${String(input)}`)
return response({})
}) satisfies typeof fetch,
log: (message) => messages.push(message)
})
expect(requests).toEqual([])
expect(messages).toEqual(['No action needed: event sender is contributor, not CodeRabbit.'])
})
})