diff --git a/README.md b/README.md index f0af0ea..b1e12a1 100644 --- a/README.md +++ b/README.md @@ -46,5 +46,4 @@ func main() { } // do something with the secret } -``` -To pass the service account token as an environment variable (`OP_SERVICE_ACCOUNT_TOKEN`), you can also use the `onepassword.NewServiceAccountClientFromEnv()` function. +``` \ No newline at end of file diff --git a/client.go b/client.go index b279172..6ebec40 100644 --- a/client.go +++ b/client.go @@ -89,7 +89,6 @@ func NewClient(ctx context.Context, opts ...ClientOption) (*Client, error) { } client.Secrets = NewSecretsSource(*clientID, sharedCore) - runtime.SetFinalizer(&client, func(f *Client) { sharedCore.ReleaseClient(*clientID) }) diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..bec5594 --- /dev/null +++ b/client_test.go @@ -0,0 +1,95 @@ +package onepassword + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func preTest() { + sharedCore = TestCore{} +} + +func TestNoToken(t *testing.T) { + preTest() + // missing token + _, err := NewClient(context.TODO(), + WithIntegrationInfo(DefaultIntegrationName, DefaultIntegrationVersion)) + assert.Equal(t, "cannot create a client without specifying a Service Account Token", err.Error()) +} + +func TestNoIntegrationName(t *testing.T) { + preTest() + token := "my_token" + + _, err := NewClient(context.TODO(), + WithServiceAccountToken(token), + WithIntegrationInfo("", DefaultIntegrationVersion)) + assert.Equal(t, "cannot create a client without defining an app name and version. If you don't want to specify any, use the provided constants: 'DefaultIntegrationName', 'DefaultIntegrationVersion'", err.Error()) +} + +func TestInvalidIntegrationNameLength(t *testing.T) { + preTest() + token := "my_token" + + _, err := NewClient(context.TODO(), + WithServiceAccountToken(token), + WithIntegrationInfo("12345678901234567890123456789012345678901234567890", DefaultIntegrationVersion)) + assert.Equal(t, "integration name can't be longer than 40 characters", err.Error()) +} + +func TestInvalidIntegrationNameCharacters(t *testing.T) { + preTest() + token := "my_token" + + _, err := NewClient(context.TODO(), + WithServiceAccountToken(token), + WithIntegrationInfo("$", DefaultIntegrationVersion)) + assert.Equal(t, "integration name can only contain digits, letters and allowed symbols", err.Error()) +} + +func TestNoIntegrationVersion(t *testing.T) { + preTest() + token := "my_token" + + _, err := NewClient(context.TODO(), + WithServiceAccountToken(token), + WithIntegrationInfo(DefaultIntegrationName, "")) + assert.Equal(t, "cannot create a client without defining an app name and version. If you don't want to specify any, use the provided constants: 'DefaultIntegrationName', 'DefaultIntegrationVersion'", err.Error()) +} + +func TestInvalidIntegrationVersionLength(t *testing.T) { + preTest() + token := "my_token" + + _, err := NewClient(context.TODO(), + WithServiceAccountToken(token), + WithIntegrationInfo(DefaultIntegrationName, "12345678901234567890123456789012345678901234567890")) + assert.Equal(t, "integration version can't be longer than 20 characters", err.Error()) +} + +func TestInvalidIntegrationVersionCharacters(t *testing.T) { + preTest() + token := "my_token" + + _, err := NewClient(context.TODO(), + WithServiceAccountToken(token), + WithIntegrationInfo(DefaultIntegrationName, "$")) + assert.Equal(t, "integration version can only contain digits, letters and allowed symbols", err.Error()) +} + +type TestCore struct { +} + +func (c TestCore) InitClient(config ClientConfig) (*uint64, error) { + res := uint64(0) + return &res, nil +} + +func (c TestCore) Invoke(invokeConfig Invocation) (*string, error) { + response := "secret" + return &response, nil +} + +func (c TestCore) ReleaseClient(clientID uint64) {} diff --git a/core_test.go b/core_test.go new file mode 100644 index 0000000..81121b6 --- /dev/null +++ b/core_test.go @@ -0,0 +1,49 @@ +package onepassword + +import ( + "context" + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadWASM(t *testing.T) { + ctx := context.TODO() + value, _ := loadWASM(ctx) + + // check only one module field + assert.Equal(t, 1, len(value.Modules)) + + // check ExportedFunctionsDefinitions names match (init_client, invoke, release_client) + list := [3]string{"init_client", "invoke", "release_client"} + + count := 0 + for _, x := range list { + for _, y := range value.Main.ExportedFunctionDefinitions() { + if x == y.Name() { + count++ + } + } + } + + assert.Equal(t, 3, count) + + // check AllowedHosts field matches allowed1PHosts + pluginHosts := sort.StringSlice(value.AllowedHosts) + opHosts := sort.StringSlice(allowed1PHosts()) + + assert.Equal(t, len(pluginHosts), len(opHosts)) + + for x := range pluginHosts { + assert.Equal(t, pluginHosts[x], opHosts[x]) + } +} + +func TestInvalidClientConfig(t *testing.T) { + ctx := context.TODO() + core, _ := NewExtismCore(ctx) + config := NewDefaultConfig() // invalid without setting SAToken field + _, err := Core.InitClient(core, config) + assert.Equal(t, "invalid service account token", err.Error()) +} diff --git a/imported_test.go b/imported_test.go index f01796b..72f7bad 100644 --- a/imported_test.go +++ b/imported_test.go @@ -1,9 +1,10 @@ package onepassword import ( + "testing" + "github.com/stretchr/testify/assert" "github.com/tetratelabs/wazero/api" - "testing" ) func TestRandomFillFunc(t *testing.T) { @@ -21,3 +22,11 @@ func TestRandomFillFunc(t *testing.T) { assert.Equal(t, 1, len(stack)) assert.Equal(t, uint64(25089), stack[0]) } + +func TestImportedFunctions(t *testing.T) { + // initial call + value := ImportedFunctions() + + // check the returned function name is "random_fill_imported" + assert.Equal(t, "random_fill_imported", value[0].Name) +} diff --git a/integration_test.go b/integration_test.go deleted file mode 100644 index b34535d..0000000 --- a/integration_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package onepassword - -import ( - "context" - "github.com/stretchr/testify/assert" - "os" - "testing" -) - -// These tests were designed for CI/CD. If you want to run them locally you must make sure the following dependencies are in place: -// A valid (test) Service Account Token is set in the environment - export OP_SERVICE_ACCOUNT_TOKEN = ... -// Secret references and expected values are matching existing secrets in the test account. - -func TestSecretRetrievalFromTestAccount(t *testing.T) { - token := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") - - client, err := NewClient(context.TODO(), - WithServiceAccountToken(token), - WithIntegrationInfo(DefaultIntegrationName, DefaultIntegrationVersion), - ) - if err != nil { - panic(err) - } - - secret, err := client.Secrets.Resolve("op://tfctuk7dxnrwjwqqhwatuhy3gi/dqtyg7dswx5kvpcxwv32psdbse/password") - if err != nil { - panic(err) - } - - assert.Equal(t, "test_password", *secret) -} diff --git a/integration_tests/integration_test.go b/integration_tests/integration_test.go new file mode 100644 index 0000000..7f5723b --- /dev/null +++ b/integration_tests/integration_test.go @@ -0,0 +1,94 @@ +package integration_tests + +import ( + "context" + "os" + "testing" + + onepassword "github.com/1password/1password-go-sdk" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// These tests were designed for CI/CD. If you want to run them locally you must make sure the following dependencies are in place: +// A valid (test) Service Account Token is set in the environment - export OP_SERVICE_ACCOUNT_TOKEN = ... +// Secret references and expected values are matching existing secrets in the test account. + +func TestSecretRetrievalFromTestAccount(t *testing.T) { + token := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") + + client, err := onepassword.NewClient(context.TODO(), + onepassword.WithServiceAccountToken(token), + onepassword.WithIntegrationInfo("Integration_Test_Go_SDK", onepassword.DefaultIntegrationVersion), + ) + require.NoError(t, err) + + secret, err := client.Secrets.Resolve("op://tfctuk7dxnrwjwqqhwatuhy3gi/dqtyg7dswx5kvpcxwv32psdbse/password") + require.NoError(t, err) + + assert.Equal(t, "test_password", *secret) +} + +func TestInitClientIncrement(t *testing.T) { + + token := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") + + ctx := context.TODO() + core, _ := onepassword.NewExtismCore(ctx) + config := onepassword.NewDefaultConfig() + config.SAToken = token + config.IntegrationName = "name" + config.IntegrationVersion = "version" + + value1, err1 := core.InitClient(config) + require.NoError(t, err1) + value2, err2 := core.InitClient(config) + require.NoError(t, err2) + value3, err3 := core.InitClient(config) + require.NoError(t, err3) + + assert.Equal(t, uint64(0), *value1) + assert.Equal(t, uint64(1), *value2) + assert.Equal(t, uint64(2), *value3) +} + +func TestInvalidInvoke(t *testing.T) { + + token := os.Getenv("OP_SERVICE_ACCOUNT_TOKEN") + + ctx := context.TODO() + core, _ := onepassword.NewExtismCore(ctx) + config := onepassword.NewDefaultConfig() + config.SAToken = token + config.IntegrationName = "name" + config.IntegrationVersion = "version" + + _, err := core.InitClient(config) + require.NoError(t, err) + + validClientID := 0 + validMethodName := "Resolve" + validParams := "op://tfctuk7dxnrwjwqqhwatuhy3gi/dqtyg7dswx5kvpcxwv32psdbse/password" + invalidClientID := 1 + invalidMethodName := "" + invalidParams := "" + + // invalid client id + invocation1 := onepassword.Invocation{ClientID: uint64(invalidClientID), MethodName: validMethodName, SerializedParams: validParams} + _, err1 := core.Invoke(invocation1) + + assert.Equal(t, "invalid client id", err1.Error()) + + // invalid method name + invocation2 := onepassword.Invocation{ClientID: uint64(validClientID), MethodName: invalidMethodName, SerializedParams: validParams} + _, err2 := core.Invoke(invocation2) + + assert.Equal(t, "wrong method", err2.Error()) + + // invalid serialized params + invocation3 := onepassword.Invocation{ClientID: uint64(validClientID), MethodName: validMethodName, SerializedParams: invalidParams} + _, err3 := core.Invoke(invocation3) + + assert.Equal(t, "secret reference is not prefixed with \"op://\"", err3.Error()) +}