Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve testing #9

Merged
merged 16 commits into from
Dec 22, 2023
6 changes: 4 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@
"rules": {
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"etc/throw-error": "off"
"etc/throw-error": "off",
"import/no-namespace": "off"
}
},
{
"files": "*.typetest.ts?(x)",
"rules": {
"@typescript-eslint/ban-ts-comment": ["error", { "ts-expect-error": false }],
"etc/throw-error": "off"
"etc/throw-error": "off",
"import/no-namespace": "off"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [17, 18, 19, 20]
node: [18, 19, 20]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
Expand Down
2 changes: 1 addition & 1 deletion .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"exit": true,
"extension": ["ts"],
"recursive": true,
"require": ["ts-node/register"],
"require": ["ts-node/register", "test/hooks.ts"],
"slow": 1000,
"spec": ["test/**/*.test.*"],
"timeout": 5000
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
"axios": "^1.6.0"
},
"devDependencies": {
"@stackbuilders/assertive-ts": "^1.4.0",
"@assertive-ts/core": "^2.0.0",
"@types/mocha": "^10.0.1",
"@types/semantic-release": "^20.0.1",
"@types/sinon": "^17.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"axios-mock-adapter": "^1.21.5",
Expand All @@ -44,6 +45,7 @@
"eslint-plugin-sonarjs": "^0.19.0",
"mocha": "^10.2.0",
"semantic-release": "^21.0.7",
"sinon": "^17.0.1",
"ts-node": "^10.9.1",
"typescript": "^5.1.3"
}
Expand Down
5 changes: 3 additions & 2 deletions src/prepare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { getCabalFilename } from "./utils/prepare";
import { readFile, writeFile } from "fs/promises";
import { resolve } from "path";

export const VERSION_PATTERN = /version:\s+(\S+)/;

export const readAndWriteNewCabal = async (fullCabalPath: string, newVersion: string): Promise<void> => {
const pattern = /^version:\s+\S+/m;
const versionContents = await readFile(fullCabalPath, "utf8");
const newContents = versionContents.replace(pattern, `version: ${newVersion}`);
const newContents = versionContents.replace(VERSION_PATTERN, `version: ${newVersion}`);
await writeFile(fullCabalPath, newContents, "utf8");
};

Expand Down
10 changes: 5 additions & 5 deletions src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { Context } from "semantic-release";
import { PluginConfig } from "./types/pluginConfig";
import { runExecCommand } from "./utils/exec";

const HACKAGE_PACKAGES_URL = "https://hackage.haskell.org/package";
const CANDIDATES = "candidates/";
export const HACKAGE_PACKAGES_URL = "https://hackage.haskell.org/package";
export const CANDIDATES_PATH = "candidates/";

const postReleaseCandidate = async (
export const postReleaseCandidate = async (
sdistPath: string,
packageName: string,
hackageToken?: string,
): Promise<number | undefined> => {
const url = `${HACKAGE_PACKAGES_URL}/${packageName}/${CANDIDATES}`;
const url = `${HACKAGE_PACKAGES_URL}/${packageName}/${CANDIDATES_PATH}`;
try {
const headers = {
Accept: "text/plain",
Expand All @@ -26,7 +26,7 @@ const postReleaseCandidate = async (

return req.status;
} catch (e: unknown) {
throw new Error(`You do not have access to POST a file to ${url}, ${String(e)}`);
throw e instanceof Error ? new Error(`You do not have access to POST a file to ${url}, ${e.message}`) : e;
}
};

Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/test-1-package.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: test-1-package
version: 0.0.1
47 changes: 47 additions & 0 deletions test/helpers/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Context } from "semantic-release";
import Sinon from "sinon";

export const semanticContext: Context = {
branch: {
channel: "",
name: "main",
prerelease: false,
range: "",
},
commits: [],
env: {},
lastRelease: undefined,
logger: {
await: Sinon.fake(),
complete: Sinon.fake(),
debug: Sinon.fake(),
error: Sinon.fake(),
fatal: Sinon.fake(),
fav: Sinon.fake(),
info: Sinon.fake(),
log: Sinon.fake(),
note: Sinon.fake(),
pause: Sinon.fake(),
pending: Sinon.fake(),
star: Sinon.fake(),
start: Sinon.fake(),
success: Sinon.fake(),
wait: Sinon.fake(),
warn: Sinon.fake(),
watch: Sinon.fake(),
},
nextRelease: {
channel: "",
gitHead: "h1",
gitTag: "v1.0.0",
name: "v1.0.0",
notes: "",
type: "minor",
version: "1.0.0",
},
};

export const contextWithoutRelease: Context = {
...semanticContext,
nextRelease: undefined,
};
9 changes: 9 additions & 0 deletions test/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Sinon from "sinon";

export function mochaHooks(): Mocha.RootHookObject {
return {
afterEach() {
Sinon.restore();
},
};
}
39 changes: 39 additions & 0 deletions test/integration/prepare.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect } from "@assertive-ts/core";
import Sinon from "sinon";

import { prepare } from "../../src/prepare";
import { PluginConfig } from "../../src/types/pluginConfig";
import * as exec from "../../src/utils/exec";
import { semanticContext, contextWithoutRelease } from "../helpers/context";

const pluginConfig: PluginConfig = {
cabalFile: "test/fixtures/test-1-package.cabal",
packageName: "test-1-package",
};

const pluginConfigWithoutCabal: PluginConfig = {
packageName: "test-1-package",
};

describe("prepare", () => {
context("when release does not exists", () => {
it("rejects the promise because of the release version", async () => {
await expect(prepare(pluginConfig, contextWithoutRelease)).toBeRejected();
});
});

context("when cabal file name does not exists", () => {
it("rejects the promise because of the cabal file", async () => {
await expect(prepare(pluginConfigWithoutCabal, semanticContext)).toBeRejected();
});
});

context("when prepare has version and cabal name", () => {
it("execs the cabal sdist command and resolves prepare fn", async () => {
const runExecCommandStub = Sinon.stub();
runExecCommandStub.withArgs("cabal sdist").resolves({ error: null, output: "Mocked output" });
Sinon.replace(exec, "runExecCommand", runExecCommandStub);
await expect(prepare(pluginConfig, semanticContext)).toBeResolved();
});
});
});
27 changes: 27 additions & 0 deletions test/integration/verifyConditions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { expect } from "@assertive-ts/core";

import { PluginConfig } from "../../src/types/pluginConfig";
import { EnvVarError } from "../../src/utils/EnvVarError";
import { verifyConditions } from "../../src/verifyConditions";
import { semanticContext } from "../helpers/context";

const pluginConfig: PluginConfig = {
cabalFile: "test-1-package.cabal",
packageName: "test-1-package",
};

describe("verifyConditions", () => {
it("throws EnvVarError when HACKAGE_TOKEN is not defined", () => {
delete process.env.HACKAGE_TOKEN;

expect(() => verifyConditions(pluginConfig, semanticContext))
.toThrowError(EnvVarError)
.toHaveMessage("Environment variable not found: HACKAGE_TOKEN. Check the README.md for config info.");
});

it("does not throw EnvVarError when HACKAGE_TOKEN is defined", () => {
process.env.HACKAGE_TOKEN = "test_token";

expect(() => verifyConditions(pluginConfig, semanticContext)).not.toThrow();
});
});
2 changes: 1 addition & 1 deletion test/unit/EnvVarError.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from "@stackbuilders/assertive-ts";
import { expect } from "@assertive-ts/core";

import { EnvVarError } from "../../src/utils/EnvVarError";

Expand Down
23 changes: 23 additions & 0 deletions test/unit/prepare.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { expect } from "@assertive-ts/core";

import { readAndWriteNewCabal } from "../../src/prepare";

import { readFile, writeFile } from "fs/promises";

const fakeCabalPath = "./test/fixtures/test-1-package.cabal";
const fakeNewVersion = "0.0.7";
const cabalContent = "name: test-1-package\nversion: 0.0.1";

describe("readAndWriteNewCabal", () => {
afterEach(async () => {
await writeFile(fakeCabalPath, cabalContent, "utf8");
});

it("updates the version in the cabal file fixture", async () => {
await readAndWriteNewCabal(fakeCabalPath, fakeNewVersion);

const modifiedContents = await readFile(fakeCabalPath, "utf8");

expect(modifiedContents).toBeEqual("name: test-1-package\nversion: 0.0.7");
});
});
31 changes: 31 additions & 0 deletions test/unit/publish.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect } from "@assertive-ts/core";
import axios from "axios";
import sinon from "sinon";

import { CANDIDATES_PATH, HACKAGE_PACKAGES_URL, postReleaseCandidate } from "../../src/publish";

const sdistPath = "sdist/path";
const packageName = "my-hackage-package";
const hackageToken = "my-fake-token";

describe("postReleaseCandidate", () => {
it("returns the status code when request is successful", async () => {
const axiosPostStub = sinon.stub(axios, "post").resolves({ status: 200 });

const statusCode = await postReleaseCandidate(sdistPath, packageName, hackageToken);

expect(statusCode).toBeEqual(200);
expect(axiosPostStub.calledOnce).toBeTruthy();
expect(axiosPostStub.firstCall.args[0]).toBeEqual(`${HACKAGE_PACKAGES_URL}/${packageName}/${CANDIDATES_PATH}`);
});

it("throws an error on unsuccessful request", async () => {
const errorMsg = "Error message from server";
const axiosPostStub = sinon.stub(axios, "post").rejects({ message: errorMsg });

const request = postReleaseCandidate(sdistPath, packageName, hackageToken);

await expect(request).toBeRejected();
expect(axiosPostStub.calledOnce).toBeTruthy();
});
});
Loading