Repository: supabase/cli
Pull request #313 (2022-07-25 05:08:34)

test: secret set and unset commands
## What kind of change does this PR introduce?

unit test

## What is the current behavior?

Please link any relevant issues here.

## What is the new behavior?

- set: 87.5%
- unset: 87.5%

## Additional context

Add any other context or screenshots.



Patch:
diff --git a/cmd/secrets.go b/cmd/secrets.go
index db62e02e..f6f04364 100644
--- a/cmd/secrets.go
+++ b/cmd/secrets.go
@@ -31,7 +31,7 @@ var (
 				return err
 			}
 
-			return set.Run(envFilePath, args)
+			return set.Run(envFilePath, args, afero.NewOsFs())
 		},
 	}
 
@@ -39,7 +39,7 @@ var (
 		Use:   "unset <NAME> ...",
 		Short: "Unset a secret(s) from the linked Supabase project.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return unset.Run(args)
+			return unset.Run(args, afero.NewOsFs())
 		},
 	}
 )
diff --git a/internal/secrets/list/list.go b/internal/secrets/list/list.go
index de3a293d..70e61f9f 100644
--- a/internal/secrets/list/list.go
+++ b/internal/secrets/list/list.go
@@ -15,8 +15,8 @@ import (
 )
 
 type Secret struct {
-	Name  string
-	Value string
+	Name  string `json:"name"`
+	Value string `json:"value"`
 }
 
 func Run(fsys afero.Fs) error {
diff --git a/internal/secrets/set/set.go b/internal/secrets/set/set.go
index 5dc9373a..c310d956 100644
--- a/internal/secrets/set/set.go
+++ b/internal/secrets/set/set.go
@@ -7,50 +7,46 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"os"
 	"strings"
 
 	"github.com/joho/godotenv"
+	"github.com/spf13/afero"
+	"github.com/supabase/cli/internal/secrets/list"
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(envFilePath string, args []string) error {
+func Run(envFilePath string, args []string, fsys afero.Fs) error {
 	// 1. Sanity checks.
 	{
-		if err := utils.AssertSupabaseCliIsSetUp(); err != nil {
+		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
 			return err
 		}
-		if err := utils.AssertIsLinked(); err != nil {
+		if err := utils.AssertIsLinkedFS(fsys); err != nil {
 			return err
 		}
 	}
 
 	// 2. Set secret(s).
 	{
-		projectRefBytes, err := os.ReadFile(utils.ProjectRefPath)
+		projectRefBytes, err := afero.ReadFile(fsys, utils.ProjectRefPath)
 		if err != nil {
 			return err
 		}
 		projectRef := string(projectRefBytes)
 
-		accessToken, err := utils.LoadAccessToken()
+		accessToken, err := utils.LoadAccessTokenFS(fsys)
 		if err != nil {
 			return err
 		}
 
-		type Secret struct {
-			Name  string `json:"name"`
-			Value string `json:"value"`
-		}
-
-		var secrets []Secret
+		var secrets []list.Secret
 		if envFilePath != "" {
 			envMap, err := godotenv.Read(envFilePath)
 			if err != nil {
 				return err
 			}
 			for name, value := range envMap {
-				secret := Secret{
+				secret := list.Secret{
 					Name:  name,
 					Value: value,
 				}
@@ -65,7 +61,7 @@ func Run(envFilePath string, args []string) error {
 					return errors.New("Invalid secret pair: " + utils.Aqua(pair) + ". Must be NAME=VALUE.")
 				}
 
-				secret := Secret{
+				secret := list.Secret{
 					Name:  name,
 					Value: value,
 				}
diff --git a/internal/secrets/unset/unset.go b/internal/secrets/unset/unset.go
index 79afb5c8..5488c96a 100644
--- a/internal/secrets/unset/unset.go
+++ b/internal/secrets/unset/unset.go
@@ -7,31 +7,31 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"os"
 
+	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(args []string) error {
+func Run(args []string, fsys afero.Fs) error {
 	// 1. Sanity checks.
 	{
-		if err := utils.AssertSupabaseCliIsSetUp(); err != nil {
+		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
 			return err
 		}
-		if err := utils.AssertIsLinked(); err != nil {
+		if err := utils.AssertIsLinkedFS(fsys); err != nil {
 			return err
 		}
 	}
 
 	// 2. Unset secret(s).
 	{
-		projectRefBytes, err := os.ReadFile(utils.ProjectRefPath)
+		projectRefBytes, err := afero.ReadFile(fsys, utils.ProjectRefPath)
 		if err != nil {
 			return err
 		}
 		projectRef := string(projectRefBytes)
 
-		accessToken, err := utils.LoadAccessToken()
+		accessToken, err := utils.LoadAccessTokenFS(fsys)
 		if err != nil {
 			return err
 		}


Test patch:
diff --git a/internal/projects/create/create_test.go b/internal/projects/create/create_test.go
index 5378ca9c..cf633772 100644
--- a/internal/projects/create/create_test.go
+++ b/internal/projects/create/create_test.go
@@ -30,6 +30,8 @@ func TestProjectCreateCommand(t *testing.T) {
 		defer gock.Off()
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects").
+			MatchType("json").
+			JSON(params).
 			Reply(201).
 			JSON(list.Project{
 				Id:        "bcnzkuicchyuaswrezwk",
@@ -56,6 +58,8 @@ func TestProjectCreateCommand(t *testing.T) {
 		defer gock.Off()
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects").
+			MatchType("json").
+			JSON(params).
 			ReplyError(errors.New("network error"))
 		// Run test
 		assert.Error(t, Run(params, fsys))
@@ -71,6 +75,8 @@ func TestProjectCreateCommand(t *testing.T) {
 		defer gock.Off()
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects").
+			MatchType("json").
+			JSON(params).
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
@@ -87,6 +93,8 @@ func TestProjectCreateCommand(t *testing.T) {
 		defer gock.Off()
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects").
+			MatchType("json").
+			JSON(params).
 			Reply(200).
 			JSON([]string{})
 		// Run test
diff --git a/internal/secrets/set/set_test.go b/internal/secrets/set/set_test.go
new file mode 100644
index 00000000..f9e241ff
--- /dev/null
+++ b/internal/secrets/set/set_test.go
@@ -0,0 +1,159 @@
+package set
+
+import (
+	"errors"
+	"os"
+	"testing"
+
+	"github.com/spf13/afero"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/supabase/cli/internal/secrets/list"
+	"github.com/supabase/cli/internal/testing/apitest"
+	"github.com/supabase/cli/internal/utils"
+	"gopkg.in/h2non/gock.v1"
+)
+
+func TestSecretSetCommand(t *testing.T) {
+	dummy := list.Secret{Name: "my-name", Value: "my-value"}
+	dummyEnv := dummy.Name + "=" + dummy.Value
+
+	t.Run("Sets secret via cli args", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Flush pending mocks after test execution
+		defer gock.Off()
+		gock.New("https://api.supabase.io").
+			Post("/v1/projects/" + project + "/secrets").
+			MatchType("json").
+			JSON([]list.Secret{dummy}).
+			Reply(200)
+		// Run test
+		assert.NoError(t, Run("", []string{dummyEnv}, fsys))
+	})
+
+	t.Run("Sets secret value via env file", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Setup dotenv file
+		tmpfile, err := os.CreateTemp("", "secret")
+		require.NoError(t, err)
+		defer os.Remove(tmpfile.Name())
+		_, err = tmpfile.Write([]byte(dummyEnv))
+		require.NoError(t, err)
+		// Flush pending mocks after test execution
+		defer gock.Off()
+		gock.New("https://api.supabase.io").
+			Post("/v1/projects/" + project + "/secrets").
+			MatchType("json").
+			JSON([]list.Secret{dummy}).
+			Reply(200)
+		// Run test
+		assert.NoError(t, Run(tmpfile.Name(), []string{}, fsys))
+	})
+
+	t.Run("throws error on missing config file", func(t *testing.T) {
+		assert.Error(t, Run("", []string{}, afero.NewMemMapFs()))
+	})
+
+	t.Run("throws error on missing project ref", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Run test
+		assert.Error(t, Run("", []string{}, fsys))
+	})
+
+	t.Run("throws error on missing access token", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		_, err = fsys.Create(utils.ProjectRefPath)
+		require.NoError(t, err)
+		// Run test
+		assert.Error(t, Run("", []string{}, fsys))
+	})
+
+	t.Run("throws error on empty secret", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Run test
+		assert.Error(t, Run("", []string{}, fsys))
+	})
+
+	t.Run("throws error on network error", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Flush pending mocks after test execution
+		defer gock.Off()
+		gock.New("https://api.supabase.io").
+			Post("/v1/projects/" + project + "/secrets").
+			MatchType("json").
+			JSON([]list.Secret{dummy}).
+			ReplyError(errors.New("network error"))
+		// Run test
+		assert.Error(t, Run("", []string{dummyEnv}, fsys))
+	})
+
+	t.Run("throws error on server unavailable", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Flush pending mocks after test execution
+		defer gock.Off()
+		gock.New("https://api.supabase.io").
+			Post("/v1/projects/" + project + "/secrets").
+			MatchType("json").
+			JSON([]list.Secret{dummy}).
+			Reply(500).
+			JSON(map[string]string{"message": "unavailable"})
+		// Run test
+		assert.Error(t, Run("", []string{dummyEnv}, fsys))
+	})
+}
diff --git a/internal/secrets/unset/unset_test.go b/internal/secrets/unset/unset_test.go
new file mode 100644
index 00000000..554d00f8
--- /dev/null
+++ b/internal/secrets/unset/unset_test.go
@@ -0,0 +1,125 @@
+package unset
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/spf13/afero"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/supabase/cli/internal/testing/apitest"
+	"github.com/supabase/cli/internal/utils"
+	"gopkg.in/h2non/gock.v1"
+)
+
+func TestSecretUnsetCommand(t *testing.T) {
+	t.Run("Unsets secret via cli args", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Flush pending mocks after test execution
+		defer gock.Off()
+		gock.New("https://api.supabase.io").
+			Delete("/v1/projects/" + project + "/secrets").
+			MatchType("json").
+			JSON([]string{"my-secret"}).
+			Reply(200)
+		// Run test
+		assert.NoError(t, Run([]string{"my-secret"}, fsys))
+	})
+
+	t.Run("throws error on missing config file", func(t *testing.T) {
+		assert.Error(t, Run([]string{}, afero.NewMemMapFs()))
+	})
+
+	t.Run("throws error on missing project ref", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Run test
+		assert.Error(t, Run([]string{}, fsys))
+	})
+
+	t.Run("throws error on missing access token", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		_, err = fsys.Create(utils.ProjectRefPath)
+		require.NoError(t, err)
+		// Run test
+		assert.Error(t, Run([]string{}, fsys))
+	})
+
+	t.Run("throws error on empty secret", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Run test
+		assert.Error(t, Run([]string{}, fsys))
+	})
+
+	t.Run("throws error on network error", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Flush pending mocks after test execution
+		defer gock.Off()
+		gock.New("https://api.supabase.io").
+			Post("/v1/projects/" + project + "/secrets").
+			MatchType("json").
+			JSON([]string{"my-secret"}).
+			ReplyError(errors.New("network error"))
+		// Run test
+		assert.Error(t, Run([]string{"my-secret"}, fsys))
+	})
+
+	t.Run("throws error on server unavailable", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		_, err := fsys.Create(utils.ConfigPath)
+		require.NoError(t, err)
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		err = afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644)
+		require.NoError(t, err)
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Flush pending mocks after test execution
+		defer gock.Off()
+		gock.New("https://api.supabase.io").
+			Post("/v1/projects/" + project + "/secrets").
+			MatchType("json").
+			JSON([]string{"my-secret"}).
+			Reply(500).
+			JSON(map[string]string{"message": "unavailable"})
+		// Run test
+		assert.Error(t, Run([]string{"my-secret"}, fsys))
+	})
+}