| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "context" |
| "os" |
| "path/filepath" |
| "testing" |
| |
| "go.fuchsia.dev/infra/cmd/roller-configurator/proto" |
| "google.golang.org/protobuf/types/known/structpb" |
| ) |
| |
| var filesWithGitmodules = map[string]string{ |
| ".gitmodules": ` |
| [submodule "path/to/submodule"] |
| url = "https://example.com/asubmodule" |
| `, |
| } |
| |
| func TestValidate_valid(t *testing.T) { |
| t.Parallel() |
| |
| testCases := []struct { |
| name string |
| config *proto.Config |
| files map[string]string |
| }{ |
| { |
| name: "empty", |
| config: &proto.Config{}, |
| }, |
| { |
| name: "submodule with interval schedule", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/submodule", |
| }}, |
| Schedule: "with 24h interval", |
| }, |
| }, |
| }, |
| files: filesWithGitmodules, |
| }, |
| { |
| name: "cipd ensure file with cron schedule", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_CipdEnsureFile{CipdEnsureFile: &proto.CIPDEnsureFile{ |
| Path: "path/to/cipd.ensure", |
| Ref: "foo", |
| }}, |
| Schedule: "0/20 * * * *", |
| }, |
| }, |
| }, |
| files: map[string]string{ |
| "path/to/cipd.ensure": ``, |
| }, |
| }, |
| { |
| name: "jiri project with notify emails", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_JiriProject{JiriProject: &proto.JiriProject{ |
| Manifest: "path/to/manifest", |
| Project: "project-name", |
| }}, |
| NotifyEmails: []string{ |
| "foo@example.com", |
| "bar@example.com", |
| }, |
| }, |
| }, |
| }, |
| files: map[string]string{ |
| "path/to/manifest": ``, |
| }, |
| }, |
| { |
| name: "jiri packages", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_JiriPackages{JiriPackages: &proto.JiriPackages{ |
| PackagesByManifest: map[string]*structpb.ListValue{ |
| "path/to/manifest": { |
| Values: []*structpb.Value{ |
| structpb.NewStringValue("package1"), |
| structpb.NewStringValue("package2"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| files: map[string]string{ |
| "path/to/manifest": ``, |
| }, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| tc := tc |
| t.Run(tc.name, func(t *testing.T) { |
| t.Parallel() |
| |
| repoRoot := t.TempDir() |
| writeFiles(t, repoRoot, tc.files) |
| |
| err := validate(context.Background(), repoRoot, tc.config) |
| if err != nil { |
| t.Fatal(err) |
| } |
| }) |
| } |
| } |
| |
| func TestValidate_invalid(t *testing.T) { |
| t.Parallel() |
| |
| testCases := []struct { |
| name string |
| config *proto.Config |
| err string |
| files map[string]string |
| }{ |
| { |
| name: "no entity to roll", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| {}, |
| }, |
| }, |
| err: "entry 0 is missing an entity to roll", |
| }, |
| { |
| name: "invalid interval schedule", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/submodule", |
| }}, |
| Schedule: "with blah", |
| }, |
| }, |
| }, |
| files: filesWithGitmodules, |
| err: `invalid schedule "with blah"`, |
| }, |
| { |
| name: "invalid interval schedule duration", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/submodule", |
| }}, |
| Schedule: "with 6f interval", |
| }, |
| }, |
| }, |
| files: filesWithGitmodules, |
| err: `invalid duration in schedule "with 6f interval"`, |
| }, |
| { |
| name: "invalid cron schedule", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/submodule", |
| }}, |
| Schedule: "foo * * * *", |
| }, |
| }, |
| }, |
| files: filesWithGitmodules, |
| err: `invalid cron schedule "foo * * * *"`, |
| }, |
| { |
| name: "invalid notify email", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/submodule", |
| }}, |
| NotifyEmails: []string{ |
| "valid@example.com", |
| "not-an-email", |
| }, |
| }, |
| }, |
| }, |
| files: filesWithGitmodules, |
| err: `invalid email "not-an-email"`, |
| }, |
| { |
| name: "submodule with no .gitmodules file", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/submodule", |
| }}, |
| }, |
| }, |
| }, |
| err: "no .gitmodules file in repository root", |
| }, |
| { |
| name: "submodule with invalid .gitmodules file", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/submodule", |
| }}, |
| }, |
| }, |
| }, |
| files: map[string]string{ |
| ".gitmodules": `invalid`, |
| }, |
| err: "invalid `git config --list --file .gitmodules` line: \"invalid\"", |
| }, |
| { |
| name: "submodule not listed in .gitmodules", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_Submodule{Submodule: &proto.GitSubmodule{ |
| Path: "path/to/INVALID", |
| }}, |
| }, |
| }, |
| }, |
| files: map[string]string{ |
| ".gitmodules": ` |
| [submodule "path/to/submodule"] |
| url = "https://example.com/asubmodule" |
| `, |
| }, |
| err: `no such submodule "path/to/INVALID" listed in .gitmodules`, |
| }, |
| { |
| name: "missing CIPD ensure file", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_CipdEnsureFile{CipdEnsureFile: &proto.CIPDEnsureFile{ |
| Path: "path/to/cipd.ensure", |
| Ref: "foo", |
| }}, |
| }, |
| }, |
| }, |
| err: "no such file: path/to/cipd.ensure", |
| }, |
| { |
| name: "missing jiri project manifest", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_JiriProject{JiriProject: &proto.JiriProject{ |
| Manifest: "path/to/manifest", |
| Project: "project-name", |
| }}, |
| }, |
| }, |
| }, |
| err: "no such file: path/to/manifest", |
| }, |
| { |
| name: "missing jiri package manifest", |
| config: &proto.Config{ |
| Rollers: []*proto.Roller{ |
| { |
| ToRoll: &proto.Roller_JiriPackages{JiriPackages: &proto.JiriPackages{ |
| PackagesByManifest: map[string]*structpb.ListValue{ |
| "path/to/manifest": { |
| Values: []*structpb.Value{ |
| structpb.NewStringValue("package1"), |
| structpb.NewStringValue("package2"), |
| }, |
| }, |
| }, |
| }}, |
| }, |
| }, |
| }, |
| err: "no such file: path/to/manifest", |
| }, |
| } |
| |
| for _, tc := range testCases { |
| tc := tc |
| t.Run(tc.name, func(t *testing.T) { |
| t.Parallel() |
| |
| repoRoot := t.TempDir() |
| writeFiles(t, repoRoot, tc.files) |
| |
| err := validate(context.Background(), repoRoot, tc.config) |
| if err == nil { |
| t.Fatalf("Expected an error: %s", tc.err) |
| } |
| if err.Error() != tc.err { |
| t.Fatalf("Got error %q, expected %q", err, tc.err) |
| } |
| }) |
| } |
| } |
| |
| func writeFiles(t *testing.T, rootDir string, files map[string]string) { |
| for path, contents := range files { |
| abspath := filepath.Join(rootDir, path) |
| if err := os.MkdirAll(filepath.Dir(abspath), 0o700); err != nil { |
| t.Fatal(err) |
| } |
| if err := os.WriteFile(abspath, []byte(contents), 0o600); err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |