@@ -10,6 +10,7 @@ import test, {
1010import nock from "nock" ;
1111import * as sinon from "sinon" ;
1212
13+ import { ActionState , StateFeature } from "./action-common" ;
1314import { ActionsEnv , ActionsEnvVars , getActionVersion } from "./actions-util" ;
1415import { AnalysisKind } from "./analyses" ;
1516import * as apiClient from "./api-client" ;
@@ -188,6 +189,99 @@ export function getTestActionsEnv(): ActionsEnv {
188189 } ;
189190}
190191
192+ /** For testing purposes, we make all available state features accessible in `TestEnv`. */
193+ type AllState = [ "Logger" , "Env" , "FeatureFlags" ] ;
194+
195+ /**
196+ * Wraps a function that accepts an `ActionState` for testing in different environments.
197+ */
198+ export class TestEnv <
199+ Args extends readonly any [ ] ,
200+ R ,
201+ Fs extends ReadonlyArray < AllState [ number ] > ,
202+ > {
203+ private readonly fn : ( state : ActionState < Fs > , ...args : Args ) => R ;
204+ private args ?: Args ;
205+ private logger : RecordingLogger ;
206+ private state : ActionState < AllState > ;
207+
208+ constructor (
209+ fn : ( state : ActionState < Fs > , ...args : Args ) => R ,
210+ cloneFrom ?: TestEnv < Args , R , Fs > ,
211+ ) {
212+ this . fn = fn ;
213+ this . args = cloneFrom ?. args ;
214+ this . logger = new RecordingLogger ( ) ;
215+ this . state =
216+ cloneFrom !== undefined
217+ ? { ...cloneFrom . state }
218+ : {
219+ logger : new RecordingLogger ( ) ,
220+ env : getTestEnv ( ) ,
221+ features : createFeatures ( [ ] ) ,
222+ } ;
223+ }
224+
225+ private clone ( ) : TestEnv < Args , R , Fs > {
226+ return new TestEnv ( this . fn , this ) ;
227+ }
228+
229+ public getLogger ( ) : RecordingLogger {
230+ return this . logger ;
231+ }
232+
233+ public getState ( ) : ActionState < AllState > {
234+ return this . state ;
235+ }
236+
237+ public getArgs ( ) : Args | undefined {
238+ return this . args ;
239+ }
240+
241+ public withArgs ( ...args : Args ) {
242+ const result = this . clone ( ) ;
243+ result . args = args ;
244+ return result ;
245+ }
246+
247+ public withFeatures ( enabled : Feature [ ] ) : TestEnv < Args , R , Fs > {
248+ const result = this . clone ( ) ;
249+ result . state . features = createFeatures ( enabled ) ;
250+ return result ;
251+ }
252+
253+ public withEnv ( env : Env ) : TestEnv < Args , R , Fs > {
254+ const result = this . clone ( ) ;
255+ result . state . env = env ;
256+ return result ;
257+ }
258+
259+ call ( ) : R {
260+ if ( ! this . args ) {
261+ throw new Error ( "Trying to call function in TestEnv without arguments." ) ;
262+ }
263+ return this . fn ( this . state as ActionState < Fs > , ...this . args ) ;
264+ }
265+
266+ public passes < T > (
267+ assertion : ( makeCall : ( ) => R ) => T | Promise < T > ,
268+ ) : T | Promise < T > {
269+ return assertion ( ( ) => {
270+ const result = this . call ( ) ;
271+ return result ;
272+ } ) ;
273+ }
274+ }
275+
276+ /** Utility function to construct a `TestEnv`. */
277+ export function callee <
278+ Args extends readonly any [ ] ,
279+ R ,
280+ Fs extends readonly StateFeature [ ] ,
281+ > ( fn : ( state : ActionState < Fs > , ...args : Args ) => R ) : TestEnv < Args , R , Fs > {
282+ return new TestEnv ( fn ) ;
283+ }
284+
191285/**
192286 * Default values for environment variables typically set in an Actions
193287 * environment. Tests can override individual variables by passing them in the
0 commit comments