Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions __tests__/bundle/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import pullReqOpenedEvent from '../fixtures/pullReq/pullReqOpenedEvent.json'
import pullReqReviewSubmittedEvent from '../fixtures/pullReq/pullReqReviewSubmittedEvent.json'
import { blobSha, prCommentEvent, pullBody } from '../utils/ownersFixtures'
import { start } from './fakeGithub'
import { comment, configReads, helpersFor, membershipReads, ownersProbe, ownersReads, queueRead, repo, token } from './helpers'
import { comment, configReads, helpersFor, membershipReads, ownersProbe, ownersReads, queueRead, repo, repoLabels, token } from './helpers'
import { bundlePath, runBundle } from './runBundle'

vi.setConfig({ testTimeout: 30_000 })
Expand All @@ -26,10 +26,6 @@ function openPr(labels: string[], overrides: Record<string, unknown> = {}) {
return { ...pr, labels: labels.map(name => ({ name })), ...overrides }
}

function repoLabels(...names: string[]) {
return { status: 200, body: names.map(name => ({ name })) }
}

function yamlFile(text: string) {
const file = structuredClone(labelFileContents)
file.content = Buffer.from(text).toString('base64')
Expand Down
5 changes: 5 additions & 0 deletions __tests__/bundle/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export function membershipReads(login: string) {
return [`GET /orgs/Codertocat/members/${login}`, `GET ${repo}/collaborators/${login}`]
}

// the repository's label list, as the label commands read it before applying a label
export function repoLabels(...names: string[]) {
return { status: 200, body: names.map(name => ({ name })) }
}

// the helpers that route or inspect a suite's fake GitHub; the suite starts it in beforeAll, after binding these,
// so `fake` is called on each use
export function helpersFor(fake: () => FakeGithub) {
Expand Down
74 changes: 74 additions & 0 deletions __tests__/bundle/labelCommands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { FakeGithub } from './fakeGithub'
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'

import labelFileContents from '../fixtures/labels/labelFileContentsResp.json'
import { start } from './fakeGithub'
import { comment, configReads, helpersFor, repo, repoLabels, token } from './helpers'
import { runBundle } from './runBundle'

vi.setConfig({ testTimeout: 30_000 })

const labelsRead = `GET ${repo}/labels?per_page=100`
const issueRead = `GET ${repo}/issues/1`

function issueLabels(...names: string[]) {
return { status: 200, body: { labels: names.map(name => ({ name })) } }
}

function deleteRoute(label: string) {
return `DELETE ${repo}/issues/1/labels/${encodeURIComponent(label)}`
}

// the label-command paths dist/index.js is not otherwise driven through: a fixed-label removal, a prefixed
// command served by Prow's built-in defaults, and the no-configuration failure docs/labeling.md promises
describe('dist/index.js fixed and built-in prefixed label commands', () => {
let gh: FakeGithub
const { expectCommandThenConfig, expectRequests } = helpersFor(() => gh)

beforeAll(async () => {
gh = await start()
})
afterEach(() => gh.reset())
afterAll(() => gh.close())

function run(body: string, command: string) {
return runBundle({ eventName: 'issue_comment', payload: comment(body), inputs: { ...token, 'prow-commands': command }, apiUrl: gh.url })
}

it('/remove-help removes help wanted and good first issue, matching label case-insensitively', async () => {
gh.route('GET', `${repo}/issues/1`, issueLabels('Help Wanted', 'good first issue', 'kind/bug'))
gh.route('DELETE', `${repo}/issues/1/labels/Help%20Wanted`, { status: 200, body: [] })
gh.route('DELETE', `${repo}/issues/1/labels/good%20first%20issue`, { status: 200, body: [] })

const result = await run('/remove-help', '/help')

expect(result.status, result.stdout).toBe(0)
expect(result.errors).toEqual([])
expect(gh.requestsMatching('POST', /./)).toEqual([])
expectCommandThenConfig([issueRead, deleteRoute('Help Wanted'), deleteRoute('good first issue')])
})

// .prowlabels.yaml has no lifecycle key, so the command falls back to Prow's built-in values
it('/lifecycle stale from the built-in defaults is exclusive: removes lifecycle/frozen, then adds lifecycle/stale', async () => {
gh.route('GET', `${repo}/contents/.prowlabels.yaml`, { status: 200, body: labelFileContents })
gh.route('GET', `${repo}/issues/1`, issueLabels('lifecycle/frozen', 'kind/bug'))
gh.route('DELETE', `${repo}/issues/1/labels/lifecycle%2Ffrozen`, { status: 200, body: [] })
gh.route('GET', `${repo}/labels`, repoLabels('lifecycle/frozen', 'lifecycle/stale', 'lifecycle/rotten'))
gh.route('POST', `${repo}/issues/1/labels`, { status: 200, body: [] })

const result = await run('/lifecycle stale', '/lifecycle')

expect(result.status, result.stdout).toBe(0)
expect(result.errors).toEqual([])
expect(gh.requestsMatching('POST', /\/issues\/1\/labels$/).map(r => r.body)).toEqual([{ labels: ['lifecycle/stale'] }])
expectRequests(configReads({ repo: '.prowlabels.yaml' }), [issueRead, deleteRoute('lifecycle/frozen'), labelsRead, `POST ${repo}/issues/1/labels`])
})

it('/lifecycle stale with no configuration file in any tier fails the run, as docs/labeling.md says it must', async () => {
const result = await run('/lifecycle stale', '/lifecycle')

expect(result.status, result.stdout).toBe(1)
expect(result.errors.some(e => e.includes('no prow configuration found'))).toBe(true)
expectRequests(configReads(), [])
})
})
Loading