Clean-up passes by splitting it into passes and throws

This commit is contained in:
Michael B. Gale
2026-07-09 17:16:06 +01:00
parent 31842a7e06
commit b571644f21
3 changed files with 62 additions and 48 deletions

View File

@@ -13,7 +13,7 @@ test("getConfigFileInput returns undefined by default", async (t) => {
await callee(getConfigFileInput)
.withArgs({})
.withFeatures([Feature.ConfigFileRepositoryProperty])
.passes(async (fn) => t.is(await fn(), undefined));
.passes(t.is, undefined);
});
const repositoryProperties = {
@@ -37,7 +37,7 @@ test("getConfigFileInput returns input value", async (t) => {
const targetWithArgs = target
.withActions(actionsEnv)
.withArgs(repositoryProperties);
await targetWithArgs.passes(async (fn) => t.is(await fn(), testInput));
await targetWithArgs.passes(t.is, testInput);
// Check for the expected log message.
t.true(
@@ -53,8 +53,9 @@ test("getConfigFileInput returns repository property value", async (t) => {
.withFeatures([Feature.ConfigFileRepositoryProperty])
.withArgs(repositoryProperties);
await target.passes(async (fn) =>
t.is(await fn(), repositoryProperties[RepositoryPropertyName.CONFIG_FILE]),
await target.passes(
t.is,
repositoryProperties[RepositoryPropertyName.CONFIG_FILE],
);
// Check for the expected log message.
@@ -70,7 +71,7 @@ test("getConfigFileInput ignores empty repository property value", async (t) =>
await callee(getConfigFileInput)
.withFeatures([Feature.ConfigFileRepositoryProperty])
.withArgs({ [RepositoryPropertyName.CONFIG_FILE]: " " })
.passes(async (fn) => t.is(await fn(), undefined));
.passes(t.is, undefined);
});
test("getConfigFileInput ignores repository property value when FF is off", async (t) => {
@@ -79,7 +80,7 @@ test("getConfigFileInput ignores repository property value when FF is off", asyn
.withFeatures([])
.withArgs(repositoryProperties);
await target.passes(async (fn) => t.is(await fn(), undefined));
await target.passes(t.is, undefined);
t.false(
target

View File

@@ -50,7 +50,7 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => {
for (const oldFormatInput of oldFormatInputs) {
await target
.withArgs(oldFormatInput.input)
.passes(async (fn) => t.deepEqual(await fn(), oldFormatInput.expected));
.passes(t.deepEqual, oldFormatInput.expected);
}
// New format.
@@ -78,14 +78,12 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => {
// Should fail when the FF is not enabled.
await targetWithArgs
.withFeatures([])
.passes(async (fn) =>
t.throwsAsync(fn, { instanceOf: ConfigurationError }),
);
.throws(t, { instanceOf: ConfigurationError });
// And pass when the FF is enabled.
await targetWithArgs
.withFeatures([Feature.NewRemoteFileAddresses])
.passes(async (fn) => t.deepEqual(await fn(), newFormatInput.expected));
.passes(t.deepEqual, newFormatInput.expected);
}
});
@@ -146,14 +144,12 @@ test("parseRemoteFileAddress accepts remote address without an owner", async (t)
// Should fail when the FF is not enabled.
await targetWithArgs
.withFeatures([])
.passes(async (fn) =>
t.throwsAsync(fn, { instanceOf: ConfigurationError }),
);
.throws(t, { instanceOf: ConfigurationError });
// And pass when the FF is enabled.
await targetWithArgs
.withFeatures([Feature.NewRemoteFileAddresses])
.passes(async (fn) => t.deepEqual(await fn(), testCase.expected));
.passes(t.deepEqual, testCase.expected);
}
});
@@ -167,7 +163,7 @@ test("parseRemoteFileAddress throws for invalid `GITHUB_REPOSITORY`", async (t)
await target
.withEnv(env)
.withFeatures([Feature.NewRemoteFileAddresses])
.passes(async (fn) => t.throwsAsync(fn, { instanceOf: Error }));
.throws(t, { instanceOf: Error });
t.assert(getRequired.calledOnceWith(ActionsEnvVars.GITHUB_REPOSITORY));
});
@@ -202,14 +198,12 @@ test("parseRemoteFileAddress accepts remote address without a path", async (t) =
// Should fail when the FF is not enabled.
await targetWithArgs
.withFeatures([])
.passes(async (fn) =>
t.throwsAsync(fn, { instanceOf: ConfigurationError }),
);
.throws(t, { instanceOf: ConfigurationError });
// And pass when the FF is enabled.
await targetWithArgs
.withFeatures([Feature.NewRemoteFileAddresses])
.passes(async (fn) => t.deepEqual(await fn(), testCase.expected));
.passes(t.deepEqual, testCase.expected);
}
});
@@ -217,17 +211,15 @@ test("parseRemoteFileAddress accepts remote address without a ref", async (t) =>
const target = callee(parseRemoteFileAddress).withArgs("owner/repo:path");
// Should only accept the input if the FF is enabled.
await target.withFeatures([]).passes(t.throwsAsync);
await target.withFeatures([]).throws(t);
await target
.withFeatures([Feature.NewRemoteFileAddresses])
.passes(async (fn) =>
t.deepEqual(await fn(), {
owner: "owner",
repo: "repo",
path: "path",
ref: DEFAULT_CONFIG_FILE_REF,
} satisfies RemoteFileAddress),
);
.passes(t.deepEqual, {
owner: "owner",
repo: "repo",
path: "path",
ref: DEFAULT_CONFIG_FILE_REF,
} satisfies RemoteFileAddress);
});
test("parseRemoteFileAddress rejects invalid values", async (t) => {
@@ -262,21 +254,17 @@ test("parseRemoteFileAddress rejects invalid values", async (t) => {
const targetWithArgs = target.withArgs(testInput);
// Should throw both when the new format is and isn't accepted.
await targetWithArgs.withFeatures([]).passes(async (fn) =>
t.throwsAsync(fn, {
instanceOf: ConfigurationError,
message: errors.getConfigFileRepoOldFormatInvalidMessage(testInput),
}),
);
await targetWithArgs.withFeatures([]).throws(t, {
instanceOf: ConfigurationError,
message: errors.getConfigFileRepoOldFormatInvalidMessage(testInput),
});
await targetWithArgs
.withFeatures([Feature.NewRemoteFileAddresses])
.passes(async (fn) =>
t.throwsAsync(fn, {
// When the new format is accepted, there are some more specific
// errors in some cases. It is sufficient for us to check that
// an exception is thrown.
instanceOf: ConfigurationError,
}),
);
.throws(t, {
// When the new format is accepted, there are some more specific
// errors in some cases. It is sufficient for us to check that
// an exception is thrown.
instanceOf: ConfigurationError,
});
}
});

View File

@@ -3,6 +3,8 @@ import path from "path";
import * as github from "@actions/github";
import test, {
type ThrownError,
type ThrowsExpectation,
type ExecutionContext,
type MacroDeclarationOptions,
type TestFn,
@@ -281,11 +283,34 @@ export class TestEnv<
return this.fn(this.state as unknown as ActionState<Fs>, ...this.args);
}
public passes<T>(assertion: (makeCall: () => R) => Promise<T>): Promise<T> {
return assertion(() => {
const result = this.call();
return result;
});
/**
* Calls the underlying function in the configured environment and passes
* the result to `assertion` along with extra `assertionArgs`.
*
* @param assertion The assertion to apply to the result.
* @param assertionArgs Extra arguments for the assertion.
* @returns The result of the assertion.
*/
public async passes<AArgs extends readonly any[], AResult>(
assertion: (val: Awaited<R>, ...assertionArgs: AArgs) => AResult,
...assertionArgs: AArgs
): Promise<AResult> {
const result = await Promise.resolve(this.call());
return assertion(result, ...assertionArgs);
}
/**
* Asserts that calling the underlying function should throw an exception.
*
* @param t The execution context for the assertion.
* @param expectations Expectations for the error.
* @returns The error that was thrown.
*/
public async throws<ErrorType extends ErrorConstructor | Error>(
t: ExecutionContext<unknown>,
expectations?: ThrowsExpectation<ErrorType>,
): Promise<ThrownError<ErrorType>> {
return t.throwsAsync(() => Promise.resolve(this.call()), expectations);
}
}