github.com/nektos/act@v0.2.63/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(ctx 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(ctx 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(ctx 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(ctx context.Context) error {
    85  		return nil
    86  	})
    87  
    88  	cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil)
    89  
    90  	salm.On("runAction", sal, filepath.Clean("/tmp/path/to/action"), (*remoteAction)(nil)).Return(func(ctx context.Context) error {
    91  		return nil
    92  	})
    93  
    94  	err := sal.pre()(ctx)
    95  	assert.Nil(t, err)
    96  
    97  	err = sal.main()(ctx)
    98  	assert.Nil(t, err)
    99  
   100  	cm.AssertExpectations(t)
   101  	salm.AssertExpectations(t)
   102  }
   103  
   104  func TestStepActionLocalPost(t *testing.T) {
   105  	table := []struct {
   106  		name               string
   107  		stepModel          *model.Step
   108  		actionModel        *model.Action
   109  		initialStepResults map[string]*model.StepResult
   110  		err                error
   111  		mocks              struct {
   112  			env  bool
   113  			exec bool
   114  		}
   115  	}{
   116  		{
   117  			name: "main-success",
   118  			stepModel: &model.Step{
   119  				ID:   "step",
   120  				Uses: "./local/action",
   121  			},
   122  			actionModel: &model.Action{
   123  				Runs: model.ActionRuns{
   124  					Using:  "node16",
   125  					Post:   "post.js",
   126  					PostIf: "always()",
   127  				},
   128  			},
   129  			initialStepResults: map[string]*model.StepResult{
   130  				"step": {
   131  					Conclusion: model.StepStatusSuccess,
   132  					Outcome:    model.StepStatusSuccess,
   133  					Outputs:    map[string]string{},
   134  				},
   135  			},
   136  			mocks: struct {
   137  				env  bool
   138  				exec bool
   139  			}{
   140  				env:  true,
   141  				exec: true,
   142  			},
   143  		},
   144  		{
   145  			name: "main-failed",
   146  			stepModel: &model.Step{
   147  				ID:   "step",
   148  				Uses: "./local/action",
   149  			},
   150  			actionModel: &model.Action{
   151  				Runs: model.ActionRuns{
   152  					Using:  "node16",
   153  					Post:   "post.js",
   154  					PostIf: "always()",
   155  				},
   156  			},
   157  			initialStepResults: map[string]*model.StepResult{
   158  				"step": {
   159  					Conclusion: model.StepStatusFailure,
   160  					Outcome:    model.StepStatusFailure,
   161  					Outputs:    map[string]string{},
   162  				},
   163  			},
   164  			mocks: struct {
   165  				env  bool
   166  				exec bool
   167  			}{
   168  				env:  true,
   169  				exec: true,
   170  			},
   171  		},
   172  		{
   173  			name: "skip-if-failed",
   174  			stepModel: &model.Step{
   175  				ID:   "step",
   176  				Uses: "./local/action",
   177  			},
   178  			actionModel: &model.Action{
   179  				Runs: model.ActionRuns{
   180  					Using:  "node16",
   181  					Post:   "post.js",
   182  					PostIf: "success()",
   183  				},
   184  			},
   185  			initialStepResults: map[string]*model.StepResult{
   186  				"step": {
   187  					Conclusion: model.StepStatusFailure,
   188  					Outcome:    model.StepStatusFailure,
   189  					Outputs:    map[string]string{},
   190  				},
   191  			},
   192  			mocks: struct {
   193  				env  bool
   194  				exec bool
   195  			}{
   196  				env:  false,
   197  				exec: false,
   198  			},
   199  		},
   200  		{
   201  			name: "skip-if-main-skipped",
   202  			stepModel: &model.Step{
   203  				ID:   "step",
   204  				If:   yaml.Node{Value: "failure()"},
   205  				Uses: "./local/action",
   206  			},
   207  			actionModel: &model.Action{
   208  				Runs: model.ActionRuns{
   209  					Using:  "node16",
   210  					Post:   "post.js",
   211  					PostIf: "always()",
   212  				},
   213  			},
   214  			initialStepResults: map[string]*model.StepResult{
   215  				"step": {
   216  					Conclusion: model.StepStatusSkipped,
   217  					Outcome:    model.StepStatusSkipped,
   218  					Outputs:    map[string]string{},
   219  				},
   220  			},
   221  			mocks: struct {
   222  				env  bool
   223  				exec bool
   224  			}{
   225  				env:  false,
   226  				exec: false,
   227  			},
   228  		},
   229  	}
   230  
   231  	for _, tt := range table {
   232  		t.Run(tt.name, func(t *testing.T) {
   233  			ctx := context.Background()
   234  
   235  			cm := &containerMock{}
   236  
   237  			sal := &stepActionLocal{
   238  				env: map[string]string{},
   239  				RunContext: &RunContext{
   240  					Config: &Config{
   241  						GitHubInstance: "https://github.com",
   242  					},
   243  					JobContainer: cm,
   244  					Run: &model.Run{
   245  						JobID: "1",
   246  						Workflow: &model.Workflow{
   247  							Jobs: map[string]*model.Job{
   248  								"1": {},
   249  							},
   250  						},
   251  					},
   252  					StepResults: tt.initialStepResults,
   253  				},
   254  				Step:   tt.stepModel,
   255  				action: tt.actionModel,
   256  			}
   257  			sal.RunContext.ExprEval = sal.RunContext.NewExpressionEvaluator(ctx)
   258  
   259  			if tt.mocks.exec {
   260  				suffixMatcher := func(suffix string) interface{} {
   261  					return mock.MatchedBy(func(array []string) bool {
   262  						return strings.HasSuffix(array[1], suffix)
   263  					})
   264  				}
   265  				cm.On("Exec", suffixMatcher("pkg/runner/local/action/post.js"), sal.env, "", "").Return(func(ctx context.Context) error { return tt.err })
   266  
   267  				cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
   268  					return nil
   269  				})
   270  
   271  				cm.On("UpdateFromEnv", "/var/run/act/workflow/envs.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
   272  					return nil
   273  				})
   274  
   275  				cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
   276  					return nil
   277  				})
   278  
   279  				cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
   280  					return nil
   281  				})
   282  
   283  				cm.On("GetContainerArchive", ctx, "/var/run/act/workflow/pathcmd.txt").Return(io.NopCloser(&bytes.Buffer{}), nil)
   284  			}
   285  
   286  			err := sal.post()(ctx)
   287  
   288  			assert.Equal(t, tt.err, err)
   289  			assert.Equal(t, sal.RunContext.StepResults["post-step"], (*model.StepResult)(nil))
   290  			cm.AssertExpectations(t)
   291  		})
   292  	}
   293  }