Skip to content

Commit

Permalink
Merge pull request #123 from fensak-io/main
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
yorinasub17 authored Oct 18, 2023
2 parents c232709 + 927b885 commit 25fcf32
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 20 deletions.
272 changes: 268 additions & 4 deletions deployments/release/integration_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Deno.test("auto-approve happy path for README update", async (t) => {
repoName,
branchName,
);
assertEquals(checkRun.conclusion, "success");
expectCheckConclusion(checkRun, "success");
});

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

await t.step(
Expand All @@ -236,7 +236,7 @@ Deno.test("manual review required for config update", async (t) => {
previousCheckRuns,
);
previousCheckRuns.push(checkRun.id);
assertEquals(checkRun.conclusion, "action_required");
expectCheckConclusion(checkRun, "action_required");
},
);

Expand All @@ -258,7 +258,260 @@ Deno.test("manual review required for config update", async (t) => {
previousCheckRuns,
);
previousCheckRuns.push(checkRun.id);
assertEquals(checkRun.conclusion, "success");
expectCheckConclusion(checkRun, "success");
},
);

await t.step("[cleanup] close PR", async () => {
if (prNum) {
await testCommitterOctokit.pulls.update({
owner: testOrg,
repo: repoName,
pull_number: prNum,
state: "closed",
});
}
});

await t.step("[cleanup] delete branch", async () => {
await deleteBranch(
testCommitterOctokit,
testOrg,
repoName,
branchName,
);
});
});

Deno.test("failed required rule fails check", async (t) => {
const repoName = "test-fensak-automated-appdeploy";
const branchName = `test/update-config-${getRandomString(6)}`;
const defaultBranchName = "main";
const previousCheckRuns: number[] = [];
let prNum = 0;

await t.step("create branch", async () => {
await createBranchFromDefault(
testCommitterOctokit,
testOrg,
repoName,
branchName,
);
});

await t.step("commit update to appversions.json and open PR", async () => {
await commitFileUpdateToBranch(
testCommitterOctokit,
testOrg,
repoName,
branchName,
"appversions.json",
'{\n "coreapp": "v0.1.0",\n "subapp": "v1.2.0",\n "logapp": "v100.1.0"\n}',
);

const { data: pullRequest } = await testCommitterOctokit.pulls.create({
owner: testOrg,
repo: repoName,
head: branchName,
base: defaultBranchName,
title: "[automated-staging-test] Failed required review fails check",
});
prNum = pullRequest.number;
});

await t.step("validate check failed from Fensak Staging", async () => {
const checkRun = await waitForFensakStagingCheck(
testCommitterOctokit,
testOrg,
repoName,
branchName,
previousCheckRuns,
);
previousCheckRuns.push(checkRun.id);
expectCheckConclusion(checkRun, "action_required");
});

await t.step(
"approve with trusted user and validate check still fails from Fensak Staging",
async () => {
await approvePR(
fensakOpsAdminOctokit,
testOrg,
repoName,
prNum,
);

const checkRun = await waitForFensakStagingCheck(
testCommitterOctokit,
testOrg,
repoName,
branchName,
previousCheckRuns,
);
previousCheckRuns.push(checkRun.id);
expectCheckConclusion(checkRun, "action_required");
},
);

await t.step("[cleanup] close PR", async () => {
if (prNum) {
await testCommitterOctokit.pulls.update({
owner: testOrg,
repo: repoName,
pull_number: prNum,
state: "closed",
});
}
});

await t.step("[cleanup] delete branch", async () => {
await deleteBranch(
testCommitterOctokit,
testOrg,
repoName,
branchName,
);
});
});

Deno.test("passed required rule and passed automerge passes check", async (t) => {
const repoName = "test-fensak-automated-appdeploy";
const branchName = `feature/update-config-${getRandomString(6)}`;
const defaultBranchName = "main";
const previousCheckRuns: number[] = [];
let prNum = 0;

await t.step("create branch", async () => {
await createBranchFromDefault(
testCommitterOctokit,
testOrg,
repoName,
branchName,
);
});

await t.step("commit update to appversions.json and open PR", async () => {
await commitFileUpdateToBranch(
testCommitterOctokit,
testOrg,
repoName,
branchName,
"appversions.json",
'{\n "coreapp": "v0.1.0",\n "subapp": "v1.2.0",\n "logapp": "v100.1.0"\n}\n',
);

const { data: pullRequest } = await testCommitterOctokit.pulls.create({
owner: testOrg,
repo: repoName,
head: branchName,
base: defaultBranchName,
title:
"[automated-staging-test] Passed required rule can pass automerge check",
});
prNum = pullRequest.number;
});

await t.step("validate check passed from Fensak Staging", async () => {
const checkRun = await waitForFensakStagingCheck(
testCommitterOctokit,
testOrg,
repoName,
branchName,
previousCheckRuns,
);
previousCheckRuns.push(checkRun.id);
expectCheckConclusion(checkRun, "success");
});

await t.step("[cleanup] close PR", async () => {
if (prNum) {
await testCommitterOctokit.pulls.update({
owner: testOrg,
repo: repoName,
pull_number: prNum,
state: "closed",
});
}
});

await t.step("[cleanup] delete branch", async () => {
await deleteBranch(
testCommitterOctokit,
testOrg,
repoName,
branchName,
);
});
});

Deno.test("passed required rule and failed automerge requires review", async (t) => {
const repoName = "test-fensak-automated-appdeploy";
const branchName = `feature/update-config-${getRandomString(6)}`;
const defaultBranchName = "main";
const previousCheckRuns: number[] = [];
let prNum = 0;

await t.step("create branch", async () => {
await createBranchFromDefault(
testCommitterOctokit,
testOrg,
repoName,
branchName,
);
});

await t.step("commit update to appversions.json and open PR", async () => {
await commitFileUpdateToBranch(
testCommitterOctokit,
testOrg,
repoName,
branchName,
"appversions.json",
'{\n "coreapp": "v0.2.0",\n "subapp": "v1.1.0",\n "logapp": "v100.1.0"\n}',
);

const { data: pullRequest } = await testCommitterOctokit.pulls.create({
owner: testOrg,
repo: repoName,
head: branchName,
base: defaultBranchName,
title:
"[automated-staging-test] Passed required rule but failed automerge check requires reviews",
});
prNum = pullRequest.number;
});

await t.step("validate check failed from Fensak Staging", async () => {
const checkRun = await waitForFensakStagingCheck(
testCommitterOctokit,
testOrg,
repoName,
branchName,
previousCheckRuns,
);
previousCheckRuns.push(checkRun.id);
expectCheckConclusion(checkRun, "action_required");
});

await t.step(
"approve with trusted user and validate check still passes from Fensak Staging",
async () => {
await approvePR(
fensakOpsAdminOctokit,
testOrg,
repoName,
prNum,
);

const checkRun = await waitForFensakStagingCheck(
testCommitterOctokit,
testOrg,
repoName,
branchName,
previousCheckRuns,
);
previousCheckRuns.push(checkRun.id);
expectCheckConclusion(checkRun, "success");
},
);

Expand Down Expand Up @@ -355,3 +608,14 @@ async function approvePR(
event: "APPROVE",
});
}

function expectCheckConclusion(
checkRun: GitHubCheckRun,
expectedConclusion: string,
): void {
assertEquals(
checkRun.conclusion,
expectedConclusion,
`Unexpected check conclusion ${checkRun.conclusion}:\n${checkRun.output.title}\n${checkRun.output.summary}\n${checkRun.output.text}`,
);
}
8 changes: 7 additions & 1 deletion fskconfig/loader_github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,13 @@ function validateRepoLimits(
maybeLimit = planRepoLimits[""];
}

const totalRepoCount = configRepoCount + ghorg.subscription.repoCount;
let existingRepoCount = 0;
for (const k in ghorg.subscription.repoCount) {
if (k !== ghorg.name) {
existingRepoCount += ghorg.subscription.repoCount[k];
}
}
const totalRepoCount = configRepoCount + existingRepoCount;
if (totalRepoCount > maybeLimit) {
throw new FensakConfigLoaderUserError(
`the config file for \`${ghorg.name}\` exceeds or causes the org to exceed the repo limit for the org (limit is ${maybeLimit})`,
Expand Down
6 changes: 4 additions & 2 deletions fskconfig/loader_github_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Deno.test("fetchAndParseConfigFromDotFensak for fensak-test example repo", async
id: "sub_asdf",
mainOrgName: "fensak-test",
planName: "pro",
repoCount: 0,
repoCount: {},
cancelledAt: 0,
};
const testOrg: GitHubOrgWithSubscription = {
Expand Down Expand Up @@ -96,7 +96,9 @@ Deno.test("fetchAndParseConfigFromDotFensak checks repo limits", async () => {
id: "sub_asdf",
mainOrgName: "fensak-test",
planName: "pro",
repoCount: 5,
repoCount: {
"yanosan": 5,
},
cancelledAt: 0,
},
};
Expand Down
10 changes: 6 additions & 4 deletions ghevent/pullrequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,12 @@ async function runReviewRoutine(
let requiredRuleFn: CompiledRuleSource | undefined;
if (repoCfg.requiredRuleFile) {
requiredRuleFn = cfg.ruleLookup[repoCfg.requiredRuleFile];
logger.warn(
`[${requestID}] Compiled required rule function could not be found for repository ${repoName}.`,
);
return false;
if (!requiredRuleFn) {
logger.warn(
`[${requestID}] Compiled required rule function could not be found for repository ${repoName}.`,
);
return false;
}
}

const authorType = await determineAuthorType(
Expand Down
2 changes: 1 addition & 1 deletion mgmt/subscription_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async function handleSubscriptionCreatedEvent(
id: data.id,
mainOrgName: data.mainOrgName,
planName: data.planName,
repoCount: 0,
repoCount: {},
cancelledAt: 0,
};
const stored = await storeSubscription(newSub, maybeSub);
Expand Down
5 changes: 2 additions & 3 deletions svcdata/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@ export interface Lock {
* @property mainOrgName The main organization that manages the subscription. Owners of this Org can manage the
* subscription.
* @property planName The name of the subscription plan.
* @property repoCount A convenient counter of the number of active repos on the subscription. This is a sum across all
* associated orgs.
* @property repoCount A convenient counter of the number of active repos for each Org in the subscription.
* @property cancelledAt The timestamp (in milliseconds after epoch in UTC) when the subscription will be cancelled.
* Used to record a future cancellation event for subscription management.
*/
export interface Subscription {
id: string;
mainOrgName: string;
planName: string;
repoCount: number;
repoCount: Record<string, number>;
cancelledAt: number;
}

Expand Down
Loading

0 comments on commit 25fcf32

Please sign in to comment.