From c77051d879862fb9b5ce83f35a979035ec59dea1 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 9 Jul 2026 19:30:29 +0100 Subject: [PATCH] Allow `withActions` and `withEnv` to mutate values --- src/config/file.test.ts | 19 +++++++-------- src/config/remote-file.test.ts | 44 ++++++++++++++++------------------ src/testing-utils.ts | 18 ++++++++++---- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/config/file.test.ts b/src/config/file.test.ts index 6632385e4..4b7b88c9c 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -22,20 +22,17 @@ const repositoryProperties = { test("getConfigFileInput returns input value", async (t) => { const testInput = "/some/path"; - const target = callee(getConfigFileInput).withFeatures([ - Feature.ConfigFileRepositoryProperty, - ]); - - const actionsEnv = target.getState().actions; - sinon - .stub(actionsEnv, "getOptionalInput") - .withArgs("config-file") - .returns(testInput); // Even though both an input and repository property are configured, // we prefer the direct input to the Action. - await target - .withActions(actionsEnv) + await callee(getConfigFileInput) + .withFeatures([Feature.ConfigFileRepositoryProperty]) + .withActions((actionsEnv) => { + sinon + .stub(actionsEnv, "getOptionalInput") + .withArgs("config-file") + .returns(testInput); + }) .withArgs(repositoryProperties) .logs(t, "Using configuration file input from workflow") .passes(t.is, testInput); diff --git a/src/config/remote-file.test.ts b/src/config/remote-file.test.ts index df55a8e75..fd1fcf3c1 100644 --- a/src/config/remote-file.test.ts +++ b/src/config/remote-file.test.ts @@ -4,7 +4,7 @@ import sinon from "sinon"; import { ActionsEnvVars } from "../environment"; import * as errors from "../error-messages"; import { Feature } from "../feature-flags"; -import { callee, getTestEnv } from "../testing-utils"; +import { callee } from "../testing-utils"; import { ConfigurationError } from "../util"; import { @@ -88,16 +88,13 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => { }); test("parseRemoteFileAddress accepts remote address without an owner", async (t) => { - const target = callee(parseRemoteFileAddress); - - const env = target.getState().env; const owner = "test-owner"; - const getRequired = sinon.stub(env, "getRequired"); - getRequired - .withArgs(ActionsEnvVars.GITHUB_REPOSITORY) - .returns(`${owner}/current-repo`); - - const targetWithEnv = target.withEnv(env); + const target = callee(parseRemoteFileAddress).withEnv((env) => { + const getRequired = sinon.stub(env, "getRequired"); + getRequired + .withArgs(ActionsEnvVars.GITHUB_REPOSITORY) + .returns(`${owner}/current-repo`); + }); const testCases: ParseRemoteFileAddressTest[] = [ { @@ -139,7 +136,7 @@ test("parseRemoteFileAddress accepts remote address without an owner", async (t) ]; for (const testCase of testCases) { - const targetWithArgs = targetWithEnv.withArgs(testCase.input); + const targetWithArgs = target.withArgs(testCase.input); // Should fail when the FF is not enabled. await targetWithArgs @@ -154,14 +151,16 @@ test("parseRemoteFileAddress accepts remote address without an owner", async (t) }); test("parseRemoteFileAddress throws for invalid `GITHUB_REPOSITORY`", async (t) => { - const target = callee(parseRemoteFileAddress).withArgs("repo@ref"); - - const env = target.getState().env; - const getRequired = sinon.stub(env, "getRequired"); + const getRequired: sinon.SinonStub = sinon.stub(); getRequired.withArgs(ActionsEnvVars.GITHUB_REPOSITORY).returns(`not-valid`); + const target = callee(parseRemoteFileAddress) + .withArgs("repo@ref") + .withEnv((env) => { + sinon.define(env, "getRequired", getRequired); + }); + await target - .withEnv(env) .withFeatures([Feature.NewRemoteFileAddresses]) .throws(t, { instanceOf: Error }); @@ -223,14 +222,13 @@ test("parseRemoteFileAddress accepts remote address without a ref", async (t) => }); test("parseRemoteFileAddress rejects invalid values", async (t) => { - const env = getTestEnv(); const owner = "owner"; - const getRequired = sinon.stub(env, "getRequired"); - getRequired - .withArgs(ActionsEnvVars.GITHUB_REPOSITORY) - .returns(`${owner}/current-repo`); - - const target = callee(parseRemoteFileAddress).withEnv(env); + const target = callee(parseRemoteFileAddress).withEnv((env) => { + const getRequired = sinon.stub(env, "getRequired"); + getRequired + .withArgs(ActionsEnvVars.GITHUB_REPOSITORY) + .returns(`${owner}/current-repo`); + }); const testInputs = [ " ", diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 653914d53..20cbadeab 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -216,6 +216,8 @@ type DelayedCheck< Fs extends ReadonlyArray, > = (env: Readonly>) => Promise; +export type ValueOrMutation = T | ((val: T) => void); + /** * Wraps a function that accepts an `ActionState` for testing in different environments. */ @@ -267,15 +269,23 @@ abstract class BaseEnvBuilder< return result; } - public withEnv(env: Env): this { + public withEnv(arg: ValueOrMutation): this { const result = this.clone(); - result.state.env = env; + if (typeof arg === "function") { + arg(result.state.env); + } else { + result.state.env = arg; + } return result; } - public withActions(actions: ActionsEnv): this { + public withActions(arg: ValueOrMutation): this { const result = this.clone(); - result.state.actions = actions; + if (typeof arg === "function") { + arg(result.state.actions); + } else { + result.state.actions = arg; + } return result; }