@@ -18,6 +18,55 @@ const (
1818 GitHubAppTokenAction = "actions/create-github-app-token"
1919)
2020
21+ // MockStepWithHTTPSpy creates a mocked step that POSTs the original step's inputs to an HTTPSpy server
22+ // and sets the response as step outputs.
23+ //
24+ // This is a generic function for mocking any action that uses HTTPSpy for recording inputs.
25+ // The inputs from originalStep.With are passed as environment variables so that GitHub Actions
26+ // expressions (e.g., ${{ needs.ci.outputs.something }}) are evaluated at runtime before being sent.
27+ //
28+ // The mock server should return a JSON object where each key-value pair becomes a step output.
29+ //
30+ // Parameters:
31+ // - originalStep: The original step being mocked (used to extract inputs from With)
32+ // - mockServerURL: The URL of the HTTPSpy server (use HTTPSpy.DockerAccessibleURL())
33+ func MockStepWithHTTPSpy (originalStep Step , mockServerURL string ) (Step , error ) {
34+ // Pass each input as an environment variable so GitHub Actions expressions are evaluated at runtime.
35+ // We use a prefix to namespace them and collect the keys to build JSON in bash.
36+ const envPrefix = "HTTPSPY_INPUT_"
37+ env := map [string ]string {
38+ "HTTPSPY_MOCK_SERVER_URL" : mockServerURL ,
39+ }
40+ var inputKeys []string
41+ for key , value := range originalStep .With {
42+ env [envPrefix + key ] = fmt .Sprintf ("%v" , value )
43+ inputKeys = append (inputKeys , key )
44+ }
45+
46+ // Serialize input keys to JSON array so bash knows which env vars to read
47+ inputKeysJSON , err := json .Marshal (inputKeys )
48+ if err != nil {
49+ return Step {}, fmt .Errorf ("marshal input keys to json: %w" , err )
50+ }
51+ env ["HTTPSPY_INPUT_KEYS" ] = string (inputKeysJSON )
52+
53+ // Build JSON from environment variables at runtime, then POST to mock server
54+ return Step {
55+ Run : Commands {
56+ `echo "Mocking step with HTTPSpy"` ,
57+ // Build JSON object from env vars using jq
58+ // For each key in HTTPSPY_INPUT_KEYS, read the corresponding HTTPSPY_INPUT_* env var
59+ `INPUTS_JSON=$(echo "${HTTPSPY_INPUT_KEYS}" | jq -c 'reduce .[] as $key ({}; . + {($key): env["HTTPSPY_INPUT_" + $key]})')` ,
60+ // POST the inputs to the mock server and capture the JSON response
61+ `MOCK_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "${INPUTS_JSON}" "${HTTPSPY_MOCK_SERVER_URL}")` ,
62+ // Parse the JSON response and set each key as an output
63+ `echo "${MOCK_RESPONSE}" | jq -r 'to_entries[] | "\(.key)=\(.value)"' >> "$GITHUB_OUTPUT"` ,
64+ }.String (),
65+ Shell : "bash" ,
66+ Env : env ,
67+ }, nil
68+ }
69+
2170// MockOutputsStep returns a Step that only sets the given outputs and does nothing else.
2271// This can be used to mock the outputs of a step for testing purposes, without executing its real implementation.
2372func MockOutputsStep (outputs map [string ]string ) Step {
@@ -27,6 +76,11 @@ func MockOutputsStep(outputs map[string]string) Step {
2776 stepCommands = append (stepCommands , fmt .Sprintf (`echo "%s=${%s}" >> "$GITHUB_OUTPUT"` , k , k ))
2877 env [k ] = v
2978 }
79+ // If we have no outputs, we must have something in "runs" otherwise the empty string
80+ // will break the yaml file (missing "run" key).
81+ if len (outputs ) == 0 {
82+ stepCommands = append (stepCommands , `echo "no outputs to set"` )
83+ }
3084 return Step {
3185 Run : stepCommands .String (),
3286 Env : env ,
@@ -308,25 +362,23 @@ func MockVaultSecretsStep(originalStep Step, secrets VaultSecrets) (Step, error)
308362}
309363
310364// MockArgoWorkflowStep returns a Step that mocks the grafana/shared-workflows/actions/trigger-argo-workflow action.
311- // Instead of actually triggering an Argo Workflow, it outputs a mock URI for the workflow.
365+ // Instead of actually triggering an Argo Workflow, it POSTs the original step's inputs to the mock server
366+ // and outputs the response (typically containing the workflow URI).
367+ //
312368// The originalStep parameter is the original Argo Workflow trigger step to be mocked.
313369// The original step must use the `grafana/shared-workflows/actions/trigger-argo-workflow` action.
314370// If those conditions are not met, an error is returned.
315371//
316- // The mocked step outputs the `uri` output expected by subsequent steps.
317- func MockArgoWorkflowStep (originalStep Step ) (Step , error ) {
318- // Make sure the original step is indeed an Argo Workflow trigger step
372+ // The mockServerURL parameter is the URL of the HTTPSpy server that will receive the inputs.
373+ // Use HTTPSpy.DockerAccessibleURL() to get a URL that works from inside act's Docker containers.
374+ //
375+ // The mocked step parses the JSON response from the mock server and sets each key as an output.
376+ // The inputs are evaluated at runtime (GitHub Actions expressions are resolved) before being sent.
377+ func MockArgoWorkflowStep (originalStep Step , mockServerURL string ) (Step , error ) {
319378 if ! strings .HasPrefix (originalStep .Uses , ArgoWorkflowAction ) {
320379 return Step {}, fmt .Errorf ("cannot mock argo workflow for a step that uses %q action, must be %q" , originalStep .Uses , ArgoWorkflowAction )
321380 }
322-
323- return Step {
324- Run : Commands {
325- `echo "Mocking Argo Workflow trigger step"` ,
326- `echo "uri=https://mock-argo-workflows.example.com/workflows/grafana-plugins-cd/mock-workflow-id" >> "$GITHUB_OUTPUT"` ,
327- }.String (),
328- Shell : "bash" ,
329- }, nil
381+ return MockStepWithHTTPSpy (originalStep , mockServerURL )
330382}
331383
332384// MockGitHubAppTokenStep returns a Step that mocks the actions/create-github-app-token action.
0 commit comments