From 3ac7dbda9595b7988fdc87b32e3029fd82598bb3 Mon Sep 17 00:00:00 2001 From: quality Date: Thu, 8 Oct 2026 02:13:39 -0400 Subject: [PATCH 1/2] test(bundle): drive the authorization section's schema rejections and the cross-tier users union through dist/index.js normalizeAuthorization's non-mapping, unknown-key, bad-policy and users rejections (config.ts 666, 671, 686, normalizeLogins 704) and mergeAuthorization's case-insensitive dedup of users listed by both the organization and the repository tier (753) had unit hits only; this file reaches them through a /triage accepted against a malformed .github/prow.yaml and a two-tier configuration. Signed-off-by: quality --- .../bundle/authorizationSchemaArms.test.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 __tests__/bundle/authorizationSchemaArms.test.ts diff --git a/__tests__/bundle/authorizationSchemaArms.test.ts b/__tests__/bundle/authorizationSchemaArms.test.ts new file mode 100644 index 0000000..c0f04b4 --- /dev/null +++ b/__tests__/bundle/authorizationSchemaArms.test.ts @@ -0,0 +1,79 @@ +import type { FakeGithub } from './fakeGithub' +import { Buffer } from 'node:buffer' +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, token } from './helpers' +import { runBundle } from './runBundle' + +vi.setConfig({ testTimeout: 30_000 }) + +const repoSource = 'Codertocat/Hello-World:.github/prow.yaml' +const orgPath = '/repos/Codertocat/.project/contents/prow.yaml' +const repoPath = `${repo}/contents/.github%2Fprow.yaml` + +function yamlFile(text: string) { + const file = structuredClone(labelFileContents) + file.content = Buffer.from(text).toString('base64') + return file +} + +// the `authorization` section's normalizer (config.ts normalizeAuthorization / normalizeLogins) and the +// cross-tier `users` union (mergeAuthorization), driven through dist/index.js with a `/triage accepted` +describe('dist/index.js authorization section schema', () => { + let gh: FakeGithub + const { expectRequests } = helpersFor(() => gh) + + beforeAll(async () => { + gh = await start() + }) + afterEach(() => gh.reset()) + afterAll(() => gh.close()) + + function triage(login = 'Codertocat') { + const payload = comment('/triage accepted') + payload.comment.user.login = login + gh.route('GET', `${repo}/labels`, { status: 200, body: [{ name: 'triage/accepted' }] }) + gh.route('POST', `${repo}/issues/1/labels`, { status: 200, body: [] }) + return runBundle({ + eventName: 'issue_comment', + payload, + inputs: { ...token, 'prow-commands': '/triage' }, + apiUrl: gh.url, + }) + } + + describe('a malformed section in the repository file', () => { + it.each([ + ['authorization that is not a mapping', 'authorization: trusted\n', `${repoSource}: authorization must be a mapping`], + ['an unknown authorization key', 'authorization:\n approve: anyone\n', `${repoSource}: authorization.approve is not a known key, expected one of labels, hold, close, review, users`], + ['authorization.labels outside the policy set', 'authorization:\n labels: owners\n', `${repoSource}: authorization.labels must be one of anyone, collaborators, members, trusted`], + ['authorization.review outside the review policy set', 'authorization:\n review: anyone\n', `${repoSource}: authorization.review must be one of members, trusted`], + ['authorization.users that is not a list', 'authorization:\n users: friend\n', `${repoSource}: authorization.users must be a list of logins`], + ['authorization.users with a login that is not a GitHub login', 'authorization:\n users: ["@friend"]\n', `${repoSource}: authorization.users must be a list of logins`], + ])('%s fails the command naming the field, with no label write', async (_name, yaml, message) => { + gh.route('GET', repoPath, { status: 200, body: yamlFile(`labels:\n triage:\n values: [accepted]\n${yaml}`) }) + + const result = await triage() + + expect(result.status, result.stdout).toBe(1) + expect(result.errors.some(e => e.includes('could not get labels from yaml: ') && e.includes(message)), result.stdout).toBe(true) + expect(gh.requestsMatching('POST', /\/issues\/1\/labels$/)).toEqual([]) + }) + }) + + describe('users listed by both the organization and the repository tier', () => { + it('are unioned case-insensitively and admit the login with no membership call', async () => { + gh.route('GET', orgPath, { status: 200, body: yamlFile('authorization:\n labels: trusted\n users: [Friend, Other]\n') }) + gh.route('GET', repoPath, { status: 200, body: yamlFile('labels:\n triage:\n values: [accepted]\nauthorization:\n users: [friend]\n') }) + + const result = await triage('friend') + + expect(result.status, result.stdout).toBe(0) + expect(result.errors).toEqual([]) + expect(gh.requestsMatching('POST', /\/issues\/1\/labels$/)[0].body).toEqual({ labels: ['triage/accepted'] }) + expectRequests(configReads({ org: '.project', repo: '.github/prow.yaml' }), [`GET ${repo}/labels?per_page=100`, `POST ${repo}/issues/1/labels`]) + }) + }) +}) From 84747444276c08a7b26c507ccfb70eac3caf13c2 Mon Sep 17 00:00:00 2001 From: quality Date: Thu, 8 Oct 2026 22:57:55 -0400 Subject: [PATCH 2/2] test(bundle): run the users-union case as an organization-only login and drop the policy-set rows #378 reaches Signed-off-by: quality --- __tests__/bundle/authorizationSchemaArms.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/__tests__/bundle/authorizationSchemaArms.test.ts b/__tests__/bundle/authorizationSchemaArms.test.ts index c0f04b4..d231578 100644 --- a/__tests__/bundle/authorizationSchemaArms.test.ts +++ b/__tests__/bundle/authorizationSchemaArms.test.ts @@ -48,8 +48,6 @@ describe('dist/index.js authorization section schema', () => { it.each([ ['authorization that is not a mapping', 'authorization: trusted\n', `${repoSource}: authorization must be a mapping`], ['an unknown authorization key', 'authorization:\n approve: anyone\n', `${repoSource}: authorization.approve is not a known key, expected one of labels, hold, close, review, users`], - ['authorization.labels outside the policy set', 'authorization:\n labels: owners\n', `${repoSource}: authorization.labels must be one of anyone, collaborators, members, trusted`], - ['authorization.review outside the review policy set', 'authorization:\n review: anyone\n', `${repoSource}: authorization.review must be one of members, trusted`], ['authorization.users that is not a list', 'authorization:\n users: friend\n', `${repoSource}: authorization.users must be a list of logins`], ['authorization.users with a login that is not a GitHub login', 'authorization:\n users: ["@friend"]\n', `${repoSource}: authorization.users must be a list of logins`], ])('%s fails the command naming the field, with no label write', async (_name, yaml, message) => { @@ -68,7 +66,8 @@ describe('dist/index.js authorization section schema', () => { gh.route('GET', orgPath, { status: 200, body: yamlFile('authorization:\n labels: trusted\n users: [Friend, Other]\n') }) gh.route('GET', repoPath, { status: 200, body: yamlFile('labels:\n triage:\n values: [accepted]\nauthorization:\n users: [friend]\n') }) - const result = await triage('friend') + // `other` is listed only by the organization tier, so it is admitted only if the lists are unioned + const result = await triage('other') expect(result.status, result.stdout).toBe(0) expect(result.errors).toEqual([])