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  }