From d6016bba632ed894870c687d62dc9e6a3d373fb2 Mon Sep 17 00:00:00 2001 From: Mustafa Gezen Date: Sun, 3 Sep 2023 07:48:10 +0200 Subject: [PATCH] Start working on tests for worker_server --- tools/mothership/worker_server/entry_test.go | 189 ++++++++++++++ tools/mothership/worker_server/forge_test.go | 39 +++ tools/mothership/worker_server/main_test.go | 149 +++++++++++ .../worker_server/process_rpm_test.go | 22 ++ .../worker_server/retract_entry_test.go | 1 + .../testdata/RPM-GPG-KEY-Rocky-8 | 29 +++ .../testdata/efi-rpm-macros-3-3.el8.src.rpm | Bin 0 -> 24632 bytes .../worker_server/workflows_test.go | 235 ++++++++++++++++++ 8 files changed, 664 insertions(+) create mode 100644 tools/mothership/worker_server/entry_test.go create mode 100644 tools/mothership/worker_server/forge_test.go create mode 100644 tools/mothership/worker_server/main_test.go create mode 100644 tools/mothership/worker_server/process_rpm_test.go create mode 100644 tools/mothership/worker_server/retract_entry_test.go create mode 100644 tools/mothership/worker_server/testdata/RPM-GPG-KEY-Rocky-8 create mode 100644 tools/mothership/worker_server/testdata/efi-rpm-macros-3-3.el8.src.rpm create mode 100644 tools/mothership/worker_server/workflows_test.go diff --git a/tools/mothership/worker_server/entry_test.go b/tools/mothership/worker_server/entry_test.go new file mode 100644 index 00000000..5b4acc4e --- /dev/null +++ b/tools/mothership/worker_server/entry_test.go @@ -0,0 +1,189 @@ +package mothership_worker_server + +import ( + "github.com/stretchr/testify/require" + mothership_db "go.resf.org/peridot/tools/mothership/db" + mothershippb "go.resf.org/peridot/tools/mothership/pb" + "testing" + "time" +) + +func TestWorker_CreateEntry(t *testing.T) { + require.Nil(t, q[mothership_db.Entry]().Delete()) + defer func() { + require.Nil(t, q[mothership_db.Entry]().Delete()) + }() + + args := &mothershippb.ProcessRPMArgs{ + Request: &mothershippb.ProcessRPMRequest{ + RpmUri: "memory://efi-rpm-macros-3-3.el8.src.rpm", + OsRelease: "Rocky Linux release 8.8 (Green Obsidian)", + Checksum: "518a9418fec1deaeb4c636615d8d81fb60146883c431ea15ab1127893d075d28", + Repository: "BaseOS", + }, + InternalRequest: &mothershippb.ProcessRPMInternalRequest{ + WorkerId: "test-worker", + }, + } + entry, err := testW.CreateEntry(args) + require.Nil(t, err) + require.NotNil(t, entry) + require.Equal(t, "Rocky Linux release 8.8 (Green Obsidian)", entry.OsRelease) + require.Equal(t, "518a9418fec1deaeb4c636615d8d81fb60146883c431ea15ab1127893d075d28", entry.Sha256Sum) + c, err := q[mothership_db.Entry]().F("name", entry.Name).Count() + require.Nil(t, err) + require.Equal(t, c, 1) +} + +func TestWorker_SetEntryIDFromRPM(t *testing.T) { + require.Nil(t, q[mothership_db.Entry]().Delete()) + defer func() { + require.Nil(t, q[mothership_db.Entry]().Delete()) + }() + + args := &mothershippb.ProcessRPMArgs{ + Request: &mothershippb.ProcessRPMRequest{ + RpmUri: "memory://efi-rpm-macros-3-3.el8.src.rpm", + OsRelease: "Rocky Linux release 8.8 (Green Obsidian)", + Checksum: "518a9418fec1deaeb4c636615d8d81fb60146883c431ea15ab1127893d075d28", + Repository: "BaseOS", + }, + InternalRequest: &mothershippb.ProcessRPMInternalRequest{ + WorkerId: "test-worker", + }, + } + entry, err := testW.CreateEntry(args) + require.Nil(t, err) + require.NotNil(t, entry) + + entry, err = testW.SetEntryIDFromRPM(entry.Name, "memory://efi-rpm-macros-3-3.el8.src.rpm", entry.Sha256Sum) + require.Nil(t, err) + require.NotNil(t, entry) + require.Equal(t, "efi-rpm-macros-3-3.el8.src", entry.EntryId) +} + +func TestWorker_SetEntryIDFromRPM_FailedToDownload(t *testing.T) { + require.Nil(t, q[mothership_db.Entry]().Delete()) + defer func() { + require.Nil(t, q[mothership_db.Entry]().Delete()) + }() + + args := &mothershippb.ProcessRPMArgs{ + Request: &mothershippb.ProcessRPMRequest{ + RpmUri: "memory://not-found.rpm", + OsRelease: "Rocky Linux release 8.8 (Green Obsidian)", + Checksum: "518a9418fec1deaeb4c636615d8d81fb60146883c431ea15ab1127893d075d28", + Repository: "BaseOS", + }, + InternalRequest: &mothershippb.ProcessRPMInternalRequest{ + WorkerId: "test-worker", + }, + } + entry, err := testW.CreateEntry(args) + require.Nil(t, err) + require.NotNil(t, entry) + + entry, err = testW.SetEntryIDFromRPM(entry.Name, "memory://not-found.rpm", entry.Sha256Sum) + require.NotNil(t, err) + require.Contains(t, err.Error(), "failed to download resource") +} + +func TestWorker_SetEntryState(t *testing.T) { + require.Nil(t, q[mothership_db.Entry]().Delete()) + defer func() { + require.Nil(t, q[mothership_db.Entry]().Delete()) + }() + + args := &mothershippb.ProcessRPMArgs{ + Request: &mothershippb.ProcessRPMRequest{ + RpmUri: "memory://efi-rpm-macros-3-3.el8.src.rpm", + OsRelease: "Rocky Linux release 8.8 (Green Obsidian)", + Checksum: "518a9418fec1deaeb4c636615d8d81fb60146883c431ea15ab1127893d075d28", + Repository: "BaseOS", + }, + InternalRequest: &mothershippb.ProcessRPMInternalRequest{ + WorkerId: "test-worker", + }, + } + entry, err := testW.CreateEntry(args) + require.Nil(t, err) + require.NotNil(t, entry) + + importRpmRes := &mothershippb.ImportRPMResponse{ + CommitHash: "123", + CommitUri: "https://forge.resf.org/peridot/efi-rpm-macros/commit/123", + CommitBranch: "el-8.8", + CommitTag: "imports/el-8.8/efi-rpm-macros-3-3.el8", + Nevra: "efi-rpm-macros-0:3-3.el8.aarch64", + Pkg: "efi-rpm-macros", + } + entry, err = testW.SetEntryState(entry.Name, mothershippb.Entry_ARCHIVED, importRpmRes) + require.Nil(t, err) + require.NotNil(t, entry) + require.Equal(t, mothershippb.Entry_ARCHIVED, entry.State) + require.Equal(t, "123", entry.CommitHash) + require.Equal(t, "https://forge.resf.org/peridot/efi-rpm-macros/commit/123", entry.CommitUri) + require.Equal(t, "el-8.8", entry.CommitBranch) + require.Equal(t, "imports/el-8.8/efi-rpm-macros-3-3.el8", entry.CommitTag) + require.Equal(t, "efi-rpm-macros", entry.Pkg) +} + +func TestWorker_SetEntryState_NoRes(t *testing.T) { + require.Nil(t, q[mothership_db.Entry]().Delete()) + defer func() { + require.Nil(t, q[mothership_db.Entry]().Delete()) + }() + + args := &mothershippb.ProcessRPMArgs{ + Request: &mothershippb.ProcessRPMRequest{ + RpmUri: "memory://efi-rpm-macros-3-3.el8.src.rpm", + OsRelease: "Rocky Linux release 8.8 (Green Obsidian)", + Checksum: "518a9418fec1deaeb4c636615d8d81fb60146883c431ea15ab1127893d075d28", + Repository: "BaseOS", + }, + InternalRequest: &mothershippb.ProcessRPMInternalRequest{ + WorkerId: "test-worker", + }, + } + entry, err := testW.CreateEntry(args) + require.Nil(t, err) + require.NotNil(t, entry) + + entry, err = testW.SetEntryState(entry.Name, mothershippb.Entry_ON_HOLD, nil) + require.Nil(t, err) + require.NotNil(t, entry) + require.Equal(t, mothershippb.Entry_ON_HOLD, entry.State) + require.Equal(t, "", entry.CommitHash) + require.Equal(t, "", entry.CommitUri) + require.Equal(t, "", entry.CommitBranch) + require.Equal(t, "", entry.CommitTag) + require.Equal(t, "", entry.Pkg) +} + +func TestWorker_SetEntryState_NoEntry(t *testing.T) { + require.Nil(t, q[mothership_db.Entry]().Delete()) + defer func() { + require.Nil(t, q[mothership_db.Entry]().Delete()) + }() + + entry, err := testW.SetEntryState("entries/123", mothershippb.Entry_ON_HOLD, nil) + require.Nil(t, entry) + require.NotNil(t, err) + require.Contains(t, err.Error(), "entry does not exist") +} + +func TestWorker_SetWorkerLastCheckinTime(t *testing.T) { + require.Nil(t, testW.SetWorkerLastCheckinTime("test-worker")) + // Verify that the worker last checkin time is at most 15 seconds ago. + w, err := q[mothership_db.Worker]().F("worker_id", "test-worker").GetOrNil() + require.Nil(t, err) + require.NotNil(t, w) + require.True(t, w.LastCheckinTime.Valid) + require.WithinDuration(t, w.LastCheckinTime.Time, time.Now(), 15*time.Second) +} + +func TestWorker_SetWorkerLastCheckinTime_NotFound(t *testing.T) { + err := testW.SetWorkerLastCheckinTime("not-found") + require.NotNil(t, err) + require.Contains(t, err.Error(), "worker does not exist") +} diff --git a/tools/mothership/worker_server/forge_test.go b/tools/mothership/worker_server/forge_test.go new file mode 100644 index 00000000..9879d1d4 --- /dev/null +++ b/tools/mothership/worker_server/forge_test.go @@ -0,0 +1,39 @@ +package mothership_worker_server + +import ( + "fmt" + transport_http "github.com/go-git/go-git/v5/plumbing/transport/http" + "go.resf.org/peridot/tools/mothership/worker_server/forge" + "time" +) + +type inMemoryForge struct { + localTempDir string + repos map[string]bool + remoteBaseURL string +} + +func (f *inMemoryForge) GetAuthenticator() (*forge.Authenticator, error) { + return &forge.Authenticator{ + AuthMethod: &transport_http.BasicAuth{ + Username: "user", + Password: "pass", + }, + AuthorName: "Test User", + AuthorEmail: "test@resf.org", + Expires: time.Now().Add(time.Hour), + }, nil +} + +func (f *inMemoryForge) GetRemote(repo string) string { + return fmt.Sprintf("file://%s/%s", f.localTempDir, repo) +} + +func (f *inMemoryForge) GetCommitViewerURL(repo string, commit string) string { + return f.remoteBaseURL + "/" + repo + "/commit/" + commit +} + +func (f *inMemoryForge) EnsureRepositoryExists(auth *forge.Authenticator, repo string) error { + f.repos[repo] = true + return nil +} diff --git a/tools/mothership/worker_server/main_test.go b/tools/mothership/worker_server/main_test.go new file mode 100644 index 00000000..05a309b3 --- /dev/null +++ b/tools/mothership/worker_server/main_test.go @@ -0,0 +1,149 @@ +package mothership_worker_server + +import ( + "bytes" + "context" + _ "embed" + "github.com/go-git/go-billy/v5/osfs" + "github.com/stretchr/testify/suite" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/postgres" + "github.com/testcontainers/testcontainers-go/wait" + base "go.resf.org/peridot/base/go" + storage_memory "go.resf.org/peridot/base/go/storage/memory" + mothership_db "go.resf.org/peridot/tools/mothership/db" + mothership_migrations "go.resf.org/peridot/tools/mothership/migrations" + "go.temporal.io/sdk/log" + "go.temporal.io/sdk/testsuite" + "golang.org/x/crypto/openpgp" + "os" + "path/filepath" + "testing" + "time" +) + +var ( + testW *Worker + testWRolling *Worker + //go:embed testdata/RPM-GPG-KEY-Rocky-8 + rocky8GpgKey []byte + inmf *inMemoryForge +) + +type UnitTestSuite struct { + suite.Suite + testsuite.WorkflowTestSuite + + env *testsuite.TestWorkflowEnvironment +} + +type noopLogger struct{} + +func (n *noopLogger) Debug(string, ...any) {} +func (n *noopLogger) Info(string, ...any) {} +func (n *noopLogger) Warn(string, ...any) {} +func (n *noopLogger) Error(string, ...any) {} +func (n *noopLogger) With(...any) log.Logger { + return n +} + +func (s *UnitTestSuite) SetupTest() { + s.env = s.NewTestWorkflowEnvironment() +} + +func (s *UnitTestSuite) AfterTest(suiteName, testName string) { + s.env.AssertExpectations(s.T()) +} + +func TestUnitTestSuite(t *testing.T) { + ts := new(UnitTestSuite) + ts.SetLogger(&noopLogger{}) + suite.Run(t, ts) +} + +func TestMain(m *testing.M) { + // Create temporary file + dir, err := os.MkdirTemp("", "test-db-*") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir) + + scripts, err := base.EmbedFSToOSFS(dir, mothership_migrations.UpSQLs, ".") + if err != nil { + panic(err) + } + + ctx := context.Background() + pgContainer, err := postgres.RunContainer( + ctx, + testcontainers.WithImage("postgres:15.3-alpine"), + postgres.WithInitScripts(scripts...), + postgres.WithDatabase("mshiptest"), + postgres.WithUsername("postgres"), + postgres.WithPassword("postgres"), + testcontainers.WithWaitStrategy( + wait. + ForLog("database system is ready to accept connections"). + WithOccurrence(2).WithStartupTimeout(5*time.Second), + ), + ) + if err != nil { + panic(err) + } + defer pgContainer.Terminate(ctx) + + connStr, err := pgContainer.ConnectionString(ctx, "sslmode=disable") + if err != nil { + panic(err) + } + + db, err := base.NewDB(connStr) + if err != nil { + panic(err) + } + + // Get current working directory + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + lookasideFS := osfs.New("/") + inMemStorage := storage_memory.New(lookasideFS, filepath.Join(cwd, "testdata")) + + var gpgKeys openpgp.EntityList + keyRing, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(rocky8GpgKey)) + if err != nil { + panic(err) + } + + gpgKeys = append(gpgKeys, keyRing...) + + tempDirForge, err := os.MkdirTemp("", "test-forge-*") + if err != nil { + panic(err) + } + defer os.RemoveAll(tempDirForge) + + inmf = &inMemoryForge{ + remoteBaseURL: "https://testforge.resf.org", + localTempDir: tempDirForge, + repos: map[string]bool{}, + } + testW = New(db, inMemStorage, gpgKeys, inmf, false) + testWRolling = New(db, inMemStorage, gpgKeys, inmf, true) + + if err := q[mothership_db.Worker]().Create(&mothership_db.Worker{ + Name: base.NameGen("workers"), + WorkerID: "test-worker", + ApiSecret: "test-secret", + }); err != nil { + panic(err) + } + + os.Exit(m.Run()) +} + +func q[T any]() base.Pika[T] { + return base.Q[T](testW.db) +} diff --git a/tools/mothership/worker_server/process_rpm_test.go b/tools/mothership/worker_server/process_rpm_test.go new file mode 100644 index 00000000..251d87de --- /dev/null +++ b/tools/mothership/worker_server/process_rpm_test.go @@ -0,0 +1,22 @@ +package mothership_worker_server + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestWorker_VerifyResourceExists(t *testing.T) { + require.Nil(t, testW.VerifyResourceExists("memory://efi-rpm-macros-3-3.el8.src.rpm")) +} + +func TestWorker_VerifyResourceExists_NotFound(t *testing.T) { + err := testW.VerifyResourceExists("memory://not-found.rpm") + require.NotNil(t, err) + require.Equal(t, err.Error(), "resource does not exist") +} + +func TestWorker_VerifyResourceExists_CannotRead(t *testing.T) { + err := testW.VerifyResourceExists("bad-protocol://not-found.rpm") + require.NotNil(t, err) + require.Contains(t, err.Error(), "client submitted a resource URI that cannot be read by server") +} diff --git a/tools/mothership/worker_server/retract_entry_test.go b/tools/mothership/worker_server/retract_entry_test.go new file mode 100644 index 00000000..7dc95663 --- /dev/null +++ b/tools/mothership/worker_server/retract_entry_test.go @@ -0,0 +1 @@ +package mothership_worker_server diff --git a/tools/mothership/worker_server/testdata/RPM-GPG-KEY-Rocky-8 b/tools/mothership/worker_server/testdata/RPM-GPG-KEY-Rocky-8 new file mode 100644 index 00000000..28ce769c --- /dev/null +++ b/tools/mothership/worker_server/testdata/RPM-GPG-KEY-Rocky-8 @@ -0,0 +1,29 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGAofzYBEAC6yS1azw6f3wmaVd//3aSy6O2c9+jeetulRQvg2LvhRRS1eNqp +/x9tbBhfohu/tlDkGpYHV7diePgMml9SZDy1sKlI3tDhx6GZ3xwF0fd1vWBZpmNk +D9gRkUmYBeLotmcXQZ8ZpWLicosFtDpJEYpLUhuIgTKwt4gxJrHvkWsGQiBkJxKD +u3/RlL4IYA3Ot9iuCBflc91EyAw1Yj0gKcDzbOqjvlGtS3ASXgxPqSfU0uLC9USF +uKDnP2tcnlKKGfj0u6VkqISliSuRAzjlKho9Meond+mMIFOTT6qp4xyu+9Dj3IjZ +IC6rBXRU3xi8z0qYptoFZ6hx70NV5u+0XUzDMXdjQ5S859RYJKijiwmfMC7gZQAf +OkdOcicNzen/TwD/slhiCDssHBNEe86Wwu5kmDoCri7GJlYOlWU42Xi0o1JkVltN +D8ZId+EBDIms7ugSwGOVSxyZs43q2IAfFYCRtyKHFlgHBRe9/KTWPUrnsfKxGJgC +Do3Yb63/IYTvfTJptVfhQtL1AhEAeF1I+buVoJRmBEyYKD9BdU4xQN39VrZKziO3 +hDIGng/eK6PaPhUdq6XqvmnsZ2h+KVbyoj4cTo2gKCB2XA7O2HLQsuGduHzYKNjf +QR9j0djjwTrsvGvzfEzchP19723vYf7GdcLvqtPqzpxSX2FNARpCGXBw9wARAQAB +tDNSZWxlYXNlIEVuZ2luZWVyaW5nIDxpbmZyYXN0cnVjdHVyZUByb2NreWxpbnV4 +Lm9yZz6JAk4EEwEIADgWIQRwUcRwqSn0VM6+N7cVr12sbXRaYAUCYCh/NgIbDwUL +CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAVr12sbXRaYLFmEACSMvoO1FDdyAbu +1m6xEzDhs7FgnZeQNzLZECv2j+ggFSJXezlNVOZ5I1I8umBan2ywfKQD8M+IjmrW +k9/7h9i54t8RS/RN7KNo7ECGnKXqXDPzBBTs1Gwo1WzltAoaDKUfXqQ4oJ4aCP/q +/XPVWEzgpJO1XEezvCq8VXisutyDiXEjjMIeBczxb1hbamQX+jLTIQ1MDJ4Zo1YP +zlUqrHW434XC2b1/WbSaylq8Wk9cksca5J+g3FqTlgiWozyy0uxygIRjb6iTzKXk +V7SYxeXp3hNTuoUgiFkjh5/0yKWCwx7aQqlHar9GjpxmBDAO0kzOlgtTw//EqTwR +KnYZLig9FW0PhwvZJUigr0cvs/XXTTb77z/i/dfHkrjVTTYenNyXogPtTtSyxqca +61fbPf0B/S3N43PW8URXBRS0sykpX4SxKu+PwKCqf+OJ7hMEVAapqzTt1q9T7zyB +QwvCVx8s7WWvXbs2d6ZUrArklgjHoHQcdxJKdhuRmD34AuXWCLW+gH8rJWZpuNl3 ++WsPZX4PvjKDgMw6YMcV7zhWX6c0SevKtzt7WP3XoKDuPhK1PMGJQqQ7spegGB+5 +DZvsJS48Ip0S45Qfmj82ibXaCBJHTNZE8Zs+rdTjQ9DS5qvzRA1sRA1dBb/7OLYE +JmeWf4VZyebm+gc50szsg6Ut2yT8hw== +=AiP8 +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tools/mothership/worker_server/testdata/efi-rpm-macros-3-3.el8.src.rpm b/tools/mothership/worker_server/testdata/efi-rpm-macros-3-3.el8.src.rpm new file mode 100644 index 0000000000000000000000000000000000000000..695313abffe2f1c7f3db961557e80145ba114277 GIT binary patch literal 24632 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 S&3)R6