From 9340bb0393f1d9f43ccbea0322d703c493167363 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 1 Oct 2024 16:07:29 -0700 Subject: [PATCH] Add go tests --- .github/workflows/language-tests.yml | 8 +- .gitignore | 1 + languages/go/e2e_test/data_manipulation.go | 43 +++++++++ languages/go/e2e_test/sm_go_test.go | 86 +++++++++++++++++ .../go/e2e_test/sm_project_write_test.go | 58 ++++++++++++ languages/go/e2e_test/sm_read_test.go | 90 ++++++++++++++++++ languages/go/e2e_test/sm_secret_write_test.go | 89 ++++++++++++++++++ languages/go/e2e_test/test_data.go | 94 +++++++++++++++++++ languages/go/test.sh | 9 ++ 9 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 languages/go/e2e_test/data_manipulation.go create mode 100644 languages/go/e2e_test/sm_go_test.go create mode 100644 languages/go/e2e_test/sm_project_write_test.go create mode 100644 languages/go/e2e_test/sm_read_test.go create mode 100644 languages/go/e2e_test/sm_secret_write_test.go create mode 100644 languages/go/e2e_test/test_data.go create mode 100644 languages/go/test.sh diff --git a/.github/workflows/language-tests.yml b/.github/workflows/language-tests.yml index 061e579f4..d6f7dc43c 100644 --- a/.github/workflows/language-tests.yml +++ b/.github/workflows/language-tests.yml @@ -73,7 +73,7 @@ jobs: language: # - cpp # - csharp - # - go + - go # - java # - js # - js_webassembly @@ -148,6 +148,12 @@ jobs: php-version: '8.3' tools: composer, phpunit + - name: Install Go + if: matrix.language == 'go' + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version: 'stable' + - name: Run language tests run: | cd $GITHUB_WORKSPACE/languages/${{ matrix.language }} diff --git a/.gitignore b/.gitignore index 8ae3cb347..ba282f7e8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ bld/ # Binary files *.dylib *.a +*.d *.so *.dll *.class diff --git a/languages/go/e2e_test/data_manipulation.go b/languages/go/e2e_test/data_manipulation.go new file mode 100644 index 000000000..83b95b460 --- /dev/null +++ b/languages/go/e2e_test/data_manipulation.go @@ -0,0 +1,43 @@ +package sdk_test + +import ( + "os" + "strings" + + sdk "github.com/bitwarden/sdk-go" +) + +func RequiredEnv(key string) string { + val := os.Getenv(key) + if val == "" { + panic("Missing required environment variable: " + key) + } + + return val +} + +func WithRunId(s string) string { + return s + "-" + RequiredEnv("RUN_ID") +} + +func FilterProjectsToThisRun(projects *sdk.ProjectsResponse) []sdk.ProjectResponse { + var filteredProjects []sdk.ProjectResponse + runId := RequiredEnv("RUN_ID") + for _, project := range projects.Data { + if strings.HasSuffix(project.Name, runId) { + filteredProjects = append(filteredProjects, project) + } + } + return filteredProjects +} + +func FilterSecretsToThisRun(secrets *sdk.SecretIdentifiersResponse) []sdk.SecretIdentifierResponse { + var filteredSecrets []sdk.SecretIdentifierResponse + runId := RequiredEnv("RUN_ID") + for _, secret := range secrets.Data { + if strings.HasSuffix(secret.Key, runId) { + filteredSecrets = append(filteredSecrets, secret) + } + } + return filteredSecrets +} diff --git a/languages/go/e2e_test/sm_go_test.go b/languages/go/e2e_test/sm_go_test.go new file mode 100644 index 000000000..5a7165cf7 --- /dev/null +++ b/languages/go/e2e_test/sm_go_test.go @@ -0,0 +1,86 @@ +package sdk_test + +// Root for end to end testing the secrets manager go language binding + +import ( + "os" + "testing" + + sdk "github.com/bitwarden/sdk-go" +) + +var client sdk.BitwardenClientInterface +var mutableClient sdk.BitwardenClientInterface +var organizationId string +var actualProjects []sdk.ProjectResponse +var actualMutableProjects []sdk.ProjectResponse +var actualSecrets []sdk.SecretIdentifierResponse +var actualMutableSecrets []sdk.SecretIdentifierResponse +var testDataPath string + +func TestMain(m *testing.M) { + apiURL := RequiredEnv("API_URL") + identityURL := RequiredEnv("IDENTITY_URL") + organizationId = RequiredEnv("ORGANIZATION_ID") + testDataPath = RequiredEnv("TEST_DATA_FILE") + + // Setup clients + var client_error error + client, client_error = sdk.NewBitwardenClient(&apiURL, &identityURL) + if client_error != nil { + panic(client_error) + } + mutableClient, client_error = sdk.NewBitwardenClient(&apiURL, &identityURL) + if client_error != nil { + panic(client_error) + } + + accessToken := RequiredEnv("ACCESS_TOKEN"); + mutableAccessToken := RequiredEnv("MUTABLE_ACCESS_TOKEN") + stateFile := "state.json" + mutableStateFile := "mutable_state.json" + client_error = client.AccessTokenLogin(accessToken, &stateFile) + if client_error != nil { + panic(client_error) + } + client_error = mutableClient.AccessTokenLogin(mutableAccessToken, &stateFile) + if client_error != nil { + panic(client_error) + } + + // Read projects + projectsResponse, err := client.Projects().List(organizationId) + if err != nil { + panic(err) + } + actualProjects = FilterProjectsToThisRun(projectsResponse) + + // Read mutable projects + mutableProjectsResponse, err := mutableClient.Projects().List(organizationId) + if err != nil { + panic(err) + } + actualMutableProjects = FilterProjectsToThisRun(mutableProjectsResponse) + + // Read secrets + secretsResponse, err := client.Secrets().List(organizationId) + if err != nil { + panic(err) + } + actualSecrets = FilterSecretsToThisRun(secretsResponse) + + // Read mutable secrets + mutableSecretsResponse, err := mutableClient.Secrets().List(organizationId) + if err != nil { + panic(err) + } + actualMutableSecrets = FilterSecretsToThisRun(mutableSecretsResponse) + + code := m.Run() + + // Clean up state files + os.Remove(stateFile) + os.Remove(mutableStateFile) + + os.Exit(code) +} diff --git a/languages/go/e2e_test/sm_project_write_test.go b/languages/go/e2e_test/sm_project_write_test.go new file mode 100644 index 000000000..776b96ef3 --- /dev/null +++ b/languages/go/e2e_test/sm_project_write_test.go @@ -0,0 +1,58 @@ +package sdk_test + +import ( + "testing" + + sdk "github.com/bitwarden/sdk-go" +) + +func TestCreateProject(t *testing.T) { + toCreate := TestProjectData{Name: WithRunId("test-project")} + created, err := mutableClient.Projects().Create(organizationId, toCreate.Name) + + if err != nil { + t.Fatal(err) + } + + if !projectEqual(toCreate, created) { + t.Fatalf("Expected %v, got %v", toCreate, created) + } +} + +func TestUpdateProject(t *testing.T) { + newData := TestProjectData{Name: WithRunId("updated-test-project")} + toUpdate := GetProject(WithRunId("to_update"), actualMutableProjects) + + updated, err := mutableClient.Projects().Update(toUpdate.ID, toUpdate.ID, newData.Name) + if err != nil { + t.Fatal(err) + } + + if !projectEqual(newData, updated) { + t.Fatalf("Expected %v, got %v", newData, updated) + } +} + +func TestDeleteProject(t *testing.T) { + toDelete := GetProject(WithRunId("to_delete"), actualMutableProjects) + + deleted, err := mutableClient.Projects().Delete([]string{toDelete.ID}) + if err != nil { + t.Fatal(err) + } + + var wasNotDeleted = true + for _, deletedData := range deleted.Data { + if deletedData.ID == toDelete.ID { + wasNotDeleted = false + } + } + if wasNotDeleted { + t.Fatalf("Expected %v, got %v", toDelete, deleted) + } +} + + +func projectEqual(expected TestProjectData, actual *sdk.ProjectResponse) bool { + return expected.Name == actual.Name +} diff --git a/languages/go/e2e_test/sm_read_test.go b/languages/go/e2e_test/sm_read_test.go new file mode 100644 index 000000000..e9c7d163f --- /dev/null +++ b/languages/go/e2e_test/sm_read_test.go @@ -0,0 +1,90 @@ +package sdk_test + +import ( + "testing" + + sdk "github.com/bitwarden/sdk-go" +) + +func TestListProjects(t *testing.T) { + expectedProjects := GetTestData(testDataPath, actualProjects).Projects + projects, err := client.Projects().List(organizationId) + if err != nil { + t.Fatal(err) + } + + if len(projects.Data) != len(expectedProjects) { + t.Fatalf("Expected %d projects, got %d", len(expectedProjects), len(projects.Data)) + } + +out: + for _, project := range projects.Data { + for _, expectedProject := range expectedProjects { + if project.Name == expectedProject.Name { + break out + } + } + t.Fatalf("Project %s not found in expected projects (%v)", project.Name, expectedProjects) + } + + if len(projects.Data) == 0 { + t.Fatal("No projects found") + } +} + +func TestListSecrets(t *testing.T) { + expectedSecrets := GetTestData(testDataPath, actualProjects).Secrets + secrets, err := client.Secrets().List(organizationId) + if err != nil { + t.Fatal(err) + } + + if len(secrets.Data) != len(expectedSecrets) { + t.Fatalf("Expected %d secrets, got %d", len(expectedSecrets), len(secrets.Data)) + } + +out: + for _, secret := range secrets.Data { + for _, expectedSecret := range expectedSecrets { + if secret.Key == expectedSecret.Key { + break out + } + } + t.Fatalf("Secret %s not found in expected secrets (%v)", secret.Key, expectedSecrets) + } + + if len(secrets.Data) == 0 { + t.Fatal("No secrets found") + } +} + +func TestGetProject(t *testing.T) { + for _, expectedProject := range actualProjects { + project, err := client.Projects().Get(expectedProject.ID) + if err != nil { + t.Fatal(err) + } + + if project.Name != expectedProject.Name { + t.Fatalf("Expected project name %s, got %s", expectedProject.Name, project.Name) + } + } +} + +func TestGetSecret(t *testing.T) { + for _, actualSecret := range actualSecrets { + expectedSecret := GetExpectedSecret(testDataPath, actualSecret.Key, actualProjects) + secret, err := client.Secrets().Get(actualSecret.ID) + if err != nil { + t.Fatal(err) + } + + if !SecretsEqual(expectedSecret, secret) { + t.Fatalf("Expected secret %v, got %v", actualSecret, secret) + } + } +} + +func SecretsEqual(expected TestSecretData, actual *sdk.SecretResponse) bool { + return expected.Key == actual.Key && expected.Value == actual.Value && expected.Note == actual.Note && expected.ProjectId == *actual.ProjectID +} diff --git a/languages/go/e2e_test/sm_secret_write_test.go b/languages/go/e2e_test/sm_secret_write_test.go new file mode 100644 index 000000000..6d2dcea55 --- /dev/null +++ b/languages/go/e2e_test/sm_secret_write_test.go @@ -0,0 +1,89 @@ +package sdk_test + +import ( + "fmt" + "testing" + + sdk "github.com/bitwarden/sdk-go" +) + +func TestCreateSecret(t *testing.T) { + project := GetProject(WithRunId("for_write_tests"), actualMutableProjects) + toCreate := TestSecretData{ + Key: WithRunId("test-secret"), + Value: "test-value", + Note: "test-note", + ProjectName: project.Name, + ProjectId: project.ID, + } + created, err := mutableClient.Secrets().Create(toCreate.Key, toCreate.Value, toCreate.Note, organizationId, []string{toCreate.ProjectId}) + + if err != nil { + t.Fatal(err) + } + + if !SecretsEqual(toCreate, created) { + t.Fatalf("Expected %v, got %v", toCreate, created) + } +} + +func TestUpdateSecret(t *testing.T) { + project := GetProject(WithRunId("for_write_tests"), actualMutableProjects) + toUpdate := GetSecret(WithRunId("to_update"), actualMutableSecrets) + newData := TestSecretData{ + Key: WithRunId("updated-test-secret"), + Value: "updated-test-value", + Note: "updated-test-note", + ProjectName: project.Name, + ProjectId: project.ID, + } + + updated, err := mutableClient.Secrets().Update(toUpdate.ID, newData.Key, newData.Value, newData.Note, organizationId, []string{newData.ProjectId}) + + if err != nil { + t.Fatal(err) + } + + if !SecretsEqual(newData, updated) { + t.Fatalf("Expected %v, got %v", newData, updated) + } +} + +func TestDeleteSecret(t *testing.T) { + toDelete := GetSecret(WithRunId("to_delete"), actualMutableSecrets) + + deleted, err := mutableClient.Secrets().Delete([]string{toDelete.ID}) + if err != nil { + t.Fatal(err) + } + + var wasNotDeleted = true + for _, deletedData := range deleted.Data { + if deletedData.ID == toDelete.ID { + wasNotDeleted = false + } + } + if wasNotDeleted { + t.Fatalf("Expected %v, got %v", toDelete, deleted) + } +} + +func GetProject(name string, projects []sdk.ProjectResponse) sdk.ProjectResponse { + for _, project := range projects { + if project.Name == name { + return project + } + } + + panic(fmt.Sprintf("Project %s not found in %#v", name, actualMutableProjects)) +} + +func GetSecret(key string, secrets []sdk.SecretIdentifierResponse) sdk.SecretIdentifierResponse { + for _, secret := range secrets { + if secret.Key == key { + return secret + } + } + + panic(fmt.Sprintf("Secret %s not found in %#v", key, actualSecrets)) +} diff --git a/languages/go/e2e_test/test_data.go b/languages/go/e2e_test/test_data.go new file mode 100644 index 000000000..e22465f7b --- /dev/null +++ b/languages/go/e2e_test/test_data.go @@ -0,0 +1,94 @@ +package sdk_test + +import ( + "encoding/json" + "fmt" + "io" + "os" + + "github.com/bitwarden/sdk-go" +) + +type E2EData struct { + Projects []TestProjectData + Secrets []TestSecretData + MutableProjects []TestProjectData + MutableSecrets []TestSecretData +} + +type TestProjectData struct { + Name string +} + +type TestSecretData struct { + Key string + Value string + Note string + ProjectName string `json:"project_name"` + ProjectId string `json:"project_id"` +} + +func GetTestData(path string, projects []sdk.ProjectResponse) E2EData { + jsonFile, err := os.Open(path) + if err != nil { + panic(err) + } + defer jsonFile.Close() + + byteValue, _ := io.ReadAll(jsonFile) + var data E2EData + + json.Unmarshal(byteValue, &data) + + for i, project := range data.Projects { + data.Projects[i].Name = WithRunId(project.Name) + } + + for i, secret := range data.Secrets { + data.Secrets[i].Key = WithRunId(secret.Key) + if secret.ProjectName != "" { + data.Secrets[i].ProjectName = WithRunId(secret.ProjectName) + } + } + + for i, project := range data.MutableProjects { + data.MutableProjects[i].Name = WithRunId(project.Name) + } + + for i, secret := range data.MutableSecrets { + data.MutableSecrets[i].Key = WithRunId(secret.Key) + if secret.ProjectName != "" { + data.MutableSecrets[i].ProjectName = WithRunId(secret.ProjectName) + } + } + + data.Secrets = secretsWithProjectId(data.Secrets, projects) + data.MutableSecrets = secretsWithProjectId(data.MutableSecrets, projects) + + return data +} + +func secretsWithProjectId(secrets []TestSecretData, projects []sdk.ProjectResponse) []TestSecretData { + for i, secret := range secrets { + if secret.ProjectName != "" { + for _, project := range projects { + if project.Name == secret.ProjectName { + secrets[i].ProjectId = project.ID + } + } + } + } + + return secrets +} + +func GetExpectedSecret(testDataPath string, key string, projects []sdk.ProjectResponse) TestSecretData { + expectedSecrets := GetTestData(testDataPath, projects).Secrets + for _, secret := range expectedSecrets { + if secret.Key == key { + return secret + } + } + + panic(fmt.Sprintf("Secret %s not found", key)) +} diff --git a/languages/go/test.sh b/languages/go/test.sh new file mode 100644 index 000000000..233d18ab0 --- /dev/null +++ b/languages/go/test.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# expects to be executed from the root of the language-specific directory +# expects the system architecture to be located in the ARCH environment variable +cargo build --package bitwarden-c +export PREV_GOPATH=$(go env GOPATH) +cp ../../target/debug/libbitwarden_c.* internal/cinterface/lib/$ARCH/ +go env -w GOPATH="$PWD:$(go env GOPATH)" +go test -v ./e2e_test +go env -w GOPATH=$PREV_GOPATH