Repository: supabase/cli
Pull request #463 (2022-09-16 10:29:28)

chore: test function delete
## What kind of change does this PR introduce?

tests

## What is the new behavior?

- use codegen client
- handle cancellation

## Additional context

Add any other context or screenshots.


coveralls: ## Pull Request Test Coverage Report for [Build 3067174385](https://coveralls.io/builds/52530753)

* **16** of **18**   **(88.89%)**  changed or added relevant lines in **2** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**1.02%**) to **36.351%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [cmd/functions.go](https://coveralls.io/builds/52530753/source?filename=cmd%2Ffunctions.go#L32) | 0 | 2 | 0.0%
<!-- | **Total:** | **16** | **18** | **88.89%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [internal/debug/postgres.go](https://coveralls.io/builds/52530753/source?filename=internal%2Fdebug%2Fpostgres.go#L139) | 3 | 66.67% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/52530753/badge)](https://coveralls.io/builds/52530753) |
| :-- | --: |
| Change from base [Build 3066752938](https://coveralls.io/builds/52528950): |  1.02% |
| Covered Lines: | 1795 |
| Relevant Lines: | 4938 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/cmd/functions.go b/cmd/functions.go
index 9e09a12f..69e338ea 100644
--- a/cmd/functions.go
+++ b/cmd/functions.go
@@ -29,7 +29,8 @@ var (
 				return err
 			}
 
-			return delete.Run(args[0], projectRef)
+			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
+			return delete.Run(ctx, args[0], projectRef, afero.NewOsFs())
 		},
 	}
 
diff --git a/internal/functions/delete/delete.go b/internal/functions/delete/delete.go
index d70a5f55..9777b2ab 100644
--- a/internal/functions/delete/delete.go
+++ b/internal/functions/delete/delete.go
@@ -1,9 +1,9 @@
 package delete
 
 import (
+	"context"
 	"errors"
 	"fmt"
-	"io"
 	"net/http"
 	"os"
 	"strings"
@@ -11,29 +11,31 @@ import (
 	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/login"
 	"github.com/supabase/cli/internal/utils"
+	"github.com/supabase/cli/pkg/api"
 )
 
-func Run(slug string, projectRefArg string) error {
+func Run(ctx context.Context, slug string, projectRefArg string, fsys afero.Fs) error {
 	// 1. Sanity checks.
+	projectRef := projectRefArg
 	{
-		if err := utils.AssertSupabaseCliIsSetUp(); err != nil {
+		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
 			return err
 		}
-		if _, err := utils.LoadAccessToken(); err != nil && strings.HasPrefix(err.Error(), "Access token not provided. Supply an access token by running") {
-			if err := login.Run(os.Stdin, afero.NewOsFs()); err != nil {
+		if _, err := utils.LoadAccessTokenFS(fsys); err != nil && strings.HasPrefix(err.Error(), "Access token not provided. Supply an access token by running") {
+			if err := login.Run(os.Stdin, fsys); err != nil {
 				return err
 			}
 		} else if err != nil {
 			return err
 		}
 		if len(projectRefArg) == 0 {
-			if err := utils.AssertIsLinked(); err != nil {
+			ref, err := utils.LoadProjectRef(fsys)
+			if err != nil {
 				return err
 			}
-		} else {
-			if !utils.ProjectRefPattern.MatchString(projectRefArg) {
-				return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
-			}
+			projectRef = ref
+		} else if !utils.ProjectRefPattern.MatchString(projectRefArg) {
+			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
 		}
 		if err := utils.ValidateFunctionSlug(slug); err != nil {
 			return err
@@ -41,64 +43,25 @@ func Run(slug string, projectRefArg string) error {
 	}
 
 	// 2. Delete Function.
-	var projectRef string
 	{
-		// --project-ref overrides value on disk
-		if len(projectRefArg) == 0 {
-			projectRefBytes, err := os.ReadFile(utils.ProjectRefPath)
-			if err != nil {
-				return err
-			}
-			projectRef = string(projectRefBytes)
-		} else {
-			projectRef = projectRefArg
-		}
-
-		accessToken, err := utils.LoadAccessToken()
-		if err != nil {
-			return err
-		}
-
-		req, err := http.NewRequest("GET", utils.GetSupabaseAPIHost()+"/v1/projects/"+projectRef+"/functions/"+slug, nil)
-		if err != nil {
-			return err
-		}
-		req.Header.Add("Authorization", "Bearer "+string(accessToken))
-		resp, err := http.DefaultClient.Do(req)
+		resp, err := utils.GetSupabase().GetFunctionWithResponse(ctx, projectRef, slug, &api.GetFunctionParams{})
 		if err != nil {
 			return err
 		}
-		defer resp.Body.Close()
 
-		switch resp.StatusCode {
+		switch resp.StatusCode() {
 		case http.StatusNotFound: // Function doesn't exist
 			return errors.New("Function " + utils.Aqua(slug) + " does not exist on the Supabase project.")
 		case http.StatusOK: // Function exists
-			req, err := http.NewRequest("DELETE", utils.GetSupabaseAPIHost()+"/v1/projects/"+projectRef+"/functions/"+slug, nil)
+			resp, err := utils.GetSupabase().DeleteFunctionWithResponse(ctx, projectRef, slug)
 			if err != nil {
 				return err
 			}
-			req.Header.Add("Authorization", "Bearer "+string(accessToken))
-			resp, err := http.DefaultClient.Do(req)
-			if err != nil {
-				return err
-			}
-			defer resp.Body.Close()
-			if resp.StatusCode != 200 {
-				body, err := io.ReadAll(resp.Body)
-				if err != nil {
-					return fmt.Errorf("Failed to delete Function %v on the Supabase project: %w", utils.Aqua(slug), err)
-				}
-
-				return errors.New("Failed to delete Function " + utils.Aqua(slug) + " on the Supabase project: " + string(body))
+			if resp.StatusCode() != http.StatusOK {
+				return errors.New("Failed to delete Function " + utils.Aqua(slug) + " on the Supabase project: " + string(resp.Body))
 			}
 		default:
-			body, err := io.ReadAll(resp.Body)
-			if err != nil {
-				return fmt.Errorf("Unexpected error deleting Function: %w", err)
-			}
-
-			return errors.New("Unexpected error deleting Function: " + string(body))
+			return errors.New("Unexpected error deleting Function: " + string(resp.Body))
 		}
 	}
 


Test patch:
diff --git a/internal/functions/delete/delete_test.go b/internal/functions/delete/delete_test.go
new file mode 100644
index 00000000..5650a7d7
--- /dev/null
+++ b/internal/functions/delete/delete_test.go
@@ -0,0 +1,165 @@
+package delete
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"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"
+	"github.com/supabase/cli/pkg/api"
+	"gopkg.in/h2non/gock.v1"
+)
+
+func TestDeleteCommand(t *testing.T) {
+	t.Run("deletes function from project", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		require.NoError(t, afero.WriteFile(fsys, utils.ProjectRefPath, []byte(project), 0644))
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Setup mock api
+		defer gock.OffAll()
+		gock.New("https://api.supabase.io").
+			Get("/v1/projects/" + project + "/functions/test-func").
+			Reply(http.StatusOK).
+			JSON(api.FunctionResponse{Id: "1"})
+		gock.New("https://api.supabase.io").
+			Delete("/v1/projects/" + project + "/functions/test-func").
+			Reply(http.StatusOK)
+		// Run test
+		assert.NoError(t, Run(context.Background(), "test-func", "", fsys))
+	})
+
+	t.Run("throws error if uninitialised", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		// Run test
+		assert.Error(t, Run(context.Background(), "test-func", "test-project", fsys))
+	})
+
+	t.Run("throws error on malformed ref", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Run test
+		assert.Error(t, Run(context.Background(), "test-func", "test-project", fsys))
+		assert.Error(t, Run(context.Background(), "test-func", "", fsys))
+	})
+
+	t.Run("throws error on malformed slug", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		// Run test
+		assert.Error(t, Run(context.Background(), "@", project, fsys))
+	})
+
+	t.Run("throws error on network failure", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Setup mock api
+		defer gock.OffAll()
+		gock.New("https://api.supabase.io").
+			Get("/v1/projects/" + project + "/functions/test-func").
+			ReplyError(errors.New("network error"))
+		// Run test
+		assert.Error(t, Run(context.Background(), "test-func", project, fsys))
+	})
+
+	t.Run("throws error on service unavailable", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Setup mock api
+		defer gock.OffAll()
+		gock.New("https://api.supabase.io").
+			Get("/v1/projects/" + project + "/functions/test-func").
+			Reply(http.StatusServiceUnavailable)
+		// Run test
+		assert.Error(t, Run(context.Background(), "test-func", project, fsys))
+	})
+
+	t.Run("throws error on missing function", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Setup mock api
+		defer gock.OffAll()
+		gock.New("https://api.supabase.io").
+			Get("/v1/projects/" + project + "/functions/test-func").
+			Reply(http.StatusNotFound)
+		// Run test
+		assert.Error(t, Run(context.Background(), "test-func", project, fsys))
+	})
+
+	t.Run("throws error on delete failure", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Setup mock api
+		defer gock.OffAll()
+		gock.New("https://api.supabase.io").
+			Get("/v1/projects/" + project + "/functions/test-func").
+			Reply(http.StatusOK).
+			JSON(api.FunctionResponse{Id: "1"})
+		gock.New("https://api.supabase.io").
+			Delete("/v1/projects/" + project + "/functions/test-func").
+			Reply(http.StatusServiceUnavailable)
+		// Run test
+		assert.Error(t, Run(context.Background(), "test-func", project, fsys))
+	})
+
+	t.Run("throws error on delete network failure", func(t *testing.T) {
+		// Setup in-memory fs
+		fsys := afero.NewMemMapFs()
+		require.NoError(t, utils.WriteConfig(fsys, false))
+		// Setup valid project ref
+		project := apitest.RandomProjectRef()
+		// Setup valid access token
+		token := apitest.RandomAccessToken(t)
+		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
+		// Setup mock api
+		defer gock.OffAll()
+		gock.New("https://api.supabase.io").
+			Get("/v1/projects/" + project + "/functions/test-func").
+			Reply(http.StatusOK).
+			JSON(api.FunctionResponse{Id: "1"})
+		gock.New("https://api.supabase.io").
+			Delete("/v1/projects/" + project + "/functions/test-func").
+			ReplyError(errors.New("network error"))
+		// Run test
+		assert.Error(t, Run(context.Background(), "test-func", project, fsys))
+	})
+}