pr-report-core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +5 -2
  2. package/dist/branch-rules.d.mts +30 -0
  3. package/dist/branch-rules.d.mts.map +1 -0
  4. package/dist/branch-rules.mjs +71 -0
  5. package/dist/branch-rules.mjs.map +1 -0
  6. package/dist/checks.d.mts +61 -62
  7. package/dist/checks.d.mts.map +1 -1
  8. package/dist/checks.mjs +0 -1
  9. package/dist/checks.mjs.map +1 -1
  10. package/dist/claim.d.mts +21 -15
  11. package/dist/claim.d.mts.map +1 -1
  12. package/dist/claim.mjs +1 -1
  13. package/dist/claim.mjs.map +1 -1
  14. package/dist/closing-issues.d.mts +18 -19
  15. package/dist/closing-issues.d.mts.map +1 -1
  16. package/dist/closing-issues.mjs +0 -1
  17. package/dist/closing-issues.mjs.map +1 -1
  18. package/dist/code-owners.d.mts +33 -34
  19. package/dist/code-owners.d.mts.map +1 -1
  20. package/dist/code-owners.mjs +0 -2
  21. package/dist/code-owners.mjs.map +1 -1
  22. package/dist/index.d.mts +3 -0
  23. package/dist/index.d.mts.map +1 -1
  24. package/dist/index.mjs +3 -0
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/issue-claim.d.mts +56 -0
  27. package/dist/issue-claim.d.mts.map +1 -0
  28. package/dist/issue-claim.mjs +87 -0
  29. package/dist/issue-claim.mjs.map +1 -0
  30. package/dist/issue-priority.d.mts +44 -0
  31. package/dist/issue-priority.d.mts.map +1 -0
  32. package/dist/issue-priority.mjs +91 -0
  33. package/dist/issue-priority.mjs.map +1 -0
  34. package/dist/labels.d.mts +43 -42
  35. package/dist/labels.d.mts.map +1 -1
  36. package/dist/labels.mjs +2 -1
  37. package/dist/labels.mjs.map +1 -1
  38. package/dist/merge-after.d.mts +22 -16
  39. package/dist/merge-after.d.mts.map +1 -1
  40. package/dist/merge-after.mjs +87 -24
  41. package/dist/merge-after.mjs.map +1 -1
  42. package/dist/queue-comments.d.mts +2 -2
  43. package/dist/queue-order.d.mts +32 -32
  44. package/dist/queue-order.mjs +0 -2
  45. package/dist/queue-order.mjs.map +1 -1
  46. package/dist/queue-owner.d.mts +14 -14
  47. package/dist/queue-owner.d.mts.map +1 -1
  48. package/dist/queue-owner.mjs +18 -0
  49. package/dist/queue-owner.mjs.map +1 -1
  50. package/dist/report.d.mts +4 -4
  51. package/dist/review-hold.d.mts +14 -14
  52. package/dist/ruleset.d.mts +12 -13
  53. package/dist/ruleset.d.mts.map +1 -1
  54. package/dist/ruleset.mjs +0 -1
  55. package/dist/ruleset.mjs.map +1 -1
  56. package/dist/set-aside.d.mts +29 -28
  57. package/dist/set-aside.d.mts.map +1 -1
  58. package/dist/set-aside.mjs +2 -0
  59. package/dist/set-aside.mjs.map +1 -1
  60. package/dist/stack.d.mts +14 -15
  61. package/dist/stack.d.mts.map +1 -1
  62. package/dist/stack.mjs +0 -1
  63. package/dist/stack.mjs.map +1 -1
  64. package/dist/start-after.d.mts +20 -21
  65. package/dist/start-after.d.mts.map +1 -1
  66. package/dist/start-after.mjs +0 -1
  67. package/dist/start-after.mjs.map +1 -1
  68. package/dist/summarize.d.mts +2 -2
  69. package/dist/timestamp.d.mts +10 -10
  70. package/dist/tree.d.mts +5 -5
  71. package/dist/types.d.mts +32 -33
  72. package/dist/types.d.mts.map +1 -1
  73. package/package.json +10 -10
  74. package/src/branch-rules.mts +105 -0
  75. package/src/branch-rules.test.mts +221 -0
  76. package/src/checks.mts +64 -65
  77. package/src/checks.test.mts +30 -31
  78. package/src/claim.mts +16 -16
  79. package/src/closing-issues.mts +18 -19
  80. package/src/closing-issues.test.mts +2 -2
  81. package/src/code-owners.mts +39 -41
  82. package/src/index.mts +3 -0
  83. package/src/issue-claim.mts +138 -0
  84. package/src/issue-claim.test.mts +149 -0
  85. package/src/issue-priority.mts +116 -0
  86. package/src/issue-priority.test.mts +71 -0
  87. package/src/labels.mts +43 -42
  88. package/src/merge-after.mts +142 -49
  89. package/src/merge-after.test.mts +80 -3
  90. package/src/queue-comments.mts +2 -2
  91. package/src/queue-order.mts +60 -62
  92. package/src/queue-order.test.mts +6 -6
  93. package/src/queue-owner.mts +46 -25
  94. package/src/queue-owner.test.mts +4 -2
  95. package/src/report.mts +9 -9
  96. package/src/review-hold.mts +14 -14
  97. package/src/review-hold.test.mts +3 -3
  98. package/src/ruleset.mts +12 -13
  99. package/src/set-aside.mts +30 -28
  100. package/src/set-aside.test.mts +22 -0
  101. package/src/stack.mts +14 -15
  102. package/src/stack.test.mts +4 -4
  103. package/src/start-after.mts +20 -21
  104. package/src/summarize.mts +2 -2
  105. package/src/timestamp.mts +14 -14
  106. package/src/tree.mts +5 -5
  107. package/src/types.mts +32 -33
package/dist/types.d.mts CHANGED
@@ -12,22 +12,21 @@ export type RepoRef = Readonly<{
12
12
  * What one context has reported on a head commit.
13
13
  *
14
14
  * `skipped` is its own state rather than a kind of `passed`. GitHub counts a
15
- * skip as met, and so does the verdict — but once the two are one word a
16
- * reader cannot tell them apart, and the aggregate that skipped may be from a
17
- * round that was superseded by one about to fail.
15
+ * skip as met, and so does the verdict — but once the two are one word a reader
16
+ * cannot tell them apart, and the aggregate that skipped may be from a round
17
+ * that was superseded by one about to fail.
18
18
  */
19
19
  export type ContextState = 'failed' | 'passed' | 'pending' | 'skipped';
20
20
  export type ChecksSummary = Readonly<{
21
21
  /**
22
- * `paused` is `skip-ci`: the gated jobs boot no runner and
23
- * `no-skip-ci-label` sits `pending` by design, so neither "pending" nor
24
- * "failing" describes it.
22
+ * `paused` is `skip-ci`: the gated jobs boot no runner and `no-skip-ci-label`
23
+ * sits `pending` by design, so neither "pending" nor "failing" describes it.
25
24
  */
26
25
  verdict: 'failing' | 'passed' | 'paused' | 'pending';
27
26
  /**
28
27
  * Every required context by what it reported. These four,
29
- * {@link ChecksSummary.missing} and {@link ChecksSummary.notRun} are
30
- * disjoint and cover the required list.
28
+ * {@link ChecksSummary.missing} and {@link ChecksSummary.notRun} are disjoint
29
+ * and cover the required list.
31
30
  */
32
31
  passed: readonly string[];
33
32
  failed: readonly string[];
@@ -41,9 +40,9 @@ export type ChecksSummary = Readonly<{
41
40
  missing: readonly string[];
42
41
  /**
43
42
  * Required contexts with nothing reported, on a pull request whose base the
44
- * ruleset does not cover — a stacked one. A workflow may run on pull
45
- * requests into `main` only, and then its contexts never come here; nothing
46
- * waits for them, since GitHub requires nothing of this base either.
43
+ * ruleset does not cover — a stacked one. A workflow may run on pull requests
44
+ * into `main` only, and then its contexts never come here; nothing waits for
45
+ * them, since GitHub requires nothing of this base either.
47
46
  */
48
47
  notRun: readonly string[];
49
48
  /** How many contexts the verdict was reached over. */
@@ -82,28 +81,28 @@ export type PullRequestFacts = Readonly<{
82
81
  labels: readonly Label[];
83
82
  /**
84
83
  * Whether auto-merge is armed. Separate from `merge-queued`, which is the
85
- * author saying a pull request is to be landed: the label is the request
86
- * and this is the mechanism, which `unblock-prs` arms when it picks the
87
- * pull request.
84
+ * author saying a pull request is to be landed: the label is the request and
85
+ * this is the mechanism, which `unblock-prs` arms when it picks the pull
86
+ * request.
88
87
  */
89
88
  autoMerge: boolean;
90
89
  headRef: string;
91
90
  headSha: string;
92
91
  baseRef: string;
93
92
  /**
94
- * Whether the head branch lives in a fork, which keeps it from being read
95
- * as the parent of a stacked pull request. See `stack.mts`.
93
+ * Whether the head branch lives in a fork, which keeps it from being read as
94
+ * the parent of a stacked pull request. See `stack.mts`.
96
95
  */
97
96
  fromFork: boolean;
98
97
  url: string;
99
98
  /**
100
- * When anything about the pull request last changed — a label, a comment,
101
- * an edit — which is why it is not {@link PullRequestFacts.headCommittedAt}.
99
+ * When anything about the pull request last changed — a label, a comment, an
100
+ * edit — which is why it is not {@link PullRequestFacts.headCommittedAt}.
102
101
  */
103
102
  updatedAt: string;
104
103
  /**
105
- * When the head commit was made, which is when the branch was last pushed
106
- * to as nearly as GitHub records it. Undefined when it could not be read.
104
+ * When the head commit was made, which is when the branch was last pushed to
105
+ * as nearly as GitHub records it. Undefined when it could not be read.
107
106
  */
108
107
  headCommittedAt: string | undefined;
109
108
  /** Undefined when the comparison could not be read. */
@@ -111,8 +110,8 @@ export type PullRequestFacts = Readonly<{
111
110
  /** What every context reported on the head commit, required or not. */
112
111
  reported: ReadonlyMap<string, ContextState>;
113
112
  /**
114
- * Whether any check run on the head commit has not finished, required or
115
- * not. See `anyRunInProgress` for why the required ones are not enough.
113
+ * Whether any check run on the head commit has not finished, required or not.
114
+ * See `anyRunInProgress` for why the required ones are not enough.
116
115
  */
117
116
  checksRunning: boolean;
118
117
  linkedIssues: readonly LinkedIssue[];
@@ -126,8 +125,8 @@ export type ReportEntry = PullRequestFacts & Readonly<{
126
125
  */
127
126
  stackedOn: number | undefined;
128
127
  /**
129
- * The open pull requests it merges after because an issue it closes
130
- * starts after them (`start-after.mts`'s `inheritStartAfter`), less what
128
+ * The open pull requests it merges after because an issue it closes starts
129
+ * after them (`start-after.mts`'s `inheritStartAfter`), less what
131
130
  * `mergeAfter` and `stackedOn` already say.
132
131
  */
133
132
  inheritedMergeAfter: readonly number[];
@@ -146,9 +145,9 @@ export type ReportEntry = PullRequestFacts & Readonly<{
146
145
  }>;
147
146
  /**
148
147
  * One pull request in the merge order. `repeated` marks the second and later
149
- * appearances of a pull request with more than one predecessor — declared,
150
- * or the one it is stacked on: it is drawn under each of them, but expanded
151
- * under the first only.
148
+ * appearances of a pull request with more than one predecessor — declared, or
149
+ * the one it is stacked on: it is drawn under each of them, but expanded under
150
+ * the first only.
152
151
  */
153
152
  export type TreeNode = Readonly<{
154
153
  number: number;
@@ -158,8 +157,8 @@ export type TreeNode = Readonly<{
158
157
  /**
159
158
  * One pull request that has already landed, for the section that says what
160
159
  * shipped. Nothing that is true of an open pull request — a merge order, a
161
- * check verdict, a distance from its base — is true of one that has merged,
162
- * so it is its own shape rather than an entry with the fields blanked.
160
+ * check verdict, a distance from its base — is true of one that has merged, so
161
+ * it is its own shape rather than an entry with the fields blanked.
163
162
  */
164
163
  export type MergedPullRequest = Readonly<{
165
164
  number: number;
@@ -193,8 +192,8 @@ export type OpenIssue = Readonly<{
193
192
  */
194
193
  startAfter: readonly number[];
195
194
  /**
196
- * What a `Merge-After:` in its body names. That line is not read on an
197
- * issue, and is shown as a slip to fix: `Start-After:` is the issue's.
195
+ * What a `Merge-After:` in its body names. That line is not read on an issue,
196
+ * and is shown as a slip to fix: `Start-After:` is the issue's.
198
197
  */
199
198
  misplacedMergeAfter: readonly number[];
200
199
  }>;
@@ -215,8 +214,8 @@ export type IssueDeclarations = Readonly<{
215
214
  /** Each issue's `Start-After:`; an issue not in it declares nothing. */
216
215
  startAfter: ReadonlyMap<number, readonly number[]>;
217
216
  /**
218
- * The numbers known to be open issues, beyond the ones an open pull
219
- * request links as open. Decides only what is said not to be started.
217
+ * The numbers known to be open issues, beyond the ones an open pull request
218
+ * links as open. Decides only what is said not to be started.
220
219
  */
221
220
  open: ReadonlySet<number>;
222
221
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-report-core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "What a GitHub merge queue driven by labels and `Merge-After:` trailers decides about the open pull requests — verdicts, merge order, stacks, owners — with no I/O, shared by the GitHub Pull Requests Manager page and the unblock-prs worker",
6
6
  "keywords": [
@@ -39,26 +39,26 @@
39
39
  "LICENSE"
40
40
  ],
41
41
  "dependencies": {
42
- "ts-data-forge": "^14.10.0",
43
- "ts-fortress": "^12.4.1"
42
+ "ts-data-forge": "^14.11.0",
43
+ "ts-fortress": "^12.4.4"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@octokit/openapi-types": "^29.0.1",
47
47
  "@types/node": "26.6.5",
48
- "@vitest/coverage-v8": "5.0.5",
48
+ "@vitest/coverage-istanbul": "5.0.5",
49
49
  "cspell": "10.4.0",
50
50
  "dedent": "1.7.2",
51
51
  "eslint": "10.13.0",
52
- "eslint-config-typed": "7.2.1",
53
- "eslint-plugin-ts-data-forge": "0.10.6",
54
- "eslint-plugin-ts-fortress": "1.3.11",
55
- "eslint-plugin-ts-type-forge": "0.8.16",
52
+ "eslint-config-typed": "7.5.0",
53
+ "eslint-plugin-ts-data-forge": "0.10.10",
54
+ "eslint-plugin-ts-fortress": "1.3.14",
55
+ "eslint-plugin-ts-type-forge": "0.8.19",
56
56
  "jiti": "2.7.0",
57
57
  "npm-run-all2": "9.0.4",
58
58
  "prettier": "3.9.10",
59
59
  "prettier-plugin-organize-imports": "4.3.0",
60
60
  "prettier-plugin-packagejson": "3.0.2",
61
- "ts-repo-utils": "10.7.1",
61
+ "ts-repo-utils": "10.7.3",
62
62
  "tsx": "4.23.15",
63
63
  "typescript": "6.0.3",
64
64
  "typescript-native": "npm:typescript@7.0.2",
@@ -70,7 +70,7 @@
70
70
  "pnpm": ">=9.0.0"
71
71
  },
72
72
  "volta": {
73
- "node": "26.12.1"
73
+ "node": "26.13.0"
74
74
  },
75
75
  "publishConfig": {
76
76
  "access": "public",
@@ -0,0 +1,105 @@
1
+ /**
2
+ * What GitHub enforces on a branch, read the one way every reader reads it:
3
+ * every ruleset that applies to the branch
4
+ * (`GET /repos/{owner}/{repo}/rules/branches/{branch}`), and the contexts a
5
+ * classic branch protection rule requires
6
+ * (`GET /repos/{owner}/{repo}/branches/{branch}`). Neither wants more than
7
+ * reading the repository, where the endpoints that list a ruleset's own
8
+ * settings or a protection rule's whole want an admin.
9
+ *
10
+ * What a classic rule says about reviews is not among what the second answers,
11
+ * so a code owner's approval is waited for only where a ruleset asks for it.
12
+ *
13
+ * Nothing here talks to GitHub: the reader hands over its own way of asking.
14
+ */
15
+
16
+ import { Arr, Result } from 'ts-data-forge';
17
+ import * as t from 'ts-fortress';
18
+ import { requirementsOfRules, type RulesetRequirements } from './ruleset.mjs';
19
+
20
+ /**
21
+ * One `GET`, by the reader's own transport. `path` is relative to
22
+ * `/repos/{owner}/{repo}` and may carry a query string; `what` names the
23
+ * request for a message.
24
+ */
25
+ export type BranchRulesGet = <A>(
26
+ path: string,
27
+ schema: t.Type<A>,
28
+ what: string,
29
+ ) => Promise<Result<A, string>>;
30
+
31
+ /**
32
+ * The requirements on `branch`, or why they could not be read. Either request
33
+ * failing fails it: a list of required contexts missing half of them would be
34
+ * read as the whole.
35
+ */
36
+ export const readBranchRequirements = async (
37
+ branch: string,
38
+ get: BranchRulesGet,
39
+ ): Promise<Result<RulesetRequirements, string>> => {
40
+ // One path segment, whatever the name holds: `release/1.x` included. The
41
+ // rule's premise, building a query string, is not what this is.
42
+ // eslint-disable-next-line no-restricted-globals
43
+ const segment = encodeURIComponent(branch);
44
+
45
+ const [rules, branchAnswer] = await Promise.all([
46
+ get(
47
+ `/rules/branches/${segment}?per_page=${RULES_PER_PAGE}`,
48
+ RulesSchema,
49
+ `the rules on ${branch}`,
50
+ ),
51
+ get(`/branches/${segment}`, BranchSchema, `the protection of ${branch}`),
52
+ ]);
53
+
54
+ if (Result.isErr(rules)) {
55
+ return rules;
56
+ }
57
+
58
+ if (Result.isErr(branchAnswer)) {
59
+ return branchAnswer;
60
+ }
61
+
62
+ const fromRulesets = requirementsOfRules(rules.value);
63
+
64
+ const protection = branchAnswer.value.protection;
65
+
66
+ const classic =
67
+ protection?.enabled === false
68
+ ? undefined
69
+ : protection?.required_status_checks;
70
+
71
+ return Result.ok({
72
+ ...fromRulesets,
73
+ requiredContexts: Arr.uniq([
74
+ ...fromRulesets.requiredContexts,
75
+ // GitHub's description leaves the level out of what it promises, so one
76
+ // not given is taken as enforcing.
77
+ ...(classic === undefined || classic.enforcement_level === 'off'
78
+ ? []
79
+ : classic.contexts),
80
+ ]),
81
+ });
82
+ };
83
+
84
+ /**
85
+ * As many rules as a page holds; GitHub's default is 30. Each ruleset
86
+ * contributes a handful, so a branch with more than a hundred is not one a
87
+ * person keeps.
88
+ */
89
+ const RULES_PER_PAGE = 100;
90
+
91
+ const RulesSchema = t.array(t.unknown());
92
+
93
+ const BranchSchema = t.record({
94
+ protection: t.optional(
95
+ t.record({
96
+ enabled: t.optional(t.boolean()),
97
+ required_status_checks: t.optional(
98
+ t.record({
99
+ enforcement_level: t.optional(t.string()),
100
+ contexts: t.array(t.string()),
101
+ }),
102
+ ),
103
+ }),
104
+ ),
105
+ });
@@ -0,0 +1,221 @@
1
+ import { Result } from 'ts-data-forge';
2
+ import type * as t from 'ts-fortress';
3
+ import {
4
+ readBranchRequirements,
5
+ type BranchRulesGet,
6
+ } from './branch-rules.mjs';
7
+
8
+ describe(readBranchRequirements, () => {
9
+ test('adds up the rulesets on the branch and its classic protection', async () => {
10
+ const { get, asked } = answering({
11
+ rules: [
12
+ {
13
+ type: 'pull_request',
14
+ parameters: {
15
+ require_code_owner_review: true,
16
+ required_review_thread_resolution: false,
17
+ },
18
+ },
19
+ {
20
+ type: 'required_status_checks',
21
+ parameters: {
22
+ required_status_checks: [
23
+ { context: 'code-check-result / result' },
24
+ { context: 'lint' },
25
+ ],
26
+ },
27
+ },
28
+ { type: 'deletion' },
29
+ ],
30
+ branch: {
31
+ protection: {
32
+ enabled: true,
33
+ required_status_checks: {
34
+ enforcement_level: 'non_admins',
35
+ contexts: ['lint', 'build'],
36
+ },
37
+ },
38
+ },
39
+ });
40
+
41
+ const read = await readBranchRequirements('main', get);
42
+
43
+ assert.isTrue(Result.isOk(read));
44
+
45
+ assert.deepStrictEqual(read.value, {
46
+ requiredContexts: ['code-check-result / result', 'lint', 'build'],
47
+ requireCodeOwnerReview: true,
48
+ requireConversationResolution: false,
49
+ });
50
+
51
+ assert.deepStrictEqual(asked.toSorted(), [
52
+ '/branches/main',
53
+ '/rules/branches/main?per_page=100',
54
+ ]);
55
+ });
56
+
57
+ test('reads the contexts of a classic rule where no ruleset applies', async () => {
58
+ const { get } = answering({
59
+ rules: [],
60
+ branch: {
61
+ protection: {
62
+ enabled: true,
63
+ required_status_checks: {
64
+ enforcement_level: 'everyone',
65
+ contexts: ['build'],
66
+ },
67
+ },
68
+ },
69
+ });
70
+
71
+ const read = await readBranchRequirements('main', get);
72
+
73
+ assert.isTrue(Result.isOk(read));
74
+
75
+ assert.deepStrictEqual(read.value.requiredContexts, ['build']);
76
+ });
77
+
78
+ test('requires nothing of a branch nothing protects', async () => {
79
+ const { get } = answering({ rules: [], branch: { protection: {} } });
80
+
81
+ const read = await readBranchRequirements('main', get);
82
+
83
+ assert.isTrue(Result.isOk(read));
84
+
85
+ assert.deepStrictEqual(read.value, {
86
+ requiredContexts: [],
87
+ requireCodeOwnerReview: false,
88
+ requireConversationResolution: false,
89
+ });
90
+ });
91
+
92
+ test('reads no contexts off classic protection that is switched off', async () => {
93
+ const { get } = answering({
94
+ rules: [],
95
+ branch: {
96
+ protection: {
97
+ enabled: true,
98
+ required_status_checks: {
99
+ enforcement_level: 'off',
100
+ contexts: ['stale'],
101
+ },
102
+ },
103
+ },
104
+ });
105
+
106
+ const read = await readBranchRequirements('main', get);
107
+
108
+ assert.isTrue(Result.isOk(read));
109
+
110
+ assert.deepStrictEqual(read.value.requiredContexts, []);
111
+ });
112
+
113
+ test('reads no contexts off classic protection that is disabled', async () => {
114
+ const { get } = answering({
115
+ rules: [],
116
+ branch: {
117
+ protection: {
118
+ enabled: false,
119
+ required_status_checks: { contexts: ['stale'] },
120
+ },
121
+ },
122
+ });
123
+
124
+ const read = await readBranchRequirements('main', get);
125
+
126
+ assert.isTrue(Result.isOk(read));
127
+
128
+ assert.deepStrictEqual(read.value.requiredContexts, []);
129
+ });
130
+
131
+ test('puts the branch name in the path as one segment', async () => {
132
+ const { get, asked } = answering({ rules: [], branch: {} });
133
+
134
+ await readBranchRequirements('release/1.x', get);
135
+
136
+ assert.deepStrictEqual(asked.toSorted(), [
137
+ '/branches/release%2F1.x',
138
+ '/rules/branches/release%2F1.x?per_page=100',
139
+ ]);
140
+ });
141
+
142
+ test.each(['/rules/', '/branches/main'] as const)(
143
+ 'fails when %s fails',
144
+ async (failing) => {
145
+ const get: BranchRulesGet = (path, schema, what) =>
146
+ Promise.resolve(
147
+ path.startsWith(failing)
148
+ ? Result.err(`refused ${what}`)
149
+ : validated(schema, path.startsWith('/rules/') ? [] : {}),
150
+ );
151
+
152
+ const read = await readBranchRequirements('main', get);
153
+
154
+ assert.isTrue(Result.isErr(read));
155
+
156
+ assert.include(read.value, 'main');
157
+ },
158
+ );
159
+
160
+ test('fails on rules of another shape', async () => {
161
+ const { get } = answering({ rules: { rules: [] }, branch: {} });
162
+
163
+ const read = await readBranchRequirements('main', get);
164
+
165
+ assert.isTrue(Result.isErr(read));
166
+ });
167
+
168
+ test('fails on protection of another shape', async () => {
169
+ const { get } = answering({
170
+ rules: [],
171
+ branch: { protection: { required_status_checks: { contexts: 'build' } } },
172
+ });
173
+
174
+ const read = await readBranchRequirements('main', get);
175
+
176
+ assert.isTrue(Result.isErr(read));
177
+ });
178
+
179
+ test('takes a classic rule that names no level as enforcing', async () => {
180
+ const { get } = answering({
181
+ rules: [],
182
+ branch: {
183
+ protection: { required_status_checks: { contexts: ['build'] } },
184
+ },
185
+ });
186
+
187
+ const read = await readBranchRequirements('main', get);
188
+
189
+ assert.isTrue(Result.isOk(read));
190
+
191
+ assert.deepStrictEqual(read.value.requiredContexts, ['build']);
192
+ });
193
+ });
194
+
195
+ const validated = <A,>(schema: t.Type<A>, body: unknown): Result<A, string> => {
196
+ const checked = schema.validate(body);
197
+
198
+ return Result.isErr(checked)
199
+ ? Result.err('not the shape expected')
200
+ : Result.ok(checked.value);
201
+ };
202
+
203
+ /** A `get` answering the two paths with the bodies given. */
204
+ const answering = (
205
+ bodies: Readonly<{ rules: unknown; branch: unknown }>,
206
+ ): Readonly<{ get: BranchRulesGet; asked: readonly string[] }> => {
207
+ const mut_asked: string[] = [];
208
+
209
+ const get: BranchRulesGet = (path, schema) => {
210
+ mut_asked.push(path);
211
+
212
+ return Promise.resolve(
213
+ validated(
214
+ schema,
215
+ path.startsWith('/rules/') ? bodies.rules : bodies.branch,
216
+ ),
217
+ );
218
+ };
219
+
220
+ return { get, asked: mut_asked };
221
+ };