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 }