github.com/nektos/act@v0.2.83/pkg/runner/action_test.go (about)

     1  package runner
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"io/fs"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/nektos/act/pkg/model"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/mock"
    13  )
    14  
    15  type closerMock struct {
    16  	mock.Mock
    17  }
    18  
    19  func (m *closerMock) Close() error {
    20  	m.Called()
    21  	return nil
    22  }
    23  
    24  func TestActionReader(t *testing.T) {
    25  	yaml := strings.ReplaceAll(`
    26  name: 'name'
    27  runs:
    28  	using: 'node16'
    29  	main: 'main.js'
    30  `, "\t", "  ")
    31  
    32  	table := []struct {
    33  		name        string
    34  		step        *model.Step
    35  		filename    string
    36  		fileContent string
    37  		expected    *model.Action
    38  	}{
    39  		{
    40  			name:        "readActionYml",
    41  			step:        &model.Step{},
    42  			filename:    "action.yml",
    43  			fileContent: yaml,
    44  			expected: &model.Action{
    45  				Name: "name",
    46  				Runs: model.ActionRuns{
    47  					Using:  "node16",
    48  					Main:   "main.js",
    49  					PreIf:  "always()",
    50  					PostIf: "always()",
    51  				},
    52  			},
    53  		},
    54  		{
    55  			name:        "readActionYaml",
    56  			step:        &model.Step{},
    57  			filename:    "action.yaml",
    58  			fileContent: yaml,
    59  			expected: &model.Action{
    60  				Name: "name",
    61  				Runs: model.ActionRuns{
    62  					Using:  "node16",
    63  					Main:   "main.js",
    64  					PreIf:  "always()",
    65  					PostIf: "always()",
    66  				},
    67  			},
    68  		},
    69  		{
    70  			name:        "readDockerfile",
    71  			step:        &model.Step{},
    72  			filename:    "Dockerfile",
    73  			fileContent: "FROM ubuntu:20.04",
    74  			expected: &model.Action{
    75  				Name: "(Synthetic)",
    76  				Runs: model.ActionRuns{
    77  					Using: "docker",
    78  					Image: "Dockerfile",
    79  				},
    80  			},
    81  		},
    82  		{
    83  			name: "readWithArgs",
    84  			step: &model.Step{
    85  				With: map[string]string{
    86  					"args": "cmd",
    87  				},
    88  			},
    89  			expected: &model.Action{
    90  				Name: "(Synthetic)",
    91  				Inputs: map[string]model.Input{
    92  					"cwd": {
    93  						Description: "(Actual working directory)",
    94  						Required:    false,
    95  						Default:     "actionDir/actionPath",
    96  					},
    97  					"command": {
    98  						Description: "(Actual program)",
    99  						Required:    false,
   100  						Default:     "cmd",
   101  					},
   102  				},
   103  				Runs: model.ActionRuns{
   104  					Using: "node12",
   105  					Main:  "trampoline.js",
   106  				},
   107  			},
   108  		},
   109  	}
   110  
   111  	for _, tt := range table {
   112  		t.Run(tt.name, func(t *testing.T) {
   113  			closerMock := &closerMock{}
   114  
   115  			readFile := func(filename string) (io.Reader, io.Closer, error) {
   116  				if tt.filename != filename {
   117  					return nil, nil, fs.ErrNotExist
   118  				}
   119  
   120  				return strings.NewReader(tt.fileContent), closerMock, nil
   121  			}
   122  
   123  			writeFile := func(filename string, _ []byte, perm fs.FileMode) error {
   124  				assert.Equal(t, "actionDir/actionPath/trampoline.js", filename)
   125  				assert.Equal(t, fs.FileMode(0400), perm)
   126  				return nil
   127  			}
   128  
   129  			if tt.filename != "" {
   130  				closerMock.On("Close")
   131  			}
   132  
   133  			action, err := readActionImpl(context.Background(), tt.step, "actionDir", "actionPath", readFile, writeFile)
   134  
   135  			assert.Nil(t, err)
   136  			assert.Equal(t, tt.expected, action)
   137  
   138  			closerMock.AssertExpectations(t)
   139  		})
   140  	}
   141  }
   142  
   143  func TestActionRunner(t *testing.T) {
   144  	table := []struct {
   145  		name        string
   146  		step        actionStep
   147  		expectedEnv map[string]string
   148  	}{
   149  		{
   150  			name: "with-input",
   151  			step: &stepActionRemote{
   152  				Step: &model.Step{
   153  					Uses: "org/repo/path@ref",
   154  				},
   155  				RunContext: &RunContext{
   156  					Config: &Config{},
   157  					Run: &model.Run{
   158  						JobID: "job",
   159  						Workflow: &model.Workflow{
   160  							Jobs: map[string]*model.Job{
   161  								"job": {
   162  									Name: "job",
   163  								},
   164  							},
   165  						},
   166  					},
   167  					nodeToolFullPath: "node",
   168  				},
   169  				action: &model.Action{
   170  					Inputs: map[string]model.Input{
   171  						"key": {
   172  							Default: "default value",
   173  						},
   174  					},
   175  					Runs: model.ActionRuns{
   176  						Using: "node16",
   177  					},
   178  				},
   179  				env: map[string]string{},
   180  			},
   181  			expectedEnv: map[string]string{"INPUT_KEY": "default value"},
   182  		},
   183  		{
   184  			name: "restore-saved-state",
   185  			step: &stepActionRemote{
   186  				Step: &model.Step{
   187  					ID:   "step",
   188  					Uses: "org/repo/path@ref",
   189  				},
   190  				RunContext: &RunContext{
   191  					ActionPath: "path",
   192  					Config:     &Config{},
   193  					Run: &model.Run{
   194  						JobID: "job",
   195  						Workflow: &model.Workflow{
   196  							Jobs: map[string]*model.Job{
   197  								"job": {
   198  									Name: "job",
   199  								},
   200  							},
   201  						},
   202  					},
   203  					CurrentStep: "post-step",
   204  					StepResults: map[string]*model.StepResult{
   205  						"step": {},
   206  					},
   207  					IntraActionState: map[string]map[string]string{
   208  						"step": {
   209  							"name": "state value",
   210  						},
   211  					},
   212  					nodeToolFullPath: "node",
   213  				},
   214  				action: &model.Action{
   215  					Runs: model.ActionRuns{
   216  						Using: "node16",
   217  					},
   218  				},
   219  				env: map[string]string{},
   220  			},
   221  			expectedEnv: map[string]string{"STATE_name": "state value"},
   222  		},
   223  	}
   224  
   225  	for _, tt := range table {
   226  		t.Run(tt.name, func(t *testing.T) {
   227  			ctx := context.Background()
   228  
   229  			cm := &containerMock{}
   230  			cm.On("CopyDir", "/var/run/act/actions/dir/", "dir/", false).Return(func(_ context.Context) error { return nil })
   231  
   232  			envMatcher := mock.MatchedBy(func(env map[string]string) bool {
   233  				for k, v := range tt.expectedEnv {
   234  					if env[k] != v {
   235  						return false
   236  					}
   237  				}
   238  				return true
   239  			})
   240  
   241  			cm.On("Exec", []string{"node", "/var/run/act/actions/dir/path"}, envMatcher, "", "").Return(func(_ context.Context) error { return nil })
   242  
   243  			tt.step.getRunContext().JobContainer = cm
   244  
   245  			err := runActionImpl(tt.step, "dir", newRemoteAction("org/repo/path@ref"))(ctx)
   246  
   247  			assert.Nil(t, err)
   248  			cm.AssertExpectations(t)
   249  		})
   250  	}
   251  }