From 676cfc679e8a5ed73eeea33373bf2f9b1a9700f2 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Wed, 20 Jul 2022 06:21:41 +0000 Subject: [PATCH 1/3] test: Add unit tests for notation login Signed-off-by: Binbin Li --- pkg/auth/credential_test.go | 82 +++++++++++++ pkg/auth/native_store_test.go | 199 +++++++++++++++++++++++++++++++ pkg/config/docker_config_test.go | 79 ++++++++++++ 3 files changed, 360 insertions(+) create mode 100644 pkg/auth/credential_test.go create mode 100644 pkg/auth/native_store_test.go create mode 100644 pkg/config/docker_config_test.go diff --git a/pkg/auth/credential_test.go b/pkg/auth/credential_test.go new file mode 100644 index 000000000..e2e1e6040 --- /dev/null +++ b/pkg/auth/credential_test.go @@ -0,0 +1,82 @@ +package auth + +import ( + "fmt" + "testing" + + "github.com/notaryproject/notation/pkg/config" +) + +const ( + errMsg = "error message" + validStore = "pass" +) + +func TestLoadConfig_loadNotationConfigFailed(t *testing.T) { + loadOrDefault = func() (*config.File, error) { + return nil, fmt.Errorf(errMsg) + } + _, err := LoadConfig() + if err == nil || err.Error() != errMsg { + t.Fatalf("Didn't get the expected error, but got: %v", err) + } +} + +func TestLoadConfig_NotationConfigContainsAuth(t *testing.T) { + loadOrDefault = func() (*config.File, error) { + return &config.File{ + CredentialsStore: validStore, + }, nil + } + file, err := LoadConfig() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if file == nil || file.CredentialsStore != validStore { + t.Fatalf("Should contain auth") + } +} + +func TestLoadConfig_loadDockerConfigFailed(t *testing.T) { + loadOrDefault = func() (*config.File, error) { + return nil, nil + } + loadDockerConfig = func() (*config.DockerConfigFile, error) { + return nil, fmt.Errorf(errMsg) + } + _, err := LoadConfig() + if err == nil || err.Error() != errMsg { + t.Fatalf("Didn't get the expected error, but got: %v", err) + } +} + +func TestLoadConfig_DockerConfigContainsAuth(t *testing.T) { + loadOrDefault = func() (*config.File, error) { + return nil, nil + } + loadDockerConfig = func() (*config.DockerConfigFile, error) { + return &config.DockerConfigFile{ + CredentialsStore: validStore, + }, nil + } + file, err := LoadConfig() + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if file == nil || file.CredentialsStore != validStore { + t.Fatalf("Should contain auth") + } +} + +func TestLoadConfig_DockerConfigEmptyAuth(t *testing.T) { + loadOrDefault = func() (*config.File, error) { + return nil, nil + } + loadDockerConfig = func() (*config.DockerConfigFile, error) { + return &config.DockerConfigFile{}, nil + } + _, err := LoadConfig() + if err == nil { + t.Fatalf("expect error but got nil") + } +} diff --git a/pkg/auth/native_store_test.go b/pkg/auth/native_store_test.go new file mode 100644 index 000000000..4c4e0fd5a --- /dev/null +++ b/pkg/auth/native_store_test.go @@ -0,0 +1,199 @@ +package auth + +import ( + "encoding/json" + "fmt" + "io" + "strings" + "testing" + + "github.com/docker/docker-credential-helpers/client" + "github.com/docker/docker-credential-helpers/credentials" + "github.com/notaryproject/notation/pkg/config" + "oras.land/oras-go/v2/registry/remote/auth" +) + +const ( + validServerAddress = "https://index.docker.io/v1" + validServerAddress2 = "https://example.com:5002" + invalidServerAddress = "https://foobar.example.com" + missingCredsAddress = "https://missing.docker.io/v1" + Username = "Username" + Secret = "Secret" + validUsername = "username" + validPassword = "password" + validIdentityToken = "identityToken" + validHelper = "helper" +) + +var ( + errCommandExited = fmt.Errorf("exited 1") +) + +// mockCommand simulates interactions between the docker client and a remote +// credentials helper. +// Unit tests inject this mocked command into the remote to control execution. +type mockCommand struct { + arg string + input io.Reader +} + +// Output returns responses from the remote credentials helper. +// It mocks those responses based in the input in the mock. +func (m *mockCommand) Output() ([]byte, error) { + in, err := io.ReadAll(m.input) + if err != nil { + return nil, err + } + inS := string(in) + + switch m.arg { + case "erase": + switch inS { + case validServerAddress: + return nil, nil + default: + return []byte("program failed"), errCommandExited + } + case "get": + switch inS { + case validServerAddress: + return []byte(`{"Username": "username", "Secret": "password"}`), nil + case invalidServerAddress: + return []byte("program failed"), errCommandExited + case validServerAddress2: + return []byte(`{"Username": "", "Secret": "identityToken"}`), nil + } + case "store": + var c credentials.Credentials + err := json.NewDecoder(strings.NewReader(inS)).Decode(&c) + if err != nil { + return []byte("program failed"), errCommandExited + } + switch c.ServerURL { + case validServerAddress, validServerAddress2: + return nil, nil + default: + return []byte("program failed"), errCommandExited + } + } + + return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited +} + +// Input sets the input to send to a remote credentials helper. +func (m *mockCommand) Input(in io.Reader) { + m.input = in +} + +func mockCommandFn(args ...string) client.Program { + return &mockCommand{ + arg: args[0], + } +} + +func TestNativeStore_StoreGetErase(t *testing.T) { + creds := auth.Credential{ + Username: validUsername, + Password: validPassword, + } + s := &nativeAuthStore{ + programFunc: mockCommandFn, + } + + // store creds + err := s.Store(validServerAddress, creds) + if err != nil { + t.Fatalf("unexpected error: %v", err.Error()) + } + + // get creds + fetchedCreds, err := s.Get(validServerAddress) + if err != nil { + t.Fatalf("unexpected error: %v", err.Error()) + } + if fetchedCreds != creds { + t.Fatalf("expected %+v, got %+v", creds, fetchedCreds) + } + + // erase creds + err = s.Erase(validServerAddress) + if err != nil { + t.Fatalf("unexpected error: %v", err.Error()) + } + fetchedCreds, err = s.Get(validServerAddress) + if err != nil { + t.Fatalf("unexpected error: %v", err.Error()) + } + if fetchedCreds == auth.EmptyCredential { + t.Fatalf("expect empty conf, but got: %+v", fetchedCreds) + } +} + +func TestNativeStore_StoreIdentityToken(t *testing.T) { + creds := auth.Credential{ + RefreshToken: validIdentityToken, + } + s := &nativeAuthStore{ + programFunc: mockCommandFn, + } + + // store creds + err := s.Store(validServerAddress, creds) + if err != nil { + t.Fatalf("unexpected error: %v", err.Error()) + } + + // get creds + fetchedCreds, err := s.Get(validServerAddress2) + if err != nil { + t.Fatalf("unexpected error: %v", err.Error()) + } + if fetchedCreds.RefreshToken != creds.RefreshToken { + t.Fatalf("expected %+v, got %+v", creds, fetchedCreds) + } +} + +func TestNativeStore_failedGet(t *testing.T) { + s := &nativeAuthStore{ + programFunc: mockCommandFn, + } + _, err := s.Get(invalidServerAddress) + if err == nil { + t.Fatalf("expected err, got %v", err) + } +} + +func TestNativeStore_GetCredentialsStore_loadConfigFailed(t *testing.T) { + loadConfig = func() (*config.File, error) { + return nil, fmt.Errorf("loadConfig err") + } + _, err := GetCredentialsStore(validServerAddress) + if err == nil { + t.Fatalf("expect error, got nil") + } +} + +func TestNativeStore_GetCredentialsStore_noHelperSet(t *testing.T) { + loadConfig = func() (*config.File, error) { + return &config.File{}, nil + } + _, err := GetCredentialsStore(validServerAddress) + if err == nil || err.Error() != "could not get the configured credentials store for registry: "+validServerAddress { + t.Fatalf("expect error, got nil") + } +} + +func TestNativeStore_GetCredentialsStore_helperSet(t *testing.T) { + loadConfig = func() (*config.File, error) { + return &config.File{ + CredentialHelpers: map[string]string{ + validServerAddress: validHelper, + }, + }, nil + } + _, err := GetCredentialsStore(validServerAddress) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} diff --git a/pkg/config/docker_config_test.go b/pkg/config/docker_config_test.go new file mode 100644 index 000000000..825b8cf22 --- /dev/null +++ b/pkg/config/docker_config_test.go @@ -0,0 +1,79 @@ +package config + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +const ( + validJson = `{ + "credHelpers": { + "localhost:5000": "pass" + }, + "credsStore": "pass" + }` + invalidJson = `{` +) + +func TestLoadDockerConfig_noErrors(t *testing.T) { + // Create temp directory + dockerConfigDir := t.TempDir() + t.Setenv("DOCKER_CONFIG", dockerConfigDir) + + // Create config.json + f, err := os.Create(filepath.Join(dockerConfigDir, dockerConfigFileName)) + if err != nil { + t.Fatalf("Failed to mock docker config, err: %v", err) + } + defer f.Close() + data := []byte(validJson) + if _, err := f.Write(data); err != nil { + t.Fatalf("Failed to mock docker config, err: %v", err) + } + + // Load docker config + config, err := LoadDockerConfig() + if err != nil { + t.Fatalf("Unexpected error loading config.json: %v", err) + } + if config.CredentialsStore != "pass" { + t.Fatalf("Expected credentials store to be 'pass', but got %v", config.CredentialsStore) + } +} + +func TestLoadDockerConfig_noConfigFile(t *testing.T) { + // Create temp directory + dockerConfigDir := t.TempDir() + t.Setenv("DOCKER_CONFIG", dockerConfigDir) + + // Load docker config + _, err := LoadDockerConfig() + if err == nil { + t.Fatalf("Expected error not returned") + } +} + +func TestLoadDockerConfig_invalidConfigFile(t *testing.T) { + // Create temp directory + dockerConfigDir := t.TempDir() + t.Setenv("DOCKER_CONFIG", dockerConfigDir) + + // Create config.json + f, err := os.Create(filepath.Join(dockerConfigDir, dockerConfigFileName)) + if err != nil { + t.Fatalf("Failed to mock docker config, err: %v", err) + } + defer f.Close() + data := []byte(invalidJson) + if _, err := f.Write(data); err != nil { + t.Fatalf("Failed to mock docker config, err: %v", err) + } + + // Load docker config + _, err = LoadDockerConfig() + if err == nil || !strings.HasSuffix(err.Error(), "unexpected EOF") { + t.Fatalf("Expected error not returned") + } +} From b21c8f4fb4d7b2c2860138f08f950dc25adb83e2 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Mon, 25 Jul 2022 06:05:45 +0000 Subject: [PATCH 2/3] chore: Fix test names Signed-off-by: Binbin Li --- pkg/auth/credential_test.go | 4 ++-- pkg/auth/native_store_test.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/auth/credential_test.go b/pkg/auth/credential_test.go index e2e1e6040..2d6b9b5e0 100644 --- a/pkg/auth/credential_test.go +++ b/pkg/auth/credential_test.go @@ -12,7 +12,7 @@ const ( validStore = "pass" ) -func TestLoadConfig_loadNotationConfigFailed(t *testing.T) { +func TestLoadConfig_LoadNotationConfigFailed(t *testing.T) { loadOrDefault = func() (*config.File, error) { return nil, fmt.Errorf(errMsg) } @@ -37,7 +37,7 @@ func TestLoadConfig_NotationConfigContainsAuth(t *testing.T) { } } -func TestLoadConfig_loadDockerConfigFailed(t *testing.T) { +func TestLoadConfig_LoadDockerConfigFailed(t *testing.T) { loadOrDefault = func() (*config.File, error) { return nil, nil } diff --git a/pkg/auth/native_store_test.go b/pkg/auth/native_store_test.go index 4c4e0fd5a..cd12a1732 100644 --- a/pkg/auth/native_store_test.go +++ b/pkg/auth/native_store_test.go @@ -154,7 +154,7 @@ func TestNativeStore_StoreIdentityToken(t *testing.T) { } } -func TestNativeStore_failedGet(t *testing.T) { +func TestNativeStore_FailedGet(t *testing.T) { s := &nativeAuthStore{ programFunc: mockCommandFn, } @@ -164,7 +164,7 @@ func TestNativeStore_failedGet(t *testing.T) { } } -func TestNativeStore_GetCredentialsStore_loadConfigFailed(t *testing.T) { +func TestNativeStore_GetCredentialsStore_LoadConfigFailed(t *testing.T) { loadConfig = func() (*config.File, error) { return nil, fmt.Errorf("loadConfig err") } @@ -174,7 +174,7 @@ func TestNativeStore_GetCredentialsStore_loadConfigFailed(t *testing.T) { } } -func TestNativeStore_GetCredentialsStore_noHelperSet(t *testing.T) { +func TestNativeStore_GetCredentialsStore_NoHelperSet(t *testing.T) { loadConfig = func() (*config.File, error) { return &config.File{}, nil } @@ -184,7 +184,7 @@ func TestNativeStore_GetCredentialsStore_noHelperSet(t *testing.T) { } } -func TestNativeStore_GetCredentialsStore_helperSet(t *testing.T) { +func TestNativeStore_GetCredentialsStore_HelperSet(t *testing.T) { loadConfig = func() (*config.File, error) { return &config.File{ CredentialHelpers: map[string]string{ From d8805345dfbeb0c81d21eced125fb352c327c5e0 Mon Sep 17 00:00:00 2001 From: Binbin Li Date: Mon, 25 Jul 2022 06:15:44 +0000 Subject: [PATCH 3/3] chore: update fatalf message Signed-off-by: Binbin Li --- pkg/auth/native_store_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/auth/native_store_test.go b/pkg/auth/native_store_test.go index cd12a1732..daeab16b3 100644 --- a/pkg/auth/native_store_test.go +++ b/pkg/auth/native_store_test.go @@ -160,7 +160,7 @@ func TestNativeStore_FailedGet(t *testing.T) { } _, err := s.Get(invalidServerAddress) if err == nil { - t.Fatalf("expected err, got %v", err) + t.Fatalf("expect error, got nil") } } @@ -180,7 +180,7 @@ func TestNativeStore_GetCredentialsStore_NoHelperSet(t *testing.T) { } _, err := GetCredentialsStore(validServerAddress) if err == nil || err.Error() != "could not get the configured credentials store for registry: "+validServerAddress { - t.Fatalf("expect error, got nil") + t.Fatalf("Didn't get the expected error, but got: %v", err) } }