github.com/nektos/act@v0.2.63/pkg/runner/run_context_test.go (about)

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"regexp"
     8  	"runtime"
     9  	"sort"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/golang-jwt/jwt/v5"
    14  	"github.com/nektos/act/pkg/exprparser"
    15  	"github.com/nektos/act/pkg/model"
    16  
    17  	log "github.com/sirupsen/logrus"
    18  	assert "github.com/stretchr/testify/assert"
    19  	yaml "gopkg.in/yaml.v3"
    20  )
    21  
    22  func TestRunContext_EvalBool(t *testing.T) {
    23  	var yml yaml.Node
    24  	err := yml.Encode(map[string][]interface{}{
    25  		"os":  {"Linux", "Windows"},
    26  		"foo": {"bar", "baz"},
    27  	})
    28  	assert.NoError(t, err)
    29  
    30  	rc := &RunContext{
    31  		Config: &Config{
    32  			Workdir: ".",
    33  		},
    34  		Env: map[string]string{
    35  			"SOMETHING_TRUE":  "true",
    36  			"SOMETHING_FALSE": "false",
    37  			"SOME_TEXT":       "text",
    38  		},
    39  		Run: &model.Run{
    40  			JobID: "job1",
    41  			Workflow: &model.Workflow{
    42  				Name: "test-workflow",
    43  				Jobs: map[string]*model.Job{
    44  					"job1": {
    45  						Strategy: &model.Strategy{
    46  							RawMatrix: yml,
    47  						},
    48  					},
    49  				},
    50  			},
    51  		},
    52  		Matrix: map[string]interface{}{
    53  			"os":  "Linux",
    54  			"foo": "bar",
    55  		},
    56  		StepResults: map[string]*model.StepResult{
    57  			"id1": {
    58  				Conclusion: model.StepStatusSuccess,
    59  				Outcome:    model.StepStatusFailure,
    60  				Outputs: map[string]string{
    61  					"foo": "bar",
    62  				},
    63  			},
    64  		},
    65  	}
    66  	rc.ExprEval = rc.NewExpressionEvaluator(context.Background())
    67  
    68  	tables := []struct {
    69  		in      string
    70  		out     bool
    71  		wantErr bool
    72  	}{
    73  		// The basic ones
    74  		{in: "failure()", out: false},
    75  		{in: "success()", out: true},
    76  		{in: "cancelled()", out: false},
    77  		{in: "always()", out: true},
    78  		// TODO: move to sc.NewExpressionEvaluator(), because "steps" context is not available here
    79  		// {in: "steps.id1.conclusion == 'success'", out: true},
    80  		// {in: "steps.id1.conclusion != 'success'", out: false},
    81  		// {in: "steps.id1.outcome == 'failure'", out: true},
    82  		// {in: "steps.id1.outcome != 'failure'", out: false},
    83  		{in: "true", out: true},
    84  		{in: "false", out: false},
    85  		// TODO: This does not throw an error, because the evaluator does not know if the expression is inside ${{ }} or not
    86  		// {in: "!true", wantErr: true},
    87  		// {in: "!false", wantErr: true},
    88  		{in: "1 != 0", out: true},
    89  		{in: "1 != 1", out: false},
    90  		{in: "${{ 1 != 0 }}", out: true},
    91  		{in: "${{ 1 != 1 }}", out: false},
    92  		{in: "1 == 0", out: false},
    93  		{in: "1 == 1", out: true},
    94  		{in: "1 > 2", out: false},
    95  		{in: "1 < 2", out: true},
    96  		// And or
    97  		{in: "true && false", out: false},
    98  		{in: "true && 1 < 2", out: true},
    99  		{in: "false || 1 < 2", out: true},
   100  		{in: "false || false", out: false},
   101  		// None boolable
   102  		{in: "env.UNKNOWN == 'true'", out: false},
   103  		{in: "env.UNKNOWN", out: false},
   104  		// Inline expressions
   105  		{in: "env.SOME_TEXT", out: true},
   106  		{in: "env.SOME_TEXT == 'text'", out: true},
   107  		{in: "env.SOMETHING_TRUE == 'true'", out: true},
   108  		{in: "env.SOMETHING_FALSE == 'true'", out: false},
   109  		{in: "env.SOMETHING_TRUE", out: true},
   110  		{in: "env.SOMETHING_FALSE", out: true},
   111  		// TODO: This does not throw an error, because the evaluator does not know if the expression is inside ${{ }} or not
   112  		// {in: "!env.SOMETHING_TRUE", wantErr: true},
   113  		// {in: "!env.SOMETHING_FALSE", wantErr: true},
   114  		{in: "${{ !env.SOMETHING_TRUE }}", out: false},
   115  		{in: "${{ !env.SOMETHING_FALSE }}", out: false},
   116  		{in: "${{ ! env.SOMETHING_TRUE }}", out: false},
   117  		{in: "${{ ! env.SOMETHING_FALSE }}", out: false},
   118  		{in: "${{ env.SOMETHING_TRUE }}", out: true},
   119  		{in: "${{ env.SOMETHING_FALSE }}", out: true},
   120  		{in: "${{ !env.SOMETHING_TRUE }}", out: false},
   121  		{in: "${{ !env.SOMETHING_FALSE }}", out: false},
   122  		{in: "${{ !env.SOMETHING_TRUE && true }}", out: false},
   123  		{in: "${{ !env.SOMETHING_FALSE && true }}", out: false},
   124  		{in: "${{ !env.SOMETHING_TRUE || true }}", out: true},
   125  		{in: "${{ !env.SOMETHING_FALSE || false }}", out: false},
   126  		{in: "${{ env.SOMETHING_TRUE && true }}", out: true},
   127  		{in: "${{ env.SOMETHING_FALSE || true }}", out: true},
   128  		{in: "${{ env.SOMETHING_FALSE || false }}", out: true},
   129  		// TODO: This does not throw an error, because the evaluator does not know if the expression is inside ${{ }} or not
   130  		// {in: "!env.SOMETHING_TRUE || true", wantErr: true},
   131  		{in: "${{ env.SOMETHING_TRUE == 'true'}}", out: true},
   132  		{in: "${{ env.SOMETHING_FALSE == 'true'}}", out: false},
   133  		{in: "${{ env.SOMETHING_FALSE == 'false'}}", out: true},
   134  		{in: "${{ env.SOMETHING_FALSE }} && ${{ env.SOMETHING_TRUE }}", out: true},
   135  
   136  		// All together now
   137  		{in: "false || env.SOMETHING_TRUE == 'true'", out: true},
   138  		{in: "true || env.SOMETHING_FALSE == 'true'", out: true},
   139  		{in: "true && env.SOMETHING_TRUE == 'true'", out: true},
   140  		{in: "false && env.SOMETHING_TRUE == 'true'", out: false},
   141  		{in: "env.SOMETHING_FALSE == 'true' && env.SOMETHING_TRUE == 'true'", out: false},
   142  		{in: "env.SOMETHING_FALSE == 'true' && true", out: false},
   143  		{in: "${{ env.SOMETHING_FALSE == 'true' }} && true", out: true},
   144  		{in: "true && ${{ env.SOMETHING_FALSE == 'true' }}", out: true},
   145  		// Check github context
   146  		{in: "github.actor == 'nektos/act'", out: true},
   147  		{in: "github.actor == 'unknown'", out: false},
   148  		{in: "github.job == 'job1'", out: true},
   149  		// The special ACT flag
   150  		{in: "${{ env.ACT }}", out: true},
   151  		{in: "${{ !env.ACT }}", out: false},
   152  		// Invalid expressions should be reported
   153  		{in: "INVALID_EXPRESSION", wantErr: true},
   154  	}
   155  
   156  	updateTestIfWorkflow(t, tables, rc)
   157  	for _, table := range tables {
   158  		table := table
   159  		t.Run(table.in, func(t *testing.T) {
   160  			assertObject := assert.New(t)
   161  			b, err := EvalBool(context.Background(), rc.ExprEval, table.in, exprparser.DefaultStatusCheckSuccess)
   162  			if table.wantErr {
   163  				assertObject.Error(err)
   164  			}
   165  
   166  			assertObject.Equal(table.out, b, fmt.Sprintf("Expected %s to be %v, was %v", table.in, table.out, b))
   167  		})
   168  	}
   169  }
   170  
   171  func updateTestIfWorkflow(t *testing.T, tables []struct {
   172  	in      string
   173  	out     bool
   174  	wantErr bool
   175  }, rc *RunContext) {
   176  	var envs string
   177  	keys := make([]string, 0, len(rc.Env))
   178  	for k := range rc.Env {
   179  		keys = append(keys, k)
   180  	}
   181  	sort.Strings(keys)
   182  	for _, k := range keys {
   183  		envs += fmt.Sprintf("  %s: %s\n", k, rc.Env[k])
   184  	}
   185  	// editorconfig-checker-disable
   186  	workflow := fmt.Sprintf(`
   187  name: "Test what expressions result in true and false on GitHub"
   188  on: push
   189  
   190  env:
   191  %s
   192  
   193  jobs:
   194    test-ifs-and-buts:
   195      runs-on: ubuntu-latest
   196      steps:
   197  `, envs)
   198  	// editorconfig-checker-enable
   199  
   200  	for i, table := range tables {
   201  		if table.wantErr || strings.HasPrefix(table.in, "github.actor") {
   202  			continue
   203  		}
   204  		expressionPattern := regexp.MustCompile(`\${{\s*(.+?)\s*}}`)
   205  
   206  		expr := expressionPattern.ReplaceAllStringFunc(table.in, func(match string) string {
   207  			return fmt.Sprintf("€{{ %s }}", expressionPattern.ReplaceAllString(match, "$1"))
   208  		})
   209  		echo := fmt.Sprintf(`run: echo "%s should be false, but was evaluated to true;" exit 1;`, table.in)
   210  		name := fmt.Sprintf(`"❌ I should not run, expr: %s"`, expr)
   211  		if table.out {
   212  			echo = `run: echo OK`
   213  			name = fmt.Sprintf(`"✅ I should run, expr: %s"`, expr)
   214  		}
   215  		workflow += fmt.Sprintf("\n      - name: %s\n        id: step%d\n        if: %s\n        %s\n", name, i, table.in, echo)
   216  		if table.out {
   217  			workflow += fmt.Sprintf("\n      - name: \"Double checking expr: %s\"\n        if: steps.step%d.conclusion == 'skipped'\n        run: echo \"%s should have been true, but wasn't\"\n", expr, i, table.in)
   218  		}
   219  	}
   220  
   221  	file, err := os.Create("../../.github/workflows/test-if.yml")
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  
   226  	_, err = file.WriteString(workflow)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  }
   231  
   232  func TestRunContext_GetBindsAndMounts(t *testing.T) {
   233  	rctemplate := &RunContext{
   234  		Name: "TestRCName",
   235  		Run: &model.Run{
   236  			Workflow: &model.Workflow{
   237  				Name: "TestWorkflowName",
   238  			},
   239  		},
   240  		Config: &Config{
   241  			BindWorkdir: false,
   242  		},
   243  	}
   244  
   245  	tests := []struct {
   246  		windowsPath bool
   247  		name        string
   248  		rc          *RunContext
   249  		wantbind    string
   250  		wantmount   string
   251  	}{
   252  		{false, "/mnt/linux", rctemplate, "/mnt/linux", "/mnt/linux"},
   253  		{false, "/mnt/path with spaces/linux", rctemplate, "/mnt/path with spaces/linux", "/mnt/path with spaces/linux"},
   254  		{true, "C:\\Users\\TestPath\\MyTestPath", rctemplate, "/mnt/c/Users/TestPath/MyTestPath", "/mnt/c/Users/TestPath/MyTestPath"},
   255  		{true, "C:\\Users\\Test Path with Spaces\\MyTestPath", rctemplate, "/mnt/c/Users/Test Path with Spaces/MyTestPath", "/mnt/c/Users/Test Path with Spaces/MyTestPath"},
   256  		{true, "/LinuxPathOnWindowsShouldFail", rctemplate, "", ""},
   257  	}
   258  
   259  	isWindows := runtime.GOOS == "windows"
   260  
   261  	for _, testcase := range tests {
   262  		// pin for scopelint
   263  		testcase := testcase
   264  		for _, bindWorkDir := range []bool{true, false} {
   265  			// pin for scopelint
   266  			bindWorkDir := bindWorkDir
   267  			testBindSuffix := ""
   268  			if bindWorkDir {
   269  				testBindSuffix = "Bind"
   270  			}
   271  
   272  			// Only run windows path tests on windows and non-windows on non-windows
   273  			if (isWindows && testcase.windowsPath) || (!isWindows && !testcase.windowsPath) {
   274  				t.Run((testcase.name + testBindSuffix), func(t *testing.T) {
   275  					config := testcase.rc.Config
   276  					config.Workdir = testcase.name
   277  					config.BindWorkdir = bindWorkDir
   278  					gotbind, gotmount := rctemplate.GetBindsAndMounts()
   279  
   280  					// Name binds/mounts are either/or
   281  					if config.BindWorkdir {
   282  						fullBind := testcase.name + ":" + testcase.wantbind
   283  						if runtime.GOOS == "darwin" {
   284  							fullBind += ":delegated"
   285  						}
   286  						assert.Contains(t, gotbind, fullBind)
   287  					} else {
   288  						mountkey := testcase.rc.jobContainerName()
   289  						assert.EqualValues(t, testcase.wantmount, gotmount[mountkey])
   290  					}
   291  				})
   292  			}
   293  		}
   294  	}
   295  
   296  	t.Run("ContainerVolumeMountTest", func(t *testing.T) {
   297  		tests := []struct {
   298  			name      string
   299  			volumes   []string
   300  			wantbind  string
   301  			wantmount map[string]string
   302  		}{
   303  			{"BindAnonymousVolume", []string{"/volume"}, "/volume", map[string]string{}},
   304  			{"BindHostFile", []string{"/path/to/file/on/host:/volume"}, "/path/to/file/on/host:/volume", map[string]string{}},
   305  			{"MountExistingVolume", []string{"volume-id:/volume"}, "", map[string]string{"volume-id": "/volume"}},
   306  		}
   307  
   308  		for _, testcase := range tests {
   309  			t.Run(testcase.name, func(t *testing.T) {
   310  				job := &model.Job{}
   311  				err := job.RawContainer.Encode(map[string][]string{
   312  					"volumes": testcase.volumes,
   313  				})
   314  				assert.NoError(t, err)
   315  
   316  				rc := &RunContext{
   317  					Name: "TestRCName",
   318  					Run: &model.Run{
   319  						Workflow: &model.Workflow{
   320  							Name: "TestWorkflowName",
   321  						},
   322  					},
   323  					Config: &Config{
   324  						BindWorkdir: false,
   325  					},
   326  				}
   327  				rc.Run.JobID = "job1"
   328  				rc.Run.Workflow.Jobs = map[string]*model.Job{"job1": job}
   329  
   330  				gotbind, gotmount := rc.GetBindsAndMounts()
   331  
   332  				if len(testcase.wantbind) > 0 {
   333  					assert.Contains(t, gotbind, testcase.wantbind)
   334  				}
   335  
   336  				for k, v := range testcase.wantmount {
   337  					assert.Contains(t, gotmount, k)
   338  					assert.Equal(t, gotmount[k], v)
   339  				}
   340  			})
   341  		}
   342  	})
   343  }
   344  
   345  func TestGetGitHubContext(t *testing.T) {
   346  	log.SetLevel(log.DebugLevel)
   347  
   348  	cwd, err := os.Getwd()
   349  	assert.Nil(t, err)
   350  
   351  	rc := &RunContext{
   352  		Config: &Config{
   353  			EventName: "push",
   354  			Workdir:   cwd,
   355  		},
   356  		Run: &model.Run{
   357  			Workflow: &model.Workflow{
   358  				Name: "GitHubContextTest",
   359  			},
   360  		},
   361  		Name:           "GitHubContextTest",
   362  		CurrentStep:    "step",
   363  		Matrix:         map[string]interface{}{},
   364  		Env:            map[string]string{},
   365  		ExtraPath:      []string{},
   366  		StepResults:    map[string]*model.StepResult{},
   367  		OutputMappings: map[MappableOutput]MappableOutput{},
   368  	}
   369  	rc.Run.JobID = "job1"
   370  
   371  	ghc := rc.getGithubContext(context.Background())
   372  
   373  	log.Debugf("%v", ghc)
   374  
   375  	actor := "nektos/act"
   376  	if a := os.Getenv("ACT_ACTOR"); a != "" {
   377  		actor = a
   378  	}
   379  
   380  	repo := "nektos/act"
   381  	if r := os.Getenv("ACT_REPOSITORY"); r != "" {
   382  		repo = r
   383  	}
   384  
   385  	owner := "nektos"
   386  	if o := os.Getenv("ACT_OWNER"); o != "" {
   387  		owner = o
   388  	}
   389  
   390  	assert.Equal(t, ghc.RunID, "1")
   391  	assert.Equal(t, ghc.RunNumber, "1")
   392  	assert.Equal(t, ghc.RetentionDays, "0")
   393  	assert.Equal(t, ghc.Actor, actor)
   394  	assert.Equal(t, ghc.Repository, repo)
   395  	assert.Equal(t, ghc.RepositoryOwner, owner)
   396  	assert.Equal(t, ghc.RunnerPerflog, "/dev/null")
   397  	assert.Equal(t, ghc.Token, rc.Config.Secrets["GITHUB_TOKEN"])
   398  	assert.Equal(t, ghc.Job, "job1")
   399  }
   400  
   401  func TestGetGithubContextRef(t *testing.T) {
   402  	table := []struct {
   403  		event string
   404  		json  string
   405  		ref   string
   406  	}{
   407  		{event: "push", json: `{"ref":"0000000000000000000000000000000000000000"}`, ref: "0000000000000000000000000000000000000000"},
   408  		{event: "create", json: `{"ref":"0000000000000000000000000000000000000000"}`, ref: "0000000000000000000000000000000000000000"},
   409  		{event: "workflow_dispatch", json: `{"ref":"0000000000000000000000000000000000000000"}`, ref: "0000000000000000000000000000000000000000"},
   410  		{event: "delete", json: `{"repository":{"default_branch": "main"}}`, ref: "refs/heads/main"},
   411  		{event: "pull_request", json: `{"number":123}`, ref: "refs/pull/123/merge"},
   412  		{event: "pull_request_review", json: `{"number":123}`, ref: "refs/pull/123/merge"},
   413  		{event: "pull_request_review_comment", json: `{"number":123}`, ref: "refs/pull/123/merge"},
   414  		{event: "pull_request_target", json: `{"pull_request":{"base":{"ref": "main"}}}`, ref: "refs/heads/main"},
   415  		{event: "deployment", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"},
   416  		{event: "deployment_status", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"},
   417  		{event: "release", json: `{"release": {"tag_name": "tag-name"}}`, ref: "refs/tags/tag-name"},
   418  	}
   419  
   420  	for _, data := range table {
   421  		data := data
   422  		t.Run(data.event, func(t *testing.T) {
   423  			rc := &RunContext{
   424  				EventJSON: data.json,
   425  				Config: &Config{
   426  					EventName: data.event,
   427  					Workdir:   "",
   428  				},
   429  				Run: &model.Run{
   430  					Workflow: &model.Workflow{
   431  						Name: "GitHubContextTest",
   432  					},
   433  				},
   434  			}
   435  
   436  			ghc := rc.getGithubContext(context.Background())
   437  
   438  			assert.Equal(t, data.ref, ghc.Ref)
   439  		})
   440  	}
   441  }
   442  
   443  func createIfTestRunContext(jobs map[string]*model.Job) *RunContext {
   444  	rc := &RunContext{
   445  		Config: &Config{
   446  			Workdir: ".",
   447  			Platforms: map[string]string{
   448  				"ubuntu-latest": "ubuntu-latest",
   449  			},
   450  		},
   451  		Env: map[string]string{},
   452  		Run: &model.Run{
   453  			JobID: "job1",
   454  			Workflow: &model.Workflow{
   455  				Name: "test-workflow",
   456  				Jobs: jobs,
   457  			},
   458  		},
   459  	}
   460  	rc.ExprEval = rc.NewExpressionEvaluator(context.Background())
   461  
   462  	return rc
   463  }
   464  
   465  func createJob(t *testing.T, input string, result string) *model.Job {
   466  	var job *model.Job
   467  	err := yaml.Unmarshal([]byte(input), &job)
   468  	assert.NoError(t, err)
   469  	job.Result = result
   470  
   471  	return job
   472  }
   473  
   474  func TestRunContextRunsOnPlatformNames(t *testing.T) {
   475  	log.SetLevel(log.DebugLevel)
   476  	assertObject := assert.New(t)
   477  
   478  	rc := createIfTestRunContext(map[string]*model.Job{
   479  		"job1": createJob(t, `runs-on: ubuntu-latest`, ""),
   480  	})
   481  	assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
   482  
   483  	rc = createIfTestRunContext(map[string]*model.Job{
   484  		"job1": createJob(t, `runs-on: ${{ 'ubuntu-latest' }}`, ""),
   485  	})
   486  	assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
   487  
   488  	rc = createIfTestRunContext(map[string]*model.Job{
   489  		"job1": createJob(t, `runs-on: [self-hosted, my-runner]`, ""),
   490  	})
   491  	assertObject.Equal([]string{"self-hosted", "my-runner"}, rc.runsOnPlatformNames(context.Background()))
   492  
   493  	rc = createIfTestRunContext(map[string]*model.Job{
   494  		"job1": createJob(t, `runs-on: [self-hosted, "${{ 'my-runner' }}"]`, ""),
   495  	})
   496  	assertObject.Equal([]string{"self-hosted", "my-runner"}, rc.runsOnPlatformNames(context.Background()))
   497  
   498  	rc = createIfTestRunContext(map[string]*model.Job{
   499  		"job1": createJob(t, `runs-on: ${{ fromJSON('["ubuntu-latest"]') }}`, ""),
   500  	})
   501  	assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
   502  
   503  	// test missing / invalid runs-on
   504  	rc = createIfTestRunContext(map[string]*model.Job{
   505  		"job1": createJob(t, `name: something`, ""),
   506  	})
   507  	assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
   508  
   509  	rc = createIfTestRunContext(map[string]*model.Job{
   510  		"job1": createJob(t, `runs-on:
   511    mapping: value`, ""),
   512  	})
   513  	assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
   514  
   515  	rc = createIfTestRunContext(map[string]*model.Job{
   516  		"job1": createJob(t, `runs-on: ${{ invalid expression }}`, ""),
   517  	})
   518  	assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
   519  }
   520  
   521  func TestRunContextIsEnabled(t *testing.T) {
   522  	log.SetLevel(log.DebugLevel)
   523  	assertObject := assert.New(t)
   524  
   525  	// success()
   526  	rc := createIfTestRunContext(map[string]*model.Job{
   527  		"job1": createJob(t, `runs-on: ubuntu-latest
   528  if: success()`, ""),
   529  	})
   530  	assertObject.True(rc.isEnabled(context.Background()))
   531  
   532  	rc = createIfTestRunContext(map[string]*model.Job{
   533  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   534  		"job2": createJob(t, `runs-on: ubuntu-latest
   535  needs: [job1]
   536  if: success()`, ""),
   537  	})
   538  	rc.Run.JobID = "job2"
   539  	assertObject.False(rc.isEnabled(context.Background()))
   540  
   541  	rc = createIfTestRunContext(map[string]*model.Job{
   542  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   543  		"job2": createJob(t, `runs-on: ubuntu-latest
   544  needs: [job1]
   545  if: success()`, ""),
   546  	})
   547  	rc.Run.JobID = "job2"
   548  	assertObject.True(rc.isEnabled(context.Background()))
   549  
   550  	rc = createIfTestRunContext(map[string]*model.Job{
   551  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   552  		"job2": createJob(t, `runs-on: ubuntu-latest
   553  if: success()`, ""),
   554  	})
   555  	rc.Run.JobID = "job2"
   556  	assertObject.True(rc.isEnabled(context.Background()))
   557  
   558  	// failure()
   559  	rc = createIfTestRunContext(map[string]*model.Job{
   560  		"job1": createJob(t, `runs-on: ubuntu-latest
   561  if: failure()`, ""),
   562  	})
   563  	assertObject.False(rc.isEnabled(context.Background()))
   564  
   565  	rc = createIfTestRunContext(map[string]*model.Job{
   566  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   567  		"job2": createJob(t, `runs-on: ubuntu-latest
   568  needs: [job1]
   569  if: failure()`, ""),
   570  	})
   571  	rc.Run.JobID = "job2"
   572  	assertObject.True(rc.isEnabled(context.Background()))
   573  
   574  	rc = createIfTestRunContext(map[string]*model.Job{
   575  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   576  		"job2": createJob(t, `runs-on: ubuntu-latest
   577  needs: [job1]
   578  if: failure()`, ""),
   579  	})
   580  	rc.Run.JobID = "job2"
   581  	assertObject.False(rc.isEnabled(context.Background()))
   582  
   583  	rc = createIfTestRunContext(map[string]*model.Job{
   584  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   585  		"job2": createJob(t, `runs-on: ubuntu-latest
   586  if: failure()`, ""),
   587  	})
   588  	rc.Run.JobID = "job2"
   589  	assertObject.False(rc.isEnabled(context.Background()))
   590  
   591  	// always()
   592  	rc = createIfTestRunContext(map[string]*model.Job{
   593  		"job1": createJob(t, `runs-on: ubuntu-latest
   594  if: always()`, ""),
   595  	})
   596  	assertObject.True(rc.isEnabled(context.Background()))
   597  
   598  	rc = createIfTestRunContext(map[string]*model.Job{
   599  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   600  		"job2": createJob(t, `runs-on: ubuntu-latest
   601  needs: [job1]
   602  if: always()`, ""),
   603  	})
   604  	rc.Run.JobID = "job2"
   605  	assertObject.True(rc.isEnabled(context.Background()))
   606  
   607  	rc = createIfTestRunContext(map[string]*model.Job{
   608  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   609  		"job2": createJob(t, `runs-on: ubuntu-latest
   610  needs: [job1]
   611  if: always()`, ""),
   612  	})
   613  	rc.Run.JobID = "job2"
   614  	assertObject.True(rc.isEnabled(context.Background()))
   615  
   616  	rc = createIfTestRunContext(map[string]*model.Job{
   617  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   618  		"job2": createJob(t, `runs-on: ubuntu-latest
   619  if: always()`, ""),
   620  	})
   621  	rc.Run.JobID = "job2"
   622  	assertObject.True(rc.isEnabled(context.Background()))
   623  
   624  	rc = createIfTestRunContext(map[string]*model.Job{
   625  		"job1": createJob(t, `uses: ./.github/workflows/reusable.yml`, ""),
   626  	})
   627  	assertObject.True(rc.isEnabled(context.Background()))
   628  
   629  	rc = createIfTestRunContext(map[string]*model.Job{
   630  		"job1": createJob(t, `uses: ./.github/workflows/reusable.yml
   631  if: false`, ""),
   632  	})
   633  	assertObject.False(rc.isEnabled(context.Background()))
   634  }
   635  
   636  func TestRunContextGetEnv(t *testing.T) {
   637  	tests := []struct {
   638  		description string
   639  		rc          *RunContext
   640  		targetEnv   string
   641  		want        string
   642  	}{
   643  		{
   644  			description: "Env from Config should overwrite",
   645  			rc: &RunContext{
   646  				Config: &Config{
   647  					Env: map[string]string{"OVERWRITTEN": "true"},
   648  				},
   649  				Run: &model.Run{
   650  					Workflow: &model.Workflow{
   651  						Jobs: map[string]*model.Job{"test": {Name: "test"}},
   652  						Env:  map[string]string{"OVERWRITTEN": "false"},
   653  					},
   654  					JobID: "test",
   655  				},
   656  			},
   657  			targetEnv: "OVERWRITTEN",
   658  			want:      "true",
   659  		},
   660  		{
   661  			description: "No overwrite occurs",
   662  			rc: &RunContext{
   663  				Config: &Config{
   664  					Env: map[string]string{"SOME_OTHER_VAR": "true"},
   665  				},
   666  				Run: &model.Run{
   667  					Workflow: &model.Workflow{
   668  						Jobs: map[string]*model.Job{"test": {Name: "test"}},
   669  						Env:  map[string]string{"OVERWRITTEN": "false"},
   670  					},
   671  					JobID: "test",
   672  				},
   673  			},
   674  			targetEnv: "OVERWRITTEN",
   675  			want:      "false",
   676  		},
   677  	}
   678  
   679  	for _, test := range tests {
   680  		t.Run(test.description, func(t *testing.T) {
   681  			envMap := test.rc.GetEnv()
   682  			assert.EqualValues(t, test.want, envMap[test.targetEnv])
   683  		})
   684  	}
   685  }
   686  
   687  func TestSetRuntimeVariables(t *testing.T) {
   688  	rc := &RunContext{
   689  		Config: &Config{
   690  			ArtifactServerAddr: "myhost",
   691  			ArtifactServerPort: "8000",
   692  		},
   693  	}
   694  	v := "http://myhost:8000/"
   695  	env := map[string]string{}
   696  	setActionRuntimeVars(rc, env)
   697  
   698  	assert.Equal(t, v, env["ACTIONS_RESULTS_URL"])
   699  	assert.Equal(t, v, env["ACTIONS_RUNTIME_URL"])
   700  	runtimeToken := env["ACTIONS_RUNTIME_TOKEN"]
   701  	assert.NotEmpty(t, v, runtimeToken)
   702  
   703  	tkn, _, err := jwt.NewParser().ParseUnverified(runtimeToken, jwt.MapClaims{})
   704  	assert.NotNil(t, tkn)
   705  	assert.Nil(t, err)
   706  }
   707  
   708  func TestSetRuntimeVariablesWithRunID(t *testing.T) {
   709  	rc := &RunContext{
   710  		Config: &Config{
   711  			ArtifactServerAddr: "myhost",
   712  			ArtifactServerPort: "8000",
   713  			Env: map[string]string{
   714  				"GITHUB_RUN_ID": "45",
   715  			},
   716  		},
   717  	}
   718  	v := "http://myhost:8000/"
   719  	env := map[string]string{}
   720  	setActionRuntimeVars(rc, env)
   721  
   722  	assert.Equal(t, v, env["ACTIONS_RESULTS_URL"])
   723  	assert.Equal(t, v, env["ACTIONS_RUNTIME_URL"])
   724  	runtimeToken := env["ACTIONS_RUNTIME_TOKEN"]
   725  	assert.NotEmpty(t, v, runtimeToken)
   726  
   727  	claims := jwt.MapClaims{}
   728  	tkn, _, err := jwt.NewParser().ParseUnverified(runtimeToken, &claims)
   729  	assert.NotNil(t, tkn)
   730  	assert.Nil(t, err)
   731  	scp, ok := claims["scp"]
   732  	assert.True(t, ok, "scp claim exists")
   733  	assert.Equal(t, "Actions.Results:45:45", scp, "contains expected scp claim")
   734  }