Skip to content

Commit

Permalink
chore: added testing
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianKuesters committed Mar 24, 2024
1 parent 5637da0 commit 44a5d17
Show file tree
Hide file tree
Showing 7 changed files with 372 additions and 14 deletions.
4 changes: 3 additions & 1 deletion env/env.template.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"GITHUB_TOKEN": "PAT_GOES_HERE"
"GITHUB_TOKEN": "PAT_GOES_HERE",
"OWNER": "OWNER_GOES_HERE",
"REPO": "REPO_WILL_BE_SET_DURING_SETUP"
}
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ module.exports = {
preset: "ts-jest",
testEnvironment: "node",
setupFiles: ["./tests/setup.ts"],
globalTeardown: "./tests/teardown.ts",
};
44 changes: 44 additions & 0 deletions tests/.test-repo-template/.github/workflows/release-dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Tag Release

on:
workflow_dispatch:
push:
branches:
- main

concurrency:
group: developemnt
cancel-in-progress: false

jobs:
tag:
name: Tag & Create Release
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.PAT }}

- uses: wemogy/[email protected]
id: get-release-version

- name: Create GitHub Release
uses: wemogy/[email protected]
if: steps.get-release-version.outputs.has-changes == 'true' && steps.get-release-version.outputs.previous-version-name != 'v0.0.0'
with:
github-token: ${{ secrets.PAT }}
release-version-tag: ${{ steps.get-release-version.outputs.version-name }}
previous-version-tag: ${{ steps.get-release-version.outputs.previous-version-name }}
release-title: Version ${{ steps.get-release-version.outputs.version }}

- name: Create GitHub Release
uses: wemogy/[email protected]
if: steps.get-release-version.outputs.has-changes == 'true' && steps.get-release-version.outputs.previous-version-name == 'v0.0.0'
with:
github-token: ${{ secrets.PAT }}
release-version-tag: ${{ steps.get-release-version.outputs.version-name }}
previous-version-tag: ""
release-title: Version ${{ steps.get-release-version.outputs.version }}
173 changes: 173 additions & 0 deletions tests/TestingUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { Octokit } from "@octokit/rest";
import * as fs from "fs";
import path from "path";
import { SimpleGit } from "simple-git";
import { SodiumPlus, X25519PublicKey } from "sodium-plus";

export default class TestingUtils {
public constructor(
private readonly owner: string,
private readonly repo: string,
private readonly repoDir: string,
private readonly git: SimpleGit,
private readonly octokit: Octokit
) {}

public async implementIssue(issueNumber: number, type: "feat" | "fix") {
// Destructuring
const { owner, repo, repoDir, git, octokit } = this;

// Create a new branch
await git.checkout("main");
await git.checkoutLocalBranch(`${type}/${issueNumber}`);

// Create a file for the feature
const filename = path.join(repoDir, `feature-${type}-${issueNumber}.txt`);
fs.writeFileSync(filename, `This is the ${type} for issue #${issueNumber}`);

// Commit the file
await git.add(filename);
await git.commit(`feat: implement feature for issue #${issueNumber}`);

// Publish the branch
await git.push("origin", `${type}/${issueNumber}`);

// Create a pull request
const pullRequest = await octokit.pulls.create({
owner,
repo,
title: `Closes #${issueNumber}`,
head: `${type}/${issueNumber}`,
base: "main",
});

// Merge the pull request
await this.mergePullRequest(pullRequest.data.number);
}

public async createRelease() {
// Destructuring
const { owner, repo, git, octokit } = this;

// check if branch called release exists in remote
const branches = await git.branch(["-r"]);
const releaseBranch = branches.all.find((b) => b === "origin/release");
if (!releaseBranch) {
await git.checkout("main");
await git.checkoutLocalBranch("release");
await git.push("origin", "release");
}

// Create a pull request from main to release
const pullRequest = await octokit.pulls.create({
owner,
repo,
title: "Release",
head: "main",
base: "release",
});

// Merge the pull request
await this.mergePullRequest(pullRequest.data.number);
}

public async addRepositorySecret(secretName: string, secretValue: string) {
// Destructuring
const { owner, repo, octokit } = this;

// Step 1: Get the public key of the repository
const {
data: { key, key_id },
} = await octokit.rest.actions.getRepoPublicKey({
owner,
repo,
});

// Step 2: Encrypt the secret using GitHub's public key
const sodium = await SodiumPlus.auto();
const publicKey = X25519PublicKey.from(Buffer.from(key, "base64") as any);

const encryptedValue = await sodium.crypto_box_seal(secretValue, publicKey);

// Step 3: Store the encrypted secret
await octokit.rest.actions.createOrUpdateRepoSecret({
owner,
repo,
secret_name: secretName,
encrypted_value: Buffer.from(encryptedValue).toString("base64"),
key_id: key_id,
});
}

public async waitForAllActionsToComplete() {
// Destructuring
const { owner, repo, octokit } = this;

// Get all workflows
const { data: workflows } = await octokit.actions.listRepoWorkflows({
owner,
repo,
});

// wait 10 seconds for all workflows to start
await new Promise((resolve) => setTimeout(resolve, 10000));

// For each workflow, fetch all workflow runs and wait for completion
for (const workflow of workflows.workflows) {
// Get the workflow id
const workflowId = workflow.id;

// Fetch all workflow runs and wait for completion
await this.fetchAllWorkflowRunsAndWaitForCompletion(workflowId);
}
}

private async mergePullRequest(pullRequestNumber: number, retries = 0) {
// Destructuring
const { owner, repo, octokit } = this;

// Merge the pull request
try {
await octokit.pulls.merge({
owner,
repo,
pull_number: pullRequestNumber,
});
} catch (e) {
if (retries >= 2) {
throw e;
}
// wait for 5 seconds and try again
await new Promise((resolve) => setTimeout(resolve, 5000));
await this.mergePullRequest(pullRequestNumber, retries + 1);
}
}

private async fetchAllWorkflowRunsAndWaitForCompletion(
workflowId: number
): Promise<void> {
// Destructuring
const { owner, repo, octokit } = this;

// Fetch all workflow runs
const { data: workflowRuns } = await octokit.actions.listWorkflowRuns({
owner,
repo,
workflow_id: workflowId,
});

// number of workflow runs in progress
const inProgressRuns = workflowRuns.workflow_runs.filter(
(run) => run.status === "in_progress"
).length;

if (inProgressRuns === 0) {
return;
}

// Wait for all runs to complete
await new Promise((resolve) => setTimeout(resolve, 10000));

await this.fetchAllWorkflowRunsAndWaitForCompletion(workflowId);
}
}
113 changes: 112 additions & 1 deletion tests/setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,114 @@
import { Octokit } from "@octokit/rest";
import * as fs from "fs";
import path from "path";
import simpleGit, { SimpleGit } from "simple-git";
import TestingUtils from "./TestingUtils";

function copyRepoTemplateToRepository(repoDir: string) {
// Copy all files from test-repo-template directory to the new repository
const templateDir = path.join(__dirname, ".test-repo-template");
const files = fs.readdirSync(templateDir, { recursive: true });
for (const file of files) {
if (typeof file !== "string") {
continue;
}

// continue if file is a directory
if (fs.statSync(path.join(templateDir, file)).isDirectory()) {
continue;
}

// ensure that the directory exists
const dir = path.dirname(file);
if (!fs.existsSync(path.join(repoDir, dir))) {
fs.mkdirSync(path.join(repoDir, dir), { recursive: true });
}

const source = path.join(templateDir, file);
const target = path.join(repoDir, file);
fs.copyFileSync(source, target);
}
}

const gitPath = path.join(__dirname, "../..");

module.exports = async () => {
console.log("Setup tests");
// generate new unique repo name and store it in env.json
const fs = require("fs");
const path = require("path");
const envPath = path.resolve(__dirname, "../env/env.json");
const env = require(envPath);
env.REPO = `test-repo-${Date.now()}`;
fs.writeFileSync(envPath, JSON.stringify(env, null, 2));

// Create services
const owner = env.OWNER;
const repo = env.REPO;
const repoDir = path.join(gitPath, repo);
const octokit = new Octokit({
auth: env.GITHUB_TOKEN,
});
const git: SimpleGit = simpleGit({});
const utils = new TestingUtils(owner, repo, repoDir, git, octokit);

// Create a new repository
const repository = await octokit.repos.createForAuthenticatedUser({
name: repo,
delete_branch_on_merge: true,
});
console.log("Created repository", repository.data.html_url);

// Add the PAT secret to the repository
await utils.addRepositorySecret("PAT", env.GITHUB_TOKEN);

// Clone the repository to repoDir
await git.clone(repository.data.clone_url, repoDir);

// Set the working directory to the repository and checkout main branch
await git.cwd(repoDir);
await git.checkoutLocalBranch("main");

// Create README.md and push to the repository (initial commit)
const readme = path.join(repoDir, "README.md");
fs.writeFileSync(readme, `# ${repo}`);
await git.add(readme);
await git.commit("Initial commit");
await git.push("origin", "main");

// Copy .test-repo-template to the repository
copyRepoTemplateToRepository(repoDir);

// Commit and push the files
await git.add(".");
await git.commit("Template files added");
await git.push("origin", "main");

await utils.waitForAllActionsToComplete();

// Feature A
const issueFeatureA = await octokit.issues.create({
owner,
repo,
title: "Feature A",
body: "This is the first feature",
labels: ["enhancement"],
});
await utils.implementIssue(issueFeatureA.data.number, "feat");

await utils.waitForAllActionsToComplete();

// Feature B
const issueFeatureB = await octokit.issues.create({
owner,
repo,
title: "Feature B",
body: "This is the second feature",
labels: ["enhancement"],
});
await utils.implementIssue(issueFeatureB.data.number, "feat");

await utils.waitForAllActionsToComplete();
await utils.waitForAllActionsToComplete();

console.log("Setup completed");
};
26 changes: 26 additions & 0 deletions tests/teardown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import path from "path";
import * as env from "../env/env.json";
import * as fs from "fs";
import { Octokit } from "@octokit/rest";

const gitPath = path.join(__dirname, "../..");

module.exports = async () => {
const owner = env.OWNER;
const repo = env.REPO;
const octokit = new Octokit({
auth: env.GITHUB_TOKEN,
});

await octokit.repos.delete({
owner: owner,
repo: repo,
});

// delete local repository, if it exists
if (fs.existsSync(path.join(gitPath, repo))) {
fs.rmSync(path.join(gitPath, repo), { recursive: true });
}

console.log(`Deleted repository ${repo}`);
};
Loading

0 comments on commit 44a5d17

Please sign in to comment.