github.com/nektos/act@v0.2.83/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  		t.Run(table.in, func(t *testing.T) {
   159  			assertObject := assert.New(t)
   160  			b, err := EvalBool(context.Background(), rc.ExprEval, table.in, exprparser.DefaultStatusCheckSuccess)
   161  			if table.wantErr {
   162  				assertObject.Error(err)
   163  			}
   164  
   165  			assertObject.Equal(table.out, b, fmt.Sprintf("Expected %s to be %v, was %v", table.in, table.out, b))
   166  		})
   167  	}
   168  }
   169  
   170  func updateTestIfWorkflow(t *testing.T, tables []struct {
   171  	in      string
   172  	out     bool
   173  	wantErr bool
   174  }, rc *RunContext) {
   175  	var envs string
   176  	keys := make([]string, 0, len(rc.Env))
   177  	for k := range rc.Env {
   178  		keys = append(keys, k)
   179  	}
   180  	sort.Strings(keys)
   181  	for _, k := range keys {
   182  		envs += fmt.Sprintf("  %s: %s\n", k, rc.Env[k])
   183  	}
   184  	// editorconfig-checker-disable
   185  	workflow := fmt.Sprintf(`
   186  name: "Test what expressions result in true and false on GitHub"
   187  on: push
   188  
   189  env:
   190  %s
   191  
   192  jobs:
   193    test-ifs-and-buts:
   194      runs-on: ubuntu-latest
   195      steps:
   196  `, envs)
   197  	// editorconfig-checker-enable
   198  
   199  	for i, table := range tables {
   200  		if table.wantErr || strings.HasPrefix(table.in, "github.actor") {
   201  			continue
   202  		}
   203  		expressionPattern := regexp.MustCompile(`\${{\s*(.+?)\s*}}`)
   204  
   205  		expr := expressionPattern.ReplaceAllStringFunc(table.in, func(match string) string {
   206  			return fmt.Sprintf("€{{ %s }}", expressionPattern.ReplaceAllString(match, "$1"))
   207  		})
   208  		echo := fmt.Sprintf(`run: echo "%s should be false, but was evaluated to true;" exit 1;`, table.in)
   209  		name := fmt.Sprintf(`"❌ I should not run, expr: %s"`, expr)
   210  		if table.out {
   211  			echo = `run: echo OK`
   212  			name = fmt.Sprintf(`"✅ I should run, expr: %s"`, expr)
   213  		}
   214  		workflow += fmt.Sprintf("\n      - name: %s\n        id: step%d\n        if: %s\n        %s\n", name, i, table.in, echo)
   215  		if table.out {
   216  			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)
   217  		}
   218  	}
   219  
   220  	file, err := os.Create("../../.github/workflows/test-if.yml")
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	}
   224  
   225  	_, err = file.WriteString(workflow)
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  }
   230  
   231  func TestRunContext_GetBindsAndMounts(t *testing.T) {
   232  	rctemplate := &RunContext{
   233  		Name: "TestRCName",
   234  		Run: &model.Run{
   235  			Workflow: &model.Workflow{
   236  				Name: "TestWorkflowName",
   237  			},
   238  		},
   239  		Config: &Config{
   240  			BindWorkdir: false,
   241  		},
   242  	}
   243  
   244  	tests := []struct {
   245  		windowsPath bool
   246  		name        string
   247  		rc          *RunContext
   248  		wantbind    string
   249  		wantmount   string
   250  	}{
   251  		{false, "/mnt/linux", rctemplate, "/mnt/linux", "/mnt/linux"},
   252  		{false, "/mnt/path with spaces/linux", rctemplate, "/mnt/path with spaces/linux", "/mnt/path with spaces/linux"},
   253  		{true, "C:\\Users\\TestPath\\MyTestPath", rctemplate, "/mnt/c/Users/TestPath/MyTestPath", "/mnt/c/Users/TestPath/MyTestPath"},
   254  		{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"},
   255  		{true, "/LinuxPathOnWindowsShouldFail", rctemplate, "", ""},
   256  	}
   257  
   258  	isWindows := runtime.GOOS == "windows"
   259  
   260  	for _, testcase := range tests {
   261  		for _, bindWorkDir := range []bool{true, false} {
   262  			testBindSuffix := ""
   263  			if bindWorkDir {
   264  				testBindSuffix = "Bind"
   265  			}
   266  
   267  			// Only run windows path tests on windows and non-windows on non-windows
   268  			if (isWindows && testcase.windowsPath) || (!isWindows && !testcase.windowsPath) {
   269  				t.Run((testcase.name + testBindSuffix), func(t *testing.T) {
   270  					config := testcase.rc.Config
   271  					config.Workdir = testcase.name
   272  					config.BindWorkdir = bindWorkDir
   273  					gotbind, gotmount := rctemplate.GetBindsAndMounts()
   274  
   275  					// Name binds/mounts are either/or
   276  					if config.BindWorkdir {
   277  						fullBind := testcase.name + ":" + testcase.wantbind
   278  						if runtime.GOOS == "darwin" {
   279  							fullBind += ":delegated"
   280  						}
   281  						assert.Contains(t, gotbind, fullBind)
   282  					} else {
   283  						mountkey := testcase.rc.jobContainerName()
   284  						assert.EqualValues(t, testcase.wantmount, gotmount[mountkey])
   285  					}
   286  				})
   287  			}
   288  		}
   289  	}
   290  
   291  	t.Run("ContainerVolumeMountTest", func(t *testing.T) {
   292  		tests := []struct {
   293  			name      string
   294  			volumes   []string
   295  			wantbind  string
   296  			wantmount map[string]string
   297  		}{
   298  			{"BindAnonymousVolume", []string{"/volume"}, "/volume", map[string]string{}},
   299  			{"BindHostFile", []string{"/path/to/file/on/host:/volume"}, "/path/to/file/on/host:/volume", map[string]string{}},
   300  			{"MountExistingVolume", []string{"volume-id:/volume"}, "", map[string]string{"volume-id": "/volume"}},
   301  		}
   302  
   303  		for _, testcase := range tests {
   304  			t.Run(testcase.name, func(t *testing.T) {
   305  				job := &model.Job{}
   306  				err := job.RawContainer.Encode(map[string][]string{
   307  					"volumes": testcase.volumes,
   308  				})
   309  				assert.NoError(t, err)
   310  
   311  				rc := &RunContext{
   312  					Name: "TestRCName",
   313  					Run: &model.Run{
   314  						Workflow: &model.Workflow{
   315  							Name: "TestWorkflowName",
   316  						},
   317  					},
   318  					Config: &Config{
   319  						BindWorkdir: false,
   320  					},
   321  				}
   322  				rc.Run.JobID = "job1"
   323  				rc.Run.Workflow.Jobs = map[string]*model.Job{"job1": job}
   324  
   325  				gotbind, gotmount := rc.GetBindsAndMounts()
   326  
   327  				if len(testcase.wantbind) > 0 {
   328  					assert.Contains(t, gotbind, testcase.wantbind)
   329  				}
   330  
   331  				for k, v := range testcase.wantmount {
   332  					assert.Contains(t, gotmount, k)
   333  					assert.Equal(t, gotmount[k], v)
   334  				}
   335  			})
   336  		}
   337  	})
   338  }
   339  
   340  func TestGetGitHubContext(t *testing.T) {
   341  	log.SetLevel(log.DebugLevel)
   342  
   343  	cwd, err := os.Getwd()
   344  	assert.Nil(t, err)
   345  
   346  	rc := &RunContext{
   347  		Config: &Config{
   348  			EventName: "push",
   349  			Workdir:   cwd,
   350  		},
   351  		Run: &model.Run{
   352  			Workflow: &model.Workflow{
   353  				Name: "GitHubContextTest",
   354  			},
   355  		},
   356  		Name:           "GitHubContextTest",
   357  		CurrentStep:    "step",
   358  		Matrix:         map[string]interface{}{},
   359  		Env:            map[string]string{},
   360  		ExtraPath:      []string{},
   361  		StepResults:    map[string]*model.StepResult{},
   362  		OutputMappings: map[MappableOutput]MappableOutput{},
   363  	}
   364  	rc.Run.JobID = "job1"
   365  
   366  	ghc := rc.getGithubContext(context.Background())
   367  
   368  	log.Debugf("%v", ghc)
   369  
   370  	actor := "nektos/act"
   371  	if a := os.Getenv("ACT_ACTOR"); a != "" {
   372  		actor = a
   373  	}
   374  
   375  	repo := "nektos/act"
   376  	if r := os.Getenv("ACT_REPOSITORY"); r != "" {
   377  		repo = r
   378  	}
   379  
   380  	owner := "nektos"
   381  	if o := os.Getenv("ACT_OWNER"); o != "" {
   382  		owner = o
   383  	}
   384  
   385  	assert.Equal(t, "1", ghc.RunID)
   386  	assert.Equal(t, "1", ghc.RunNumber)
   387  	assert.Equal(t, "0", ghc.RetentionDays)
   388  	assert.Equal(t, actor, ghc.Actor)
   389  	assert.Equal(t, repo, ghc.Repository)
   390  	assert.Equal(t, owner, ghc.RepositoryOwner)
   391  	assert.Equal(t, "/dev/null", ghc.RunnerPerflog)
   392  	assert.Equal(t, rc.Config.Secrets["GITHUB_TOKEN"], ghc.Token)
   393  	assert.Equal(t, "job1", ghc.Job)
   394  }
   395  
   396  func TestGetGithubContextRef(t *testing.T) {
   397  	table := []struct {
   398  		event string
   399  		json  string
   400  		ref   string
   401  	}{
   402  		{event: "push", json: `{"ref":"0000000000000000000000000000000000000000"}`, ref: "0000000000000000000000000000000000000000"},
   403  		{event: "create", json: `{"ref":"0000000000000000000000000000000000000000"}`, ref: "0000000000000000000000000000000000000000"},
   404  		{event: "workflow_dispatch", json: `{"ref":"0000000000000000000000000000000000000000"}`, ref: "0000000000000000000000000000000000000000"},
   405  		{event: "delete", json: `{"repository":{"default_branch": "main"}}`, ref: "refs/heads/main"},
   406  		{event: "pull_request", json: `{"number":123}`, ref: "refs/pull/123/merge"},
   407  		{event: "pull_request_review", json: `{"number":123}`, ref: "refs/pull/123/merge"},
   408  		{event: "pull_request_review_comment", json: `{"number":123}`, ref: "refs/pull/123/merge"},
   409  		{event: "pull_request_target", json: `{"pull_request":{"base":{"ref": "main"}}}`, ref: "refs/heads/main"},
   410  		{event: "deployment", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"},
   411  		{event: "deployment_status", json: `{"deployment": {"ref": "tag-name"}}`, ref: "tag-name"},
   412  		{event: "release", json: `{"release": {"tag_name": "tag-name"}}`, ref: "refs/tags/tag-name"},
   413  	}
   414  
   415  	for _, data := range table {
   416  		t.Run(data.event, func(t *testing.T) {
   417  			rc := &RunContext{
   418  				EventJSON: data.json,
   419  				Config: &Config{
   420  					EventName: data.event,
   421  					Workdir:   "",
   422  				},
   423  				Run: &model.Run{
   424  					Workflow: &model.Workflow{
   425  						Name: "GitHubContextTest",
   426  					},
   427  				},
   428  			}
   429  
   430  			ghc := rc.getGithubContext(context.Background())
   431  
   432  			assert.Equal(t, data.ref, ghc.Ref)
   433  		})
   434  	}
   435  }
   436  
   437  func createIfTestRunContext(jobs map[string]*model.Job) *RunContext {
   438  	rc := &RunContext{
   439  		Config: &Config{
   440  			Workdir: ".",
   441  			Platforms: map[string]string{
   442  				"ubuntu-latest": "ubuntu-latest",
   443  			},
   444  		},
   445  		Env: map[string]string{},
   446  		Run: &model.Run{
   447  			JobID: "job1",
   448  			Workflow: &model.Workflow{
   449  				Name: "test-workflow",
   450  				Jobs: jobs,
   451  			},
   452  		},
   453  	}
   454  	rc.ExprEval = rc.NewExpressionEvaluator(context.Background())
   455  
   456  	return rc
   457  }
   458  
   459  func createJob(t *testing.T, input string, result string) *model.Job {
   460  	var job *model.Job
   461  	err := yaml.Unmarshal([]byte(input), &job)
   462  	assert.NoError(t, err)
   463  	job.Result = result
   464  
   465  	return job
   466  }
   467  
   468  func TestRunContextRunsOnPlatformNames(t *testing.T) {
   469  	log.SetLevel(log.DebugLevel)
   470  	assertObject := assert.New(t)
   471  
   472  	rc := createIfTestRunContext(map[string]*model.Job{
   473  		"job1": createJob(t, `runs-on: ubuntu-latest`, ""),
   474  	})
   475  	assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
   476  
   477  	rc = createIfTestRunContext(map[string]*model.Job{
   478  		"job1": createJob(t, `runs-on: ${{ 'ubuntu-latest' }}`, ""),
   479  	})
   480  	assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
   481  
   482  	rc = createIfTestRunContext(map[string]*model.Job{
   483  		"job1": createJob(t, `runs-on: [self-hosted, my-runner]`, ""),
   484  	})
   485  	assertObject.Equal([]string{"self-hosted", "my-runner"}, rc.runsOnPlatformNames(context.Background()))
   486  
   487  	rc = createIfTestRunContext(map[string]*model.Job{
   488  		"job1": createJob(t, `runs-on: [self-hosted, "${{ 'my-runner' }}"]`, ""),
   489  	})
   490  	assertObject.Equal([]string{"self-hosted", "my-runner"}, rc.runsOnPlatformNames(context.Background()))
   491  
   492  	rc = createIfTestRunContext(map[string]*model.Job{
   493  		"job1": createJob(t, `runs-on: ${{ fromJSON('["ubuntu-latest"]') }}`, ""),
   494  	})
   495  	assertObject.Equal([]string{"ubuntu-latest"}, rc.runsOnPlatformNames(context.Background()))
   496  
   497  	// test missing / invalid runs-on
   498  	rc = createIfTestRunContext(map[string]*model.Job{
   499  		"job1": createJob(t, `name: something`, ""),
   500  	})
   501  	assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
   502  
   503  	rc = createIfTestRunContext(map[string]*model.Job{
   504  		"job1": createJob(t, `runs-on:
   505    mapping: value`, ""),
   506  	})
   507  	assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
   508  
   509  	rc = createIfTestRunContext(map[string]*model.Job{
   510  		"job1": createJob(t, `runs-on: ${{ invalid expression }}`, ""),
   511  	})
   512  	assertObject.Equal([]string{}, rc.runsOnPlatformNames(context.Background()))
   513  }
   514  
   515  func TestRunContextIsEnabled(t *testing.T) {
   516  	log.SetLevel(log.DebugLevel)
   517  	assertObject := assert.New(t)
   518  
   519  	// success()
   520  	rc := createIfTestRunContext(map[string]*model.Job{
   521  		"job1": createJob(t, `runs-on: ubuntu-latest
   522  if: success()`, ""),
   523  	})
   524  	assertObject.True(rc.isEnabled(context.Background()))
   525  
   526  	rc = createIfTestRunContext(map[string]*model.Job{
   527  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   528  		"job2": createJob(t, `runs-on: ubuntu-latest
   529  needs: [job1]
   530  if: success()`, ""),
   531  	})
   532  	rc.Run.JobID = "job2"
   533  	assertObject.False(rc.isEnabled(context.Background()))
   534  
   535  	rc = createIfTestRunContext(map[string]*model.Job{
   536  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   537  		"job2": createJob(t, `runs-on: ubuntu-latest
   538  needs: [job1]
   539  if: success()`, ""),
   540  	})
   541  	rc.Run.JobID = "job2"
   542  	assertObject.True(rc.isEnabled(context.Background()))
   543  
   544  	rc = createIfTestRunContext(map[string]*model.Job{
   545  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   546  		"job2": createJob(t, `runs-on: ubuntu-latest
   547  if: success()`, ""),
   548  	})
   549  	rc.Run.JobID = "job2"
   550  	assertObject.True(rc.isEnabled(context.Background()))
   551  
   552  	// failure()
   553  	rc = createIfTestRunContext(map[string]*model.Job{
   554  		"job1": createJob(t, `runs-on: ubuntu-latest
   555  if: failure()`, ""),
   556  	})
   557  	assertObject.False(rc.isEnabled(context.Background()))
   558  
   559  	rc = createIfTestRunContext(map[string]*model.Job{
   560  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   561  		"job2": createJob(t, `runs-on: ubuntu-latest
   562  needs: [job1]
   563  if: failure()`, ""),
   564  	})
   565  	rc.Run.JobID = "job2"
   566  	assertObject.True(rc.isEnabled(context.Background()))
   567  
   568  	rc = createIfTestRunContext(map[string]*model.Job{
   569  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   570  		"job2": createJob(t, `runs-on: ubuntu-latest
   571  needs: [job1]
   572  if: failure()`, ""),
   573  	})
   574  	rc.Run.JobID = "job2"
   575  	assertObject.False(rc.isEnabled(context.Background()))
   576  
   577  	rc = createIfTestRunContext(map[string]*model.Job{
   578  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   579  		"job2": createJob(t, `runs-on: ubuntu-latest
   580  if: failure()`, ""),
   581  	})
   582  	rc.Run.JobID = "job2"
   583  	assertObject.False(rc.isEnabled(context.Background()))
   584  
   585  	// always()
   586  	rc = createIfTestRunContext(map[string]*model.Job{
   587  		"job1": createJob(t, `runs-on: ubuntu-latest
   588  if: always()`, ""),
   589  	})
   590  	assertObject.True(rc.isEnabled(context.Background()))
   591  
   592  	rc = createIfTestRunContext(map[string]*model.Job{
   593  		"job1": createJob(t, `runs-on: ubuntu-latest`, "failure"),
   594  		"job2": createJob(t, `runs-on: ubuntu-latest
   595  needs: [job1]
   596  if: always()`, ""),
   597  	})
   598  	rc.Run.JobID = "job2"
   599  	assertObject.True(rc.isEnabled(context.Background()))
   600  
   601  	rc = createIfTestRunContext(map[string]*model.Job{
   602  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   603  		"job2": createJob(t, `runs-on: ubuntu-latest
   604  needs: [job1]
   605  if: always()`, ""),
   606  	})
   607  	rc.Run.JobID = "job2"
   608  	assertObject.True(rc.isEnabled(context.Background()))
   609  
   610  	rc = createIfTestRunContext(map[string]*model.Job{
   611  		"job1": createJob(t, `runs-on: ubuntu-latest`, "success"),
   612  		"job2": createJob(t, `runs-on: ubuntu-latest
   613  if: always()`, ""),
   614  	})
   615  	rc.Run.JobID = "job2"
   616  	assertObject.True(rc.isEnabled(context.Background()))
   617  
   618  	rc = createIfTestRunContext(map[string]*model.Job{
   619  		"job1": createJob(t, `uses: ./.github/workflows/reusable.yml`, ""),
   620  	})
   621  	assertObject.True(rc.isEnabled(context.Background()))
   622  
   623  	rc = createIfTestRunContext(map[string]*model.Job{
   624  		"job1": createJob(t, `uses: ./.github/workflows/reusable.yml
   625  if: false`, ""),
   626  	})
   627  	assertObject.False(rc.isEnabled(context.Background()))
   628  }
   629  
   630  func TestRunContextGetEnv(t *testing.T) {
   631  	tests := []struct {
   632  		description string
   633  		rc          *RunContext
   634  		targetEnv   string
   635  		want        string
   636  	}{
   637  		{
   638  			description: "Env from Config should overwrite",
   639  			rc: &RunContext{
   640  				Config: &Config{
   641  					Env: map[string]string{"OVERWRITTEN": "true"},
   642  				},
   643  				Run: &model.Run{
   644  					Workflow: &model.Workflow{
   645  						Jobs: map[string]*model.Job{"test": {Name: "test"}},
   646  						Env:  map[string]string{"OVERWRITTEN": "false"},
   647  					},
   648  					JobID: "test",
   649  				},
   650  			},
   651  			targetEnv: "OVERWRITTEN",
   652  			want:      "true",
   653  		},
   654  		{
   655  			description: "No overwrite occurs",
   656  			rc: &RunContext{
   657  				Config: &Config{
   658  					Env: map[string]string{"SOME_OTHER_VAR": "true"},
   659  				},
   660  				Run: &model.Run{
   661  					Workflow: &model.Workflow{
   662  						Jobs: map[string]*model.Job{"test": {Name: "test"}},
   663  						Env:  map[string]string{"OVERWRITTEN": "false"},
   664  					},
   665  					JobID: "test",
   666  				},
   667  			},
   668  			targetEnv: "OVERWRITTEN",
   669  			want:      "false",
   670  		},
   671  	}
   672  
   673  	for _, test := range tests {
   674  		t.Run(test.description, func(t *testing.T) {
   675  			envMap := test.rc.GetEnv()
   676  			assert.EqualValues(t, test.want, envMap[test.targetEnv])
   677  		})
   678  	}
   679  }
   680  
   681  func TestSetRuntimeVariables(t *testing.T) {
   682  	rc := &RunContext{
   683  		Config: &Config{
   684  			ArtifactServerAddr: "myhost",
   685  			ArtifactServerPort: "8000",
   686  		},
   687  	}
   688  	v := "http://myhost:8000/"
   689  	env := map[string]string{}
   690  	setActionRuntimeVars(rc, env)
   691  
   692  	assert.Equal(t, v, env["ACTIONS_RESULTS_URL"])
   693  	assert.Equal(t, v, env["ACTIONS_RUNTIME_URL"])
   694  	runtimeToken := env["ACTIONS_RUNTIME_TOKEN"]
   695  	assert.NotEmpty(t, v, runtimeToken)
   696  
   697  	tkn, _, err := jwt.NewParser().ParseUnverified(runtimeToken, jwt.MapClaims{})
   698  	assert.NotNil(t, tkn)
   699  	assert.Nil(t, err)
   700  }
   701  
   702  func TestSetRuntimeVariablesWithRunID(t *testing.T) {
   703  	rc := &RunContext{
   704  		Config: &Config{
   705  			ArtifactServerAddr: "myhost",
   706  			ArtifactServerPort: "8000",
   707  			Env: map[string]string{
   708  				"GITHUB_RUN_ID": "45",
   709  			},
   710  		},
   711  	}
   712  	v := "http://myhost:8000/"
   713  	env := map[string]string{}
   714  	setActionRuntimeVars(rc, env)
   715  
   716  	assert.Equal(t, v, env["ACTIONS_RESULTS_URL"])
   717  	assert.Equal(t, v, env["ACTIONS_RUNTIME_URL"])
   718  	runtimeToken := env["ACTIONS_RUNTIME_TOKEN"]
   719  	assert.NotEmpty(t, v, runtimeToken)
   720  
   721  	claims := jwt.MapClaims{}
   722  	tkn, _, err := jwt.NewParser().ParseUnverified(runtimeToken, &claims)
   723  	assert.NotNil(t, tkn)
   724  	assert.Nil(t, err)
   725  	scp, ok := claims["scp"]
   726  	assert.True(t, ok, "scp claim exists")
   727  	assert.Equal(t, "Actions.Results:45:45", scp, "contains expected scp claim")
   728  }