Skip to content

Commit 25fcf32

Browse files
authored
Merge pull request #123 from fensak-io/main
Release
2 parents c232709 + 927b885 commit 25fcf32

File tree

7 files changed

+333
-20
lines changed

7 files changed

+333
-20
lines changed

deployments/release/integration_test.ts

Lines changed: 268 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Deno.test("auto-approve happy path for README update", async (t) => {
145145
repoName,
146146
branchName,
147147
);
148-
assertEquals(checkRun.conclusion, "success");
148+
expectCheckConclusion(checkRun, "success");
149149
});
150150

151151
await t.step("[cleanup] close PR", async () => {
@@ -215,7 +215,7 @@ Deno.test("manual review required for config update", async (t) => {
215215
previousCheckRuns,
216216
);
217217
previousCheckRuns.push(checkRun.id);
218-
assertEquals(checkRun.conclusion, "action_required");
218+
expectCheckConclusion(checkRun, "action_required");
219219
});
220220

221221
await t.step(
@@ -236,7 +236,7 @@ Deno.test("manual review required for config update", async (t) => {
236236
previousCheckRuns,
237237
);
238238
previousCheckRuns.push(checkRun.id);
239-
assertEquals(checkRun.conclusion, "action_required");
239+
expectCheckConclusion(checkRun, "action_required");
240240
},
241241
);
242242

@@ -258,7 +258,260 @@ Deno.test("manual review required for config update", async (t) => {
258258
previousCheckRuns,
259259
);
260260
previousCheckRuns.push(checkRun.id);
261-
assertEquals(checkRun.conclusion, "success");
261+
expectCheckConclusion(checkRun, "success");
262+
},
263+
);
264+
265+
await t.step("[cleanup] close PR", async () => {
266+
if (prNum) {
267+
await testCommitterOctokit.pulls.update({
268+
owner: testOrg,
269+
repo: repoName,
270+
pull_number: prNum,
271+
state: "closed",
272+
});
273+
}
274+
});
275+
276+
await t.step("[cleanup] delete branch", async () => {
277+
await deleteBranch(
278+
testCommitterOctokit,
279+
testOrg,
280+
repoName,
281+
branchName,
282+
);
283+
});
284+
});
285+
286+
Deno.test("failed required rule fails check", async (t) => {
287+
const repoName = "test-fensak-automated-appdeploy";
288+
const branchName = `test/update-config-${getRandomString(6)}`;
289+
const defaultBranchName = "main";
290+
const previousCheckRuns: number[] = [];
291+
let prNum = 0;
292+
293+
await t.step("create branch", async () => {
294+
await createBranchFromDefault(
295+
testCommitterOctokit,
296+
testOrg,
297+
repoName,
298+
branchName,
299+
);
300+
});
301+
302+
await t.step("commit update to appversions.json and open PR", async () => {
303+
await commitFileUpdateToBranch(
304+
testCommitterOctokit,
305+
testOrg,
306+
repoName,
307+
branchName,
308+
"appversions.json",
309+
'{\n "coreapp": "v0.1.0",\n "subapp": "v1.2.0",\n "logapp": "v100.1.0"\n}',
310+
);
311+
312+
const { data: pullRequest } = await testCommitterOctokit.pulls.create({
313+
owner: testOrg,
314+
repo: repoName,
315+
head: branchName,
316+
base: defaultBranchName,
317+
title: "[automated-staging-test] Failed required review fails check",
318+
});
319+
prNum = pullRequest.number;
320+
});
321+
322+
await t.step("validate check failed from Fensak Staging", async () => {
323+
const checkRun = await waitForFensakStagingCheck(
324+
testCommitterOctokit,
325+
testOrg,
326+
repoName,
327+
branchName,
328+
previousCheckRuns,
329+
);
330+
previousCheckRuns.push(checkRun.id);
331+
expectCheckConclusion(checkRun, "action_required");
332+
});
333+
334+
await t.step(
335+
"approve with trusted user and validate check still fails from Fensak Staging",
336+
async () => {
337+
await approvePR(
338+
fensakOpsAdminOctokit,
339+
testOrg,
340+
repoName,
341+
prNum,
342+
);
343+
344+
const checkRun = await waitForFensakStagingCheck(
345+
testCommitterOctokit,
346+
testOrg,
347+
repoName,
348+
branchName,
349+
previousCheckRuns,
350+
);
351+
previousCheckRuns.push(checkRun.id);
352+
expectCheckConclusion(checkRun, "action_required");
353+
},
354+
);
355+
356+
await t.step("[cleanup] close PR", async () => {
357+
if (prNum) {
358+
await testCommitterOctokit.pulls.update({
359+
owner: testOrg,
360+
repo: repoName,
361+
pull_number: prNum,
362+
state: "closed",
363+
});
364+
}
365+
});
366+
367+
await t.step("[cleanup] delete branch", async () => {
368+
await deleteBranch(
369+
testCommitterOctokit,
370+
testOrg,
371+
repoName,
372+
branchName,
373+
);
374+
});
375+
});
376+
377+
Deno.test("passed required rule and passed automerge passes check", async (t) => {
378+
const repoName = "test-fensak-automated-appdeploy";
379+
const branchName = `feature/update-config-${getRandomString(6)}`;
380+
const defaultBranchName = "main";
381+
const previousCheckRuns: number[] = [];
382+
let prNum = 0;
383+
384+
await t.step("create branch", async () => {
385+
await createBranchFromDefault(
386+
testCommitterOctokit,
387+
testOrg,
388+
repoName,
389+
branchName,
390+
);
391+
});
392+
393+
await t.step("commit update to appversions.json and open PR", async () => {
394+
await commitFileUpdateToBranch(
395+
testCommitterOctokit,
396+
testOrg,
397+
repoName,
398+
branchName,
399+
"appversions.json",
400+
'{\n "coreapp": "v0.1.0",\n "subapp": "v1.2.0",\n "logapp": "v100.1.0"\n}\n',
401+
);
402+
403+
const { data: pullRequest } = await testCommitterOctokit.pulls.create({
404+
owner: testOrg,
405+
repo: repoName,
406+
head: branchName,
407+
base: defaultBranchName,
408+
title:
409+
"[automated-staging-test] Passed required rule can pass automerge check",
410+
});
411+
prNum = pullRequest.number;
412+
});
413+
414+
await t.step("validate check passed from Fensak Staging", async () => {
415+
const checkRun = await waitForFensakStagingCheck(
416+
testCommitterOctokit,
417+
testOrg,
418+
repoName,
419+
branchName,
420+
previousCheckRuns,
421+
);
422+
previousCheckRuns.push(checkRun.id);
423+
expectCheckConclusion(checkRun, "success");
424+
});
425+
426+
await t.step("[cleanup] close PR", async () => {
427+
if (prNum) {
428+
await testCommitterOctokit.pulls.update({
429+
owner: testOrg,
430+
repo: repoName,
431+
pull_number: prNum,
432+
state: "closed",
433+
});
434+
}
435+
});
436+
437+
await t.step("[cleanup] delete branch", async () => {
438+
await deleteBranch(
439+
testCommitterOctokit,
440+
testOrg,
441+
repoName,
442+
branchName,
443+
);
444+
});
445+
});
446+
447+
Deno.test("passed required rule and failed automerge requires review", async (t) => {
448+
const repoName = "test-fensak-automated-appdeploy";
449+
const branchName = `feature/update-config-${getRandomString(6)}`;
450+
const defaultBranchName = "main";
451+
const previousCheckRuns: number[] = [];
452+
let prNum = 0;
453+
454+
await t.step("create branch", async () => {
455+
await createBranchFromDefault(
456+
testCommitterOctokit,
457+
testOrg,
458+
repoName,
459+
branchName,
460+
);
461+
});
462+
463+
await t.step("commit update to appversions.json and open PR", async () => {
464+
await commitFileUpdateToBranch(
465+
testCommitterOctokit,
466+
testOrg,
467+
repoName,
468+
branchName,
469+
"appversions.json",
470+
'{\n "coreapp": "v0.2.0",\n "subapp": "v1.1.0",\n "logapp": "v100.1.0"\n}',
471+
);
472+
473+
const { data: pullRequest } = await testCommitterOctokit.pulls.create({
474+
owner: testOrg,
475+
repo: repoName,
476+
head: branchName,
477+
base: defaultBranchName,
478+
title:
479+
"[automated-staging-test] Passed required rule but failed automerge check requires reviews",
480+
});
481+
prNum = pullRequest.number;
482+
});
483+
484+
await t.step("validate check failed from Fensak Staging", async () => {
485+
const checkRun = await waitForFensakStagingCheck(
486+
testCommitterOctokit,
487+
testOrg,
488+
repoName,
489+
branchName,
490+
previousCheckRuns,
491+
);
492+
previousCheckRuns.push(checkRun.id);
493+
expectCheckConclusion(checkRun, "action_required");
494+
});
495+
496+
await t.step(
497+
"approve with trusted user and validate check still passes from Fensak Staging",
498+
async () => {
499+
await approvePR(
500+
fensakOpsAdminOctokit,
501+
testOrg,
502+
repoName,
503+
prNum,
504+
);
505+
506+
const checkRun = await waitForFensakStagingCheck(
507+
testCommitterOctokit,
508+
testOrg,
509+
repoName,
510+
branchName,
511+
previousCheckRuns,
512+
);
513+
previousCheckRuns.push(checkRun.id);
514+
expectCheckConclusion(checkRun, "success");
262515
},
263516
);
264517

@@ -355,3 +608,14 @@ async function approvePR(
355608
event: "APPROVE",
356609
});
357610
}
611+
612+
function expectCheckConclusion(
613+
checkRun: GitHubCheckRun,
614+
expectedConclusion: string,
615+
): void {
616+
assertEquals(
617+
checkRun.conclusion,
618+
expectedConclusion,
619+
`Unexpected check conclusion ${checkRun.conclusion}:\n${checkRun.output.title}\n${checkRun.output.summary}\n${checkRun.output.text}`,
620+
);
621+
}

fskconfig/loader_github.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,13 @@ function validateRepoLimits(
345345
maybeLimit = planRepoLimits[""];
346346
}
347347

348-
const totalRepoCount = configRepoCount + ghorg.subscription.repoCount;
348+
let existingRepoCount = 0;
349+
for (const k in ghorg.subscription.repoCount) {
350+
if (k !== ghorg.name) {
351+
existingRepoCount += ghorg.subscription.repoCount[k];
352+
}
353+
}
354+
const totalRepoCount = configRepoCount + existingRepoCount;
349355
if (totalRepoCount > maybeLimit) {
350356
throw new FensakConfigLoaderUserError(
351357
`the config file for \`${ghorg.name}\` exceeds or causes the org to exceed the repo limit for the org (limit is ${maybeLimit})`,

fskconfig/loader_github_test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Deno.test("fetchAndParseConfigFromDotFensak for fensak-test example repo", async
2424
id: "sub_asdf",
2525
mainOrgName: "fensak-test",
2626
planName: "pro",
27-
repoCount: 0,
27+
repoCount: {},
2828
cancelledAt: 0,
2929
};
3030
const testOrg: GitHubOrgWithSubscription = {
@@ -96,7 +96,9 @@ Deno.test("fetchAndParseConfigFromDotFensak checks repo limits", async () => {
9696
id: "sub_asdf",
9797
mainOrgName: "fensak-test",
9898
planName: "pro",
99-
repoCount: 5,
99+
repoCount: {
100+
"yanosan": 5,
101+
},
100102
cancelledAt: 0,
101103
},
102104
};

ghevent/pullrequest.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ async function runReviewRoutine(
152152
let requiredRuleFn: CompiledRuleSource | undefined;
153153
if (repoCfg.requiredRuleFile) {
154154
requiredRuleFn = cfg.ruleLookup[repoCfg.requiredRuleFile];
155-
logger.warn(
156-
`[${requestID}] Compiled required rule function could not be found for repository ${repoName}.`,
157-
);
158-
return false;
155+
if (!requiredRuleFn) {
156+
logger.warn(
157+
`[${requestID}] Compiled required rule function could not be found for repository ${repoName}.`,
158+
);
159+
return false;
160+
}
159161
}
160162

161163
const authorType = await determineAuthorType(

mgmt/subscription_events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ async function handleSubscriptionCreatedEvent(
8686
id: data.id,
8787
mainOrgName: data.mainOrgName,
8888
planName: data.planName,
89-
repoCount: 0,
89+
repoCount: {},
9090
cancelledAt: 0,
9191
};
9292
const stored = await storeSubscription(newSub, maybeSub);

svcdata/models.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,15 @@ export interface Lock {
1717
* @property mainOrgName The main organization that manages the subscription. Owners of this Org can manage the
1818
* subscription.
1919
* @property planName The name of the subscription plan.
20-
* @property repoCount A convenient counter of the number of active repos on the subscription. This is a sum across all
21-
* associated orgs.
20+
* @property repoCount A convenient counter of the number of active repos for each Org in the subscription.
2221
* @property cancelledAt The timestamp (in milliseconds after epoch in UTC) when the subscription will be cancelled.
2322
* Used to record a future cancellation event for subscription management.
2423
*/
2524
export interface Subscription {
2625
id: string;
2726
mainOrgName: string;
2827
planName: string;
29-
repoCount: number;
28+
repoCount: Record<string, number>;
3029
cancelledAt: number;
3130
}
3231

0 commit comments

Comments
 (0)