github.com/snyk/vervet@v1.5.2-0.20220114214253-5ea660d01425/internal/linter/optic/linter_test.go (about)

     1  package optic
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  	"time"
    13  
    14  	qt "github.com/frankban/quicktest"
    15  	"github.com/go-git/go-git/v5"
    16  	"github.com/go-git/go-git/v5/plumbing"
    17  	"github.com/go-git/go-git/v5/plumbing/object"
    18  	"github.com/google/uuid"
    19  
    20  	"github.com/snyk/vervet/config"
    21  	"github.com/snyk/vervet/internal/files"
    22  	"github.com/snyk/vervet/testdata"
    23  )
    24  
    25  func TestNewLocalFile(t *testing.T) {
    26  	c := qt.New(t)
    27  	ctx, cancel := context.WithCancel(context.TODO())
    28  	c.Cleanup(cancel)
    29  
    30  	// Sanity check constructor
    31  	l, err := New(ctx, &config.OpticCILinter{
    32  		Image:    "some-image",
    33  		Original: "",
    34  		Proposed: "",
    35  	})
    36  	c.Assert(err, qt.IsNil)
    37  	c.Assert(l.image, qt.Equals, "some-image")
    38  	c.Assert(l.fromSource, qt.DeepEquals, files.NilSource{})
    39  	c.Assert(l.toSource, qt.DeepEquals, files.LocalFSSource{})
    40  
    41  	// Set up a local example project
    42  	testProject := c.TempDir()
    43  	versionDir := testProject + "/hello/2021-06-01"
    44  	c.Assert(os.MkdirAll(versionDir, 0777), qt.IsNil)
    45  	copyFile(c, filepath.Join(versionDir, "spec.yaml"), testdata.Path("resources/_examples/hello-world/2021-06-01/spec.yaml"))
    46  	origWd, err := os.Getwd()
    47  	c.Assert(err, qt.IsNil)
    48  	c.Cleanup(func() { c.Assert(os.Chdir(origWd), qt.IsNil) })
    49  	c.Assert(os.Chdir(testProject), qt.IsNil)
    50  
    51  	// Mock time for repeatable tests
    52  	l.timeNow = func() time.Time { return time.Date(2021, time.October, 30, 1, 2, 3, 0, time.UTC) }
    53  
    54  	// Capture stdout to a file
    55  	tempFile, err := os.Create(c.TempDir() + "/stdout")
    56  	c.Assert(err, qt.IsNil)
    57  	c.Patch(&os.Stdout, tempFile)
    58  	defer tempFile.Close()
    59  
    60  	runner := &mockRunner{}
    61  	l.runner = runner
    62  	err = l.Run(ctx, "hello", "hello/2021-06-01/spec.yaml")
    63  	c.Assert(err, qt.IsNil)
    64  	c.Assert(runner.runs, qt.HasLen, 1)
    65  	c.Assert(strings.Join(runner.runs[0], " "), qt.Matches,
    66  		``+
    67  			`^docker run --rm -v .*:/input.json -v .*/hello:/to/hello `+
    68  			`some-image bulk-compare --input /input.json`)
    69  
    70  	// Verify captured output was substituted. Mainly a convenience that makes
    71  	// output host-relevant and cmd-clickable if possible.
    72  	c.Assert(tempFile.Sync(), qt.IsNil)
    73  	capturedOutput, err := ioutil.ReadFile(tempFile.Name())
    74  	c.Assert(err, qt.IsNil)
    75  	c.Assert(string(capturedOutput), qt.Equals, "(does not exist):here.yaml (local file):eternity.yaml\n")
    76  
    77  	// Command failed.
    78  	runner = &mockRunner{err: fmt.Errorf("bad wolf")}
    79  	l.runner = runner
    80  	err = l.Run(ctx, "hello", "hello/2021-06-01/spec.yaml")
    81  	c.Assert(err, qt.ErrorMatches, ".*: bad wolf")
    82  }
    83  
    84  func TestNoSuchWorkingCopyFile(t *testing.T) {
    85  	c := qt.New(t)
    86  	path, err := files.LocalFSSource{}.Fetch(uuid.New().String())
    87  	c.Assert(err, qt.IsNil)
    88  	c.Assert(path, qt.Equals, "")
    89  }
    90  
    91  func TestNoSuchGitFile(t *testing.T) {
    92  	c := qt.New(t)
    93  	testRepo, commitHash := setupGitRepo(c)
    94  	gitSource, err := newGitRepoSource(testRepo, commitHash.String())
    95  	c.Assert(err, qt.IsNil)
    96  	c.Cleanup(func() { c.Assert(gitSource.Close(), qt.IsNil) })
    97  	_, err = gitSource.Prefetch("hello")
    98  	c.Assert(err, qt.IsNil)
    99  	path, err := gitSource.Fetch("hello/" + uuid.New().String())
   100  	c.Assert(err, qt.IsNil)
   101  	c.Assert(path, qt.Equals, "")
   102  }
   103  
   104  func TestNoSuchGitBranch(t *testing.T) {
   105  	c := qt.New(t)
   106  	testRepo, _ := setupGitRepo(c)
   107  	_, err := newGitRepoSource(testRepo, "nope")
   108  	c.Assert(err, qt.ErrorMatches, "reference not found")
   109  }
   110  
   111  func TestNewGitFile(t *testing.T) {
   112  	c := qt.New(t)
   113  	ctx, cancel := context.WithCancel(context.TODO())
   114  	c.Cleanup(cancel)
   115  
   116  	testRepo, commitHash := setupGitRepo(c)
   117  	origWd, err := os.Getwd()
   118  	c.Assert(err, qt.IsNil)
   119  	c.Cleanup(func() { c.Assert(os.Chdir(origWd), qt.IsNil) })
   120  	c.Assert(os.Chdir(testRepo), qt.IsNil)
   121  
   122  	// Sanity check constructor
   123  	l, err := New(ctx, &config.OpticCILinter{
   124  		Image:    "some-image",
   125  		Original: commitHash.String(),
   126  		Proposed: "",
   127  	})
   128  	c.Assert(err, qt.IsNil)
   129  	c.Assert(l.image, qt.Equals, "some-image")
   130  	c.Assert(l.fromSource, qt.Satisfies, func(v interface{}) bool {
   131  		_, ok := v.(*gitRepoSource)
   132  		return ok
   133  	})
   134  	c.Assert(l.toSource, qt.DeepEquals, files.LocalFSSource{})
   135  
   136  	// Sanity check gitRepoSource
   137  	_, err = l.fromSource.Prefetch("hello")
   138  	c.Assert(err, qt.IsNil)
   139  	path, err := l.fromSource.Fetch("hello/2021-06-01/spec.yaml")
   140  	c.Assert(err, qt.IsNil)
   141  	c.Assert(path, qt.Not(qt.Equals), "")
   142  
   143  	runner := &mockRunner{}
   144  	l.runner = runner
   145  	err = l.Run(ctx, "hello", "hello/2021-06-01/spec.yaml")
   146  	c.Assert(err, qt.IsNil)
   147  	c.Assert(runner.runs, qt.HasLen, 1)
   148  	c.Assert(strings.Join(runner.runs[0], " "), qt.Matches,
   149  		``+
   150  			`^docker run --rm -v .*:/input.json -v .*/hello:/to/hello `+
   151  			`some-image bulk-compare --input /input.json`)
   152  
   153  	// Command failed.
   154  	runner = &mockRunner{err: fmt.Errorf("bad wolf")}
   155  	l.runner = runner
   156  	err = l.Run(ctx, "hello", "hello/2021-06-01/spec.yaml")
   157  	c.Assert(err, qt.ErrorMatches, ".*: bad wolf")
   158  }
   159  
   160  func TestGitScript(t *testing.T) {
   161  	c := qt.New(t)
   162  	ctx, cancel := context.WithCancel(context.TODO())
   163  	c.Cleanup(cancel)
   164  
   165  	testRepo, commitHash := setupGitRepo(c)
   166  	origWd, err := os.Getwd()
   167  	c.Assert(err, qt.IsNil)
   168  	c.Cleanup(func() { c.Assert(os.Chdir(origWd), qt.IsNil) })
   169  	c.Assert(os.Chdir(testRepo), qt.IsNil)
   170  
   171  	// Sanity check constructor
   172  	l, err := New(ctx, &config.OpticCILinter{
   173  		Script:   "/usr/local/lib/node_modules/.bin/sweater-comb",
   174  		Original: commitHash.String(),
   175  		Proposed: "",
   176  	})
   177  	c.Assert(err, qt.IsNil)
   178  	c.Assert(l.image, qt.Equals, "")
   179  	c.Assert(l.script, qt.Equals, "/usr/local/lib/node_modules/.bin/sweater-comb")
   180  	c.Assert(l.fromSource, qt.Satisfies, func(v interface{}) bool {
   181  		_, ok := v.(*gitRepoSource)
   182  		return ok
   183  	})
   184  	c.Assert(l.toSource, qt.DeepEquals, files.LocalFSSource{})
   185  
   186  	// Sanity check gitRepoSource
   187  	_, err = l.fromSource.Prefetch("hello")
   188  	c.Assert(err, qt.IsNil)
   189  	path, err := l.fromSource.Fetch("hello/2021-06-01/spec.yaml")
   190  	c.Assert(err, qt.IsNil)
   191  	c.Assert(path, qt.Not(qt.Equals), "")
   192  
   193  	runner := &mockRunner{}
   194  	l.runner = runner
   195  	err = l.Run(ctx, "hello", "hello/2021-06-01/spec.yaml")
   196  	c.Assert(err, qt.IsNil)
   197  	c.Assert(runner.runs, qt.HasLen, 1)
   198  	c.Assert(strings.Join(runner.runs[0], " "), qt.Matches,
   199  		`/usr/local/lib/node_modules/.bin/sweater-comb bulk-compare --input `+filepath.Clean(os.TempDir())+`.*-input.json`)
   200  
   201  	// Command failed.
   202  	runner = &mockRunner{err: fmt.Errorf("bad wolf")}
   203  	l.runner = runner
   204  	err = l.Run(ctx, "hello", "hello/2021-06-01/spec.yaml")
   205  	c.Assert(err, qt.ErrorMatches, ".*: bad wolf")
   206  }
   207  
   208  func TestMatchDisjointSources(t *testing.T) {
   209  	c := qt.New(t)
   210  	o := &Optic{
   211  		fromSource: mockSource([]string{"apple", "orange"}),
   212  		toSource:   mockSource([]string{"blue", "green"}),
   213  	}
   214  	result, err := o.Match(&config.ResourceSet{Path: "whatever"})
   215  	c.Assert(err, qt.IsNil)
   216  	c.Assert(result, qt.ContentEquals, []string{"apple", "blue", "green", "orange"})
   217  }
   218  
   219  func TestMatchIntersectSources(t *testing.T) {
   220  	c := qt.New(t)
   221  	o := &Optic{
   222  		fromSource: mockSource([]string{"apple", "orange"}),
   223  		toSource:   mockSource([]string{"orange", "green"}),
   224  	}
   225  	result, err := o.Match(&config.ResourceSet{Path: "whatever"})
   226  	c.Assert(err, qt.IsNil)
   227  	c.Assert(result, qt.ContentEquals, []string{"apple", "green", "orange"})
   228  }
   229  
   230  type mockRunner struct {
   231  	runs [][]string
   232  	err  error
   233  }
   234  
   235  func (r *mockRunner) run(cmd *exec.Cmd) error {
   236  	fmt.Fprintln(cmd.Stdout, "/from/here.yaml /to/eternity.yaml")
   237  	r.runs = append(r.runs, cmd.Args)
   238  	return r.err
   239  }
   240  
   241  func copyFile(c *qt.C, dst, src string) {
   242  	contents, err := ioutil.ReadFile(src)
   243  	c.Assert(err, qt.IsNil)
   244  	err = ioutil.WriteFile(dst, contents, 0644)
   245  	c.Assert(err, qt.IsNil)
   246  }
   247  
   248  func setupGitRepo(c *qt.C) (string, plumbing.Hash) {
   249  	testRepo := c.TempDir()
   250  	repo, err := git.PlainInit(testRepo, false)
   251  	c.Assert(err, qt.IsNil)
   252  	versionDir := testRepo + "/hello/2021-06-01"
   253  	c.Assert(os.MkdirAll(versionDir, 0777), qt.IsNil)
   254  	copyFile(c, filepath.Join(versionDir, "spec.yaml"), testdata.Path("resources/_examples/hello-world/2021-06-01/spec.yaml"))
   255  	worktree, err := repo.Worktree()
   256  	c.Assert(err, qt.IsNil)
   257  	_, err = worktree.Add("hello")
   258  	c.Assert(err, qt.IsNil)
   259  	commitHash, err := worktree.Commit("test: initial commit", &git.CommitOptions{
   260  		All: true,
   261  		Author: &object.Signature{
   262  			Name:  "Bob Dobbs",
   263  			Email: "bob@example.com",
   264  		},
   265  	})
   266  	c.Assert(err, qt.IsNil)
   267  	copyFile(c, filepath.Join(versionDir, "spec.yaml"), testdata.Path("resources/_examples/hello-world/2021-06-13/spec.yaml"))
   268  	return testRepo, commitHash
   269  }
   270  
   271  type mockSource []string
   272  
   273  func (m mockSource) Name() string {
   274  	return "mock"
   275  }
   276  
   277  func (m mockSource) Match(*config.ResourceSet) ([]string, error) {
   278  	return m, nil
   279  }
   280  
   281  func (mockSource) Prefetch(path string) (string, error) { return path, nil }
   282  
   283  func (mockSource) Fetch(path string) (string, error) { return path, nil }
   284  
   285  func (mockSource) Close() error { return nil }