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
47 changes: 47 additions & 0 deletions __tests__/plugins/tide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ describe('fetchMergeability', () => {

await expect(getPull(octokit, context, 1)).resolves.toMatchObject({ draft: false })
})

it('reads a head whose repository is gone (head.repo null) as not a fork', async () => {
servePull(pull([], {
head: { sha: 'headsha', repo: null },
base: { ref: 'master', sha: 'basesha', repo: { full_name: 'Codertocat/Hello-World' } },
}))

await expect(getPull(octokit, context, 1)).resolves.toMatchObject({ fork: false })
})
})

describe('mergeOnce', () => {
Expand Down Expand Up @@ -214,6 +223,19 @@ describe('tryMergePullRequest', () => {
expect(sleep).not.toHaveBeenCalled()
})

// the draft flag is read before mergeable_state: a draft is refused without waiting for GitHub to compute the state
it('does not wait for an unknown state on a draft pr', async () => {
const gets = servePull(pull(['lgtm'], { ...unknown, draft: true }))
const merge = observeMerge()
const info = vi.spyOn(core, 'info')

await expect(tryMergePullRequest(octokit, context, 1, tide)).resolves.toBe('skipped')
await expect(merge.notCalled()).resolves.toBe('not called')
expect(gets).toHaveLength(1)
expect(sleep).not.toHaveBeenCalled()
expect(info).toHaveBeenCalledWith('skipping pr #1: not mergeable (draft)')
})

it('waits for an unknown state when the gate passes, then merges', async () => {
const gets = servePull(pull(['lgtm'], unknown), pull(['lgtm']))
const merge = observeMerge()
Expand Down Expand Up @@ -466,6 +488,31 @@ describe('tryMergePullRequest', () => {
expect(body).toContain('workflow files (`.github/workflows/a.yml`, `.github/workflows/b.yml`, `.github/workflows/c.yml`, `.github/workflows/d.yml`, `.github/workflows/e.yml` and 2 more) that it changes.')
})

it('exactly five workflow files are all listed, with no "and 0 more"', async () => {
servePull(forkPull())
observeMerge(403, forbidden)
const own = ['a', 'b', 'c', 'd', 'e'].map(name => `.github/workflows/${name}.yml`)
const diff = serveDiff([], own)
vi.spyOn(core, 'warning').mockImplementation(() => {})

await expect(tryMergePullRequest(octokit, context, 1, tide)).resolves.toBe('skipped')
const body = (await diff.comment.body()).body as string
expect(body).toContain('workflow files (`.github/workflows/a.yml`, `.github/workflows/b.yml`, `.github/workflows/c.yml`, `.github/workflows/d.yml`, `.github/workflows/e.yml`) that it changes.')
expect(body).not.toContain('more')
})

it('only paths under the top-level .github/workflows/ count: a nested copy is not a workflow file', async () => {
servePull(forkPull())
observeMerge(403, forbidden)
const diff = serveDiff(['docs/.github/workflows/example.yml'], ['templates/.github/workflows/prow.yml'])
const error = vi.spyOn(core, 'error').mockImplementation(() => {})

await expect(tryMergePullRequest(octokit, context, 1, tide)).resolves.toBe('failed')
await expect(diff.compare.called()).resolves.toBe('called')
await expect(diff.comment.notCalled()).resolves.toBe('not called')
expect(error).toHaveBeenCalledWith(expect.stringContaining('Resource not accessible by integration'))
})

it('a same-repository pull request keeps the raw failure and never compares', async () => {
servePull(pull(['lgtm'], { head: { sha: 'headsha', repo: { full_name: 'Codertocat/Hello-World' } }, base: { ref: 'master', sha: 'basesha', repo: { full_name: 'Codertocat/Hello-World' } } }))
observeMerge(403, forbidden)
Expand Down
22 changes: 21 additions & 1 deletion __tests__/plugins/tideMergeQueue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,19 @@ describe('evaluateMerge on a branch that requires a merge queue', () => {
expect(mutations(calls, 'enqueuePullRequest')).toHaveLength(0)
})

// without a draft field the gate cannot see the draft; mergeable_state: draft is still ours to refuse, not the queue's
it('a pr without a draft field whose mergeable_state is draft is refused, not enqueued', async () => {
const { draft, ...noDraft } = pull(['lgtm'], { mergeable_state: 'draft' })
expect(draft).toBe(false)
servePull(noDraft)
const merge = observeMerge()
const calls = serveGraphql({ enabled: true })

await expect(evaluateMerge(octokit, context, 1, tide)).resolves.toEqual({ result: 'skipped', reason: 'not mergeable (draft)' })
await expect(merge.notCalled()).resolves.toBe('not called')
expect(mutations(calls, 'enqueuePullRequest')).toHaveLength(0)
})

it('an unknown state is retried, then enqueued', async () => {
const gets = servePull(pull(['lgtm'], { mergeable: null, mergeable_state: 'unknown' }), pull(['lgtm']))
const calls = serveGraphql()
Expand Down Expand Up @@ -282,14 +295,19 @@ describe('evaluateMerge on a branch that requires a merge queue', () => {
servePull(pull(['lgtm']))
const merge = observeMerge(405, { message: 'Changes must be made through the merge queue.' })
vi.spyOn(core, 'error').mockImplementation(() => {})
// no /graphql handler: an unhandled request would fail the test
// queueState swallows a failed read (one warning, then a direct merge), so an unhandled
// request would not fail the test: count the calls instead
const calls = serveGraphql({ enabled: true })
const warning = vi.spyOn(core, 'warning')

await expect(evaluateMerge(octokit, context, 1, resolveTide({ merge_queue: 'off' }))).resolves.toEqual({
result: 'failed',
message: expect.stringContaining('Changes must be made through the merge queue.'),
status: 405,
})
await expect(merge.called()).resolves.toBe('called')
expect(calls).toHaveLength(0)
expect(warning).not.toHaveBeenCalled()
})

it('debugs once that the queue\'s merge method wins over tide.merge_method', async () => {
Expand Down Expand Up @@ -374,8 +392,10 @@ describe('dequeue when the gate breaks', () => {

it('merge_queue: off: no GraphQL call when the gate fails', async () => {
servePull(pull([]))
const calls = serveGraphql({ enabled: true })

await expect(evaluateMerge(octokit, context, 1, resolveTide({ merge_queue: 'off' }))).resolves.toEqual({ result: 'skipped', reason: 'missing lgtm' })
expect(calls).toHaveLength(0)
})

it('a stale lgtm on an in-queue bot-enqueued pr is stripped and dequeued', async () => {
Expand Down
Loading