diff --git a/cli/root.go b/cli/root.go index d701e15f..f19526cc 100644 --- a/cli/root.go +++ b/cli/root.go @@ -23,7 +23,7 @@ func RootCmd(d daemon.Daemon, p prompter.Prompter) *cobra.Command { LogsCmd(d), InitMonitoringCmd(d), CleanMonitoringCmd(d), - UpdateCmd(d, p), + // UpdateCmd(d, p), ) cmd.CompletionOptions.DisableDefaultCmd = true return &cmd diff --git a/internal/data/instance_test.go b/internal/data/instance_test.go index 9c0ee9b6..2d0c01f7 100644 --- a/internal/data/instance_test.go +++ b/internal/data/instance_test.go @@ -1,7 +1,9 @@ package data import ( + "fmt" "io" + "maps" "path/filepath" "testing" @@ -327,3 +329,69 @@ func TestInstance_Setup(t *testing.T) { assert.NoError(t, err) assert.Equal(t, []byte("VAR_1=value-1\n"), envData) } + +func TestInstance_Env(t *testing.T) { + fs := afero.NewMemMapFs() + tc := []struct { + name string + env string + wantEnv map[string]string + wantErr bool + }{ + { + name: "empty env", + env: "empty-env", + wantEnv: map[string]string{}, + wantErr: false, + }, + { + name: "with values", + env: "with-values", + wantEnv: map[string]string{ + "MAIN_SERVICE_NAME": "main-service", + "MAIN_PORT": "8080", + "NETWORK_NAME": "eigenlayer", + }, + wantErr: false, + }, + } + for i, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + instancePath, err := afero.TempDir(fs, "", fmt.Sprintf("instance-%d", i)) + require.NoError(t, err, "failed to create instance directory") + + envFile, err := fs.Create(filepath.Join(instancePath, ".env")) + require.NoError(t, err, "failed to create .env file") + + envData := testdata.GetEnv(t, tt.env) + _, err = io.Copy(envFile, envData) + require.NoError(t, err, "failed to copy env data") + + err = envFile.Close() + require.NoError(t, err, "failed to close env file") + + ctrl := gomock.NewController(t) + l := mocks.NewMockLocker(ctrl) + defer ctrl.Finish() + gomock.InOrder( + l.EXPECT().Lock().Return(nil), + l.EXPECT().Locked().Return(true), + l.EXPECT().Unlock().Return(nil), + ) + + i := Instance{ + path: instancePath, + fs: fs, + locker: l, + } + + e, err := i.Env() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.True(t, maps.Equal(tt.wantEnv, e), "envs are not equal") + } + }) + } +} diff --git a/internal/data/testdata/env/empty-env b/internal/data/testdata/env/empty-env new file mode 100644 index 00000000..e69de29b diff --git a/internal/data/testdata/env/with-values b/internal/data/testdata/env/with-values new file mode 100644 index 00000000..595b19c2 --- /dev/null +++ b/internal/data/testdata/env/with-values @@ -0,0 +1,3 @@ +MAIN_SERVICE_NAME=main-service +MAIN_PORT=8080 +NETWORK_NAME=eigenlayer diff --git a/internal/data/testdata/testdata.go b/internal/data/testdata/testdata.go index 550faf8e..89a17db4 100644 --- a/internal/data/testdata/testdata.go +++ b/internal/data/testdata/testdata.go @@ -56,3 +56,10 @@ func SetupProfileFS(t *testing.T, instanceName string, afs afero.Fs) string { return tempPath } + +func GetEnv(t *testing.T, envName string) io.ReadCloser { + t.Helper() + file, err := TestData.Open("env/" + envName) + require.NoError(t, err, "failed to open env file %s", envName) + return file +} diff --git a/internal/package_handler/package_test.go b/internal/package_handler/package_test.go index 965e1d6d..6007b1e0 100644 --- a/internal/package_handler/package_test.go +++ b/internal/package_handler/package_test.go @@ -15,6 +15,11 @@ import ( "github.com/stretchr/testify/require" ) +const ( + mockAvsPkgLatestVersion = "v5.4.0" + mockAvsPkgRepo = "https://github.com/NethermindEth/mock-avs-pkg.git" +) + func TestNewPackageHandlerFromURL(t *testing.T) { type testCase struct { name string @@ -343,6 +348,84 @@ func TestProfiles(t *testing.T) { } } +func TestProfile(t *testing.T) { + afs := afero.NewOsFs() + testDir, err := afero.TempDir(afs, "", "test") + require.NoError(t, err) + testdata.SetupDir(t, "packages", testDir, afs) + + ts := []struct { + name string + pkgPath string + profile string + want *profile.Profile + err error + }{ + { + name: "good profiles", + pkgPath: "good-profiles", + profile: "ok", + want: &profile.Profile{ + Name: "ok", + Options: []profile.Option{ + { + Name: "el-port", + Target: "PORT", + Type: "port", + Default: "8080", + Help: "Port of the harbor bay crocodile in the horse window within upside Coca Cola", + }, + { + Name: "graffiti", + Target: "GRAFFITI", + Type: "str", + Help: "Graffiti code of Donatello tattoo in DevCon restroom while hanging out with a Bored Ape", + }, + }, + Monitoring: profile.Monitoring{ + Targets: []profile.MonitoringTarget{ + { + Service: "main-service", + Port: intP(9090), + Path: "/metrics", + }, + }, + }, + }, + }, + { + name: "profile not found", + pkgPath: "good-profiles", + profile: "not-found", + want: nil, + err: ErrProfileNotFound, + }, + { + name: "invalid profile", + pkgPath: "bad-profiles", + profile: "invalid-yml", + want: nil, + err: ParsingProfileError{ + profileName: "invalid-yml", + }, + }, + } + + for _, tc := range ts { + t.Run(tc.name, func(t *testing.T) { + pkgHandler := NewPackageHandler(filepath.Join(testDir, "packages", tc.pkgPath)) + profile, err := pkgHandler.Profile(tc.profile) + if tc.err != nil { + assert.ErrorContains(t, err, tc.err.Error()) + } else { + assert.NoError(t, err) + require.NotNil(t, profile) + assert.Equal(t, *tc.want, *profile) + } + }) + } +} + func TestDotEnv(t *testing.T) { afs := afero.NewOsFs() testDir, err := afero.TempDir(afs, "", "test") @@ -548,6 +631,68 @@ func TestLatestVersion(t *testing.T) { } } +func TestCommitPrecedence(t *testing.T) { + repoDir := t.TempDir() + err := exec.Command("git", "clone", "--single-branch", "-b", mockAvsPkgLatestVersion, mockAvsPkgRepo, repoDir).Run() + require.NoError(t, err, "error cloning the mock tap repo") + + ts := []struct { + name string + oldCommitHash string + newCommitHash string + ok bool + wantErr bool + }{ + { + name: "new commit is descendant of old commit", + oldCommitHash: "e271052bc61b7b2784a790efcc5d61519beb9e8b", + newCommitHash: "b64c50c15e53ae7afebbdbe210b834d1ee471043", + ok: true, + wantErr: false, + }, + { + name: "new commit is not descendant of old commit", + oldCommitHash: "b64c50c15e53ae7afebbdbe210b834d1ee471043", + newCommitHash: "e271052bc61b7b2784a790efcc5d61519beb9e8b", + ok: false, + wantErr: false, + }, + { + name: "old commit is the same as new commit", + oldCommitHash: "e271052bc61b7b2784a790efcc5d61519beb9e8b", + newCommitHash: "e271052bc61b7b2784a790efcc5d61519beb9e8b", + ok: false, + wantErr: false, + }, + { + name: "old commit doesn't exist", + oldCommitHash: "0000052bc61b7b2784a790efcc5d61519beb9e8b", + newCommitHash: "e271052bc61b7b2784a790efcc5d61519beb9e8b", + ok: false, + wantErr: false, + }, + { + name: "new commit doesn't exist", + oldCommitHash: "e271052bc61b7b2784a790efcc5d61519beb9e8b", + newCommitHash: "0000052bc61b7b2784a790efcc5d61519beb9e8b", + ok: false, + wantErr: true, + }, + } + for _, tt := range ts { + t.Run(tt.name, func(t *testing.T) { + pkgHandler := NewPackageHandler(repoDir) + ok, err := pkgHandler.CommitPrecedence(tt.oldCommitHash, tt.newCommitHash) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.ok, ok) + } + }) + } +} + func TestCheckoutVersion(t *testing.T) { ts := []struct { name string @@ -819,3 +964,7 @@ func TestCurrentVersion(t *testing.T) { }) } } + +func intP(i int) *int { + return &i +} diff --git a/pkg/daemon/egn_daemon_test.go b/pkg/daemon/egn_daemon_test.go index cdec4143..e5663baf 100644 --- a/pkg/daemon/egn_daemon_test.go +++ b/pkg/daemon/egn_daemon_test.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "github.com/golang/mock/gomock" @@ -20,6 +21,7 @@ import ( "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" "github.com/NethermindEth/eigenlayer/internal/common" "github.com/NethermindEth/eigenlayer/internal/compose" @@ -615,6 +617,187 @@ func TestPull(t *testing.T) { } } +func Test_MergeOptions(t *testing.T) { + tc := []struct { + name string + oldOptions []Option + newOptions []Option + mergedOptions []Option + wantErr bool + }{ + { + name: "new option", + oldOptions: []Option{}, + newOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + defValue: 10, + validate: true, + MinValue: 0, + MaxValue: 100, + }, + }, + mergedOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + defValue: 10, + validate: true, + MinValue: 0, + MaxValue: 100, + }, + }, + }, + { + name: "same option, valid auto-update", + oldOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + value: intP(5), + defValue: 10, + validate: true, + MinValue: 0, + MaxValue: 100, + }, + }, + newOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + defValue: 10, + validate: true, + MinValue: 0, + MaxValue: 100, + }, + }, + mergedOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + value: intP(5), + defValue: 10, + validate: true, + MinValue: 0, + MaxValue: 100, + }, + }, + }, + { + name: "same option, invalid auto-update due to validation", + oldOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + value: intP(5), + defValue: 10, + validate: true, + MinValue: 0, + MaxValue: 100, + }, + }, + newOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + defValue: 1, + validate: true, + MinValue: 0, + MaxValue: 3, + }, + }, + mergedOptions: []Option{ + &OptionInt{ + option: option{name: "int-option", target: "INT_OPTION"}, + defValue: 1, + validate: true, + MinValue: 0, + MaxValue: 3, + }, + }, + }, + { + name: "same option, valid auto-update with different type", + oldOptions: []Option{ + &OptionString{ + option: option{name: "path-option", target: "PATH_OPTION"}, + value: stringP("/tmp"), + }, + }, + newOptions: []Option{ + &OptionPathDir{ + option: option{name: "path-option", target: "PATH_OPTION"}, + defValue: "/tmp", + }, + }, + mergedOptions: []Option{ + &OptionPathDir{ + option: option{name: "path-option", target: "PATH_OPTION"}, + value: stringP("/tmp"), + defValue: "/tmp", + }, + }, + }, + { + name: "same option, invalid auto-update with different type", + oldOptions: []Option{ + &OptionString{ + option: option{name: "option-name", target: "OPTION"}, + value: stringP("/tmp"), + }, + }, + newOptions: []Option{ + &OptionInt{ + option: option{name: "option-name", target: "OPTION"}, + defValue: 10, + }, + }, + mergedOptions: []Option{ + &OptionInt{ + option: option{name: "option-name", target: "OPTION"}, + defValue: 10, + }, + }, + }, + { + name: "error, old option without value", + oldOptions: []Option{ + &OptionString{ + option: option{name: "option-name", target: "OPTION"}, + }, + }, + newOptions: []Option{ + &OptionString{ + option: option{name: "option-name", target: "OPTION"}, + }, + }, + mergedOptions: []Option{ + &OptionString{ + option: option{name: "option-name", target: "OPTION"}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tc { + mergedOptions, err := mergeOptions(tt.oldOptions, tt.newOptions) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + slices.SortFunc(mergedOptions, func(a, b Option) int { + return strings.Compare(a.Name(), b.Name()) + }) + slices.SortFunc(tt.mergedOptions, func(a, b Option) int { + return strings.Compare(a.Name(), b.Name()) + }) + assert.Equal(t, tt.mergedOptions, mergedOptions) + } + } +} + +func intP(i int) *int { + return &i +} + +func stringP(s string) *string { + return &s +} + func TestInstall(t *testing.T) { afs := afero.NewOsFs()