github.com/nektos/act@v0.2.83/pkg/runner/step_action_local_test.go (about) 1 package runner 2 3 import ( 4 "bytes" 5 "context" 6 "io" 7 "path/filepath" 8 "strings" 9 "testing" 10 11 "github.com/nektos/act/pkg/common" 12 "github.com/nektos/act/pkg/model" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/mock" 15 "gopkg.in/yaml.v3" 16 ) 17 18 type stepActionLocalMocks struct { 19 mock.Mock 20 } 21 22 func (salm *stepActionLocalMocks) runAction(step actionStep, actionDir string, remoteAction *remoteAction) common.Executor { 23 args := salm.Called(step, actionDir, remoteAction) 24 return args.Get(0).(func(context.Context) error) 25 } 26 27 func (salm *stepActionLocalMocks) readAction(_ context.Context, step *model.Step, actionDir string, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) { 28 args := salm.Called(step, actionDir, actionPath, readFile, writeFile) 29 return args.Get(0).(*model.Action), args.Error(1) 30 } 31 32 func TestStepActionLocalTest(t *testing.T) { 33 ctx := context.Background() 34 35 cm := &containerMock{} 36 salm := &stepActionLocalMocks{} 37 38 sal := &stepActionLocal{ 39 readAction: salm.readAction, 40 runAction: salm.runAction, 41 RunContext: &RunContext{ 42 StepResults: map[string]*model.StepResult{}, 43 ExprEval: &expressionEvaluator{}, 44 Config: &Config{ 45 Workdir: "/tmp", 46 }, 47 Run: &model.Run{ 48 JobID: "1", 49 Workflow: &model.Workflow{ 50 Jobs: map[string]*model.Job{ 51 "1": { 52 Defaults: model.Defaults{ 53 Run: model.RunDefaults{ 54 Shell: "bash", 55 }, 56 }, 57 }, 58 }, 59 }, 60 }, 61 JobContainer: cm, 62 }, 63 Step: &model.Step{ 64 ID: "1", 65 Uses: "./path/to/action", 66 }, 67 } 68 69 salm.On("readAction", sal.Step, filepath.Clean("/tmp/path/to/action"), "", mock.Anything, mock.Anything). 70 Return(&model.Action{}, nil) 71 72 cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(_ context.Context) error { 73 return nil 74 }) 75 76 cm.On("UpdateFromEnv", "/var/run/act/workflow/envs.txt", mock.AnythingOfType("*map[string]string")).Return(func(_ context.Context) error { 77 return nil 78 }) 79 80 cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(_ context.Context) error { 81 return nil 82 }) 83 84 cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(_ context.Context) error { 85 return nil 86 }) 87 88 cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/SUMMARY.md").Return(io.NopCloser(&bytes.Buffer{}), nil) 89 cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil) 90 91 salm.On("runAction", sal, filepath.Clean("/tmp/path/to/action"), (*remoteAction)(nil)).Return(func(_ context.Context) error { 92 return nil 93 }) 94 95 err := sal.pre()(ctx) 96 assert.Nil(t, err) 97 98 err = sal.main()(ctx) 99 assert.Nil(t, err) 100 101 cm.AssertExpectations(t) 102 salm.AssertExpectations(t) 103 } 104 105 func TestStepActionLocalPost(t *testing.T) { 106 table := []struct { 107 name string 108 stepModel *model.Step 109 actionModel *model.Action 110 initialStepResults map[string]*model.StepResult 111 err error 112 mocks struct { 113 env bool 114 exec bool 115 } 116 }{ 117 { 118 name: "main-success", 119 stepModel: &model.Step{ 120 ID: "step", 121 Uses: "./local/action", 122 }, 123 actionModel: &model.Action{ 124 Runs: model.ActionRuns{ 125 Using: "node16", 126 Post: "post.js", 127 PostIf: "always()", 128 }, 129 }, 130 initialStepResults: map[string]*model.StepResult{ 131 "step": { 132 Conclusion: model.StepStatusSuccess, 133 Outcome: model.StepStatusSuccess, 134 Outputs: map[string]string{}, 135 }, 136 }, 137 mocks: struct { 138 env bool 139 exec bool 140 }{ 141 env: true, 142 exec: true, 143 }, 144 }, 145 { 146 name: "main-failed", 147 stepModel: &model.Step{ 148 ID: "step", 149 Uses: "./local/action", 150 }, 151 actionModel: &model.Action{ 152 Runs: model.ActionRuns{ 153 Using: "node16", 154 Post: "post.js", 155 PostIf: "always()", 156 }, 157 }, 158 initialStepResults: map[string]*model.StepResult{ 159 "step": { 160 Conclusion: model.StepStatusFailure, 161 Outcome: model.StepStatusFailure, 162 Outputs: map[string]string{}, 163 }, 164 }, 165 mocks: struct { 166 env bool 167 exec bool 168 }{ 169 env: true, 170 exec: true, 171 }, 172 }, 173 { 174 name: "skip-if-failed", 175 stepModel: &model.Step{ 176 ID: "step", 177 Uses: "./local/action", 178 }, 179 actionModel: &model.Action{ 180 Runs: model.ActionRuns{ 181 Using: "node16", 182 Post: "post.js", 183 PostIf: "success()", 184 }, 185 }, 186 initialStepResults: map[string]*model.StepResult{ 187 "step": { 188 Conclusion: model.StepStatusFailure, 189 Outcome: model.StepStatusFailure, 190 Outputs: map[string]string{}, 191 }, 192 }, 193 mocks: struct { 194 env bool 195 exec bool 196 }{ 197 env: false, 198 exec: false, 199 }, 200 }, 201 { 202 name: "skip-if-main-skipped", 203 stepModel: &model.Step{ 204 ID: "step", 205 If: yaml.Node{Value: "failure()"}, 206 Uses: "./local/action", 207 }, 208 actionModel: &model.Action{ 209 Runs: model.ActionRuns{ 210 Using: "node16", 211 Post: "post.js", 212 PostIf: "always()", 213 }, 214 }, 215 initialStepResults: map[string]*model.StepResult{ 216 "step": { 217 Conclusion: model.StepStatusSkipped, 218 Outcome: model.StepStatusSkipped, 219 Outputs: map[string]string{}, 220 }, 221 }, 222 mocks: struct { 223 env bool 224 exec bool 225 }{ 226 env: false, 227 exec: false, 228 }, 229 }, 230 } 231 232 for _, tt := range table { 233 t.Run(tt.name, func(t *testing.T) { 234 ctx := context.Background() 235 236 cm := &containerMock{} 237 238 sal := &stepActionLocal{ 239 env: map[string]string{}, 240 RunContext: &RunContext{ 241 Config: &Config{ 242 GitHubInstance: "https://github.com", 243 }, 244 JobContainer: cm, 245 Run: &model.Run{ 246 JobID: "1", 247 Workflow: &model.Workflow{ 248 Jobs: map[string]*model.Job{ 249 "1": {}, 250 }, 251 }, 252 }, 253 StepResults: tt.initialStepResults, 254 nodeToolFullPath: "node", 255 }, 256 Step: tt.stepModel, 257 action: tt.actionModel, 258 } 259 sal.RunContext.ExprEval = sal.RunContext.NewExpressionEvaluator(ctx) 260 261 if tt.mocks.exec { 262 suffixMatcher := func(suffix string) interface{} { 263 return mock.MatchedBy(func(array []string) bool { 264 return strings.HasSuffix(array[1], suffix) 265 }) 266 } 267 cm.On("Exec", suffixMatcher("pkg/runner/local/action/post.js"), sal.env, "", "").Return(func(_ context.Context) error { return tt.err }) 268 269 cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(_ context.Context) error { 270 return nil 271 }) 272 273 cm.On("UpdateFromEnv", "/var/run/act/workflow/envs.txt", mock.AnythingOfType("*map[string]string")).Return(func(_ context.Context) error { 274 return nil 275 }) 276 277 cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(_ context.Context) error { 278 return nil 279 }) 280 281 cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(_ context.Context) error { 282 return nil 283 }) 284 285 cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/SUMMARY.md").Return(io.NopCloser(&bytes.Buffer{}), nil) 286 cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil) 287 } 288 289 err := sal.post()(ctx) 290 291 assert.Equal(t, tt.err, err) 292 assert.Equal(t, sal.RunContext.StepResults["post-step"], (*model.StepResult)(nil)) 293 cm.AssertExpectations(t) 294 }) 295 } 296 }