Repository: supabase/cli
Pull request #351 (2022-08-11 06:55:19)

chore: switch to generated api client
## What kind of change does this PR introduce?

Bug fix, feature, docs update, ...

## What is the current behavior?

Please link any relevant issues here.

## What is the new behavior?

Feel free to include screenshots if it includes visual changes.

## Additional context

Add any other context or screenshots.


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

* **11** of **13**   **(84.62%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.02%**) to **25.21%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [cmd/secrets.go](https://coveralls.io/builds/51583594/source?filename=cmd%2Fsecrets.go#L34) | 0 | 1 | 0.0%
| [internal/link/link.go](https://coveralls.io/builds/51583594/source?filename=internal%2Flink%2Flink.go#L15) | 4 | 5 | 80.0%
<!-- | **Total:** | **11** | **13** | **84.62%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/51583594/badge)](https://coveralls.io/builds/51583594) |
| :-- | --: |
| Change from base [Build 2837906247](https://coveralls.io/builds/51583405): |  -0.02% |
| Covered Lines: | 1232 |
| Relevant Lines: | 4887 |

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


Patch:
diff --git a/cmd/secrets.go b/cmd/secrets.go
index 56540f6e..c2ef80a6 100644
--- a/cmd/secrets.go
+++ b/cmd/secrets.go
@@ -18,7 +18,7 @@ var (
 		Use:   "list",
 		Short: "List all secrets in the linked project",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return list.Run(afero.NewOsFs())
+			return list.Run(cmd.Context(), afero.NewOsFs())
 		},
 	}
 
@@ -31,7 +31,7 @@ var (
 				return err
 			}
 
-			return set.Run(envFilePath, args, afero.NewOsFs())
+			return set.Run(cmd.Context(), 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, afero.NewOsFs())
+			return unset.Run(cmd.Context(), args, afero.NewOsFs())
 		},
 	}
 )
diff --git a/internal/link/link.go b/internal/link/link.go
index e7e4caae..fb1f646d 100644
--- a/internal/link/link.go
+++ b/internal/link/link.go
@@ -3,9 +3,6 @@ package link
 import (
 	"context"
 	"errors"
-	"fmt"
-	"io"
-	"net/http"
 	"path/filepath"
 
 	"github.com/spf13/afero"
@@ -15,7 +12,7 @@ import (
 
 func Run(ctx context.Context, projectRef, username, password, database string, fsys afero.Fs) error {
 	// 1. Validate access token + project ref
-	if err := validateProjectRef(projectRef, fsys); err != nil {
+	if err := validateProjectRef(ctx, projectRef, fsys); err != nil {
 		return err
 	}
 	if err := utils.LoadConfigFS(fsys); err != nil {
@@ -54,34 +51,18 @@ func Run(ctx context.Context, projectRef, username, password, database string, f
 	return nil
 }
 
-func validateProjectRef(projectRef string, fsys afero.Fs) error {
+func validateProjectRef(ctx context.Context, projectRef string, fsys afero.Fs) error {
 	if !utils.ProjectRefPattern.MatchString(projectRef) {
 		return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
 	}
 
-	accessToken, err := utils.LoadAccessTokenFS(fsys)
+	resp, err := utils.GetSupabase().GetFunctionsWithResponse(ctx, projectRef)
 	if err != nil {
 		return err
 	}
 
-	req, err := http.NewRequest("GET", utils.GetSupabaseAPIHost()+"/v1/projects/"+projectRef+"/functions", nil)
-	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("Authorization failed for the access token and project ref pair: %w", err)
-		}
-
-		return errors.New("Authorization failed for the access token and project ref pair: " + string(body))
+	if resp.JSON200 == nil {
+		return errors.New("Authorization failed for the access token and project ref pair: " + string(resp.Body))
 	}
 
 	return nil
diff --git a/internal/secrets/list/list.go b/internal/secrets/list/list.go
index 70e61f9f..1d39ee38 100644
--- a/internal/secrets/list/list.go
+++ b/internal/secrets/list/list.go
@@ -1,12 +1,10 @@
 package list
 
 import (
+	"context"
 	"crypto/md5"
-	"encoding/json"
 	"errors"
 	"fmt"
-	"io"
-	"net/http"
 	"strings"
 
 	"github.com/charmbracelet/glamour"
@@ -14,69 +12,34 @@ import (
 	"github.com/supabase/cli/internal/utils"
 )
 
-type Secret struct {
-	Name  string `json:"name"`
-	Value string `json:"value"`
-}
-
-func Run(fsys afero.Fs) error {
+func Run(ctx context.Context, fsys afero.Fs) error {
 	// 1. Sanity checks.
 	{
 		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
 			return err
 		}
-		if err := utils.AssertIsLinkedFS(fsys); err != nil {
-			return err
-		}
 	}
 
 	// 2. Print secrets.
 	{
-		projectRefBytes, err := afero.ReadFile(fsys, utils.ProjectRefPath)
+		projectRef, err := utils.LoadProjectRef(fsys)
 		if err != nil {
 			return err
 		}
-		projectRef := string(projectRefBytes)
 
-		accessToken, err := utils.LoadAccessTokenFS(fsys)
+		resp, err := utils.GetSupabase().GetSecretsWithResponse(ctx, projectRef)
 		if err != nil {
 			return err
 		}
 
-		req, err := http.NewRequest("GET", utils.GetSupabaseAPIHost()+"/v1/projects/"+projectRef+"/secrets", nil)
-		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("Unexpected error retrieving project secrets: %w", err)
-			}
-
-			return errors.New("Unexpected error retrieving project secrets: " + string(body))
-		}
-
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
-		}
-
-		var secrets []Secret
-		if err := json.Unmarshal(body, &secrets); err != nil {
-			return err
+		if resp.JSON200 == nil {
+			return errors.New("Unexpected error retrieving project secrets: " + string(resp.Body))
 		}
 
 		table := `|NAME|DIGEST|
 |-|-|
 `
-		for _, secret := range secrets {
+		for _, secret := range *resp.JSON200 {
 			table += fmt.Sprintf("|`%s`|`%x`|\n", strings.ReplaceAll(secret.Name, "|", "\\|"), md5.Sum([]byte(secret.Value)))
 		}
 
diff --git a/internal/secrets/set/set.go b/internal/secrets/set/set.go
index c310d956..c6a3f8ae 100644
--- a/internal/secrets/set/set.go
+++ b/internal/secrets/set/set.go
@@ -1,52 +1,41 @@
 package set
 
 import (
-	"bytes"
-	"encoding/json"
+	"context"
 	"errors"
 	"fmt"
-	"io"
 	"net/http"
 	"strings"
 
 	"github.com/joho/godotenv"
 	"github.com/spf13/afero"
-	"github.com/supabase/cli/internal/secrets/list"
 	"github.com/supabase/cli/internal/utils"
+	"github.com/supabase/cli/pkg/api"
 )
 
-func Run(envFilePath string, args []string, fsys afero.Fs) error {
+func Run(ctx context.Context, envFilePath string, args []string, fsys afero.Fs) error {
 	// 1. Sanity checks.
 	{
 		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
 			return err
 		}
-		if err := utils.AssertIsLinkedFS(fsys); err != nil {
-			return err
-		}
 	}
 
 	// 2. Set secret(s).
 	{
-		projectRefBytes, err := afero.ReadFile(fsys, utils.ProjectRefPath)
-		if err != nil {
-			return err
-		}
-		projectRef := string(projectRefBytes)
-
-		accessToken, err := utils.LoadAccessTokenFS(fsys)
+		projectRef, err := utils.LoadProjectRef(fsys)
 		if err != nil {
 			return err
 		}
 
-		var secrets []list.Secret
+		var secrets api.CreateSecretsJSONBody
 		if envFilePath != "" {
 			envMap, err := godotenv.Read(envFilePath)
 			if err != nil {
 				return err
 			}
 			for name, value := range envMap {
-				secret := list.Secret{
+				secret := api.CreateSecretBody{
 					Name:  name,
 					Value: value,
 				}
@@ -61,7 +50,7 @@ func Run(envFilePath string, args []string, fsys afero.Fs) error {
 					return errors.New("Invalid secret pair: " + utils.Aqua(pair) + ". Must be NAME=VALUE.")
 				}
 
-				secret := list.Secret{
+				secret := api.CreateSecretBody{
 					Name:  name,
 					Value: value,
 				}
@@ -69,31 +58,13 @@ func Run(envFilePath string, args []string, fsys afero.Fs) error {
 			}
 		}
 
-		secretsBytes, err := json.Marshal(secrets)
-		if err != nil {
-			return err
-		}
-		reqBody := bytes.NewReader(secretsBytes)
-
-		req, err := http.NewRequest("POST", utils.GetSupabaseAPIHost()+"/v1/projects/"+projectRef+"/secrets", reqBody)
-		if err != nil {
-			return err
-		}
-		req.Header.Add("Authorization", "Bearer "+string(accessToken))
-		req.Header.Add("Content-Type", "application/json")
-		resp, err := http.DefaultClient.Do(req)
+		resp, err := utils.GetSupabase().CreateSecretsWithResponse(ctx, projectRef, secrets)
 		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("Unexpected error setting project secrets: %w", err)
-			}
 
-			return errors.New("Unexpected error setting project secrets: " + string(body))
+		if resp.StatusCode() != http.StatusOK {
+			return errors.New("Unexpected error setting project secrets: " + string(resp.Body))
 		}
 	}
 
diff --git a/internal/secrets/unset/unset.go b/internal/secrets/unset/unset.go
index 5488c96a..1a32a105 100644
--- a/internal/secrets/unset/unset.go
+++ b/internal/secrets/unset/unset.go
@@ -1,67 +1,37 @@
 package unset
 
 import (
-	"bytes"
-	"encoding/json"
+	"context"
 	"errors"
 	"fmt"
-	"io"
 	"net/http"
 
 	"github.com/spf13/afero"
 	"github.com/supabase/cli/internal/utils"
 )
 
-func Run(args []string, fsys afero.Fs) error {
+func Run(ctx context.Context, args []string, fsys afero.Fs) error {
 	// 1. Sanity checks.
 	{
 		if err := utils.AssertSupabaseCliIsSetUpFS(fsys); err != nil {
 			return err
 		}
-		if err := utils.AssertIsLinkedFS(fsys); err != nil {
-			return err
-		}
 	}
 
 	// 2. Unset secret(s).
 	{
-		projectRefBytes, err := afero.ReadFile(fsys, utils.ProjectRefPath)
-		if err != nil {
-			return err
-		}
-		projectRef := string(projectRefBytes)
-
-		accessToken, err := utils.LoadAccessTokenFS(fsys)
-		if err != nil {
-			return err
-		}
-
-		secretsNamesBytes, err := json.Marshal(args)
+		projectRef, err := utils.LoadProjectRef(fsys)
 		if err != nil {
 			return err
 		}
-		reqBody := bytes.NewReader(secretsNamesBytes)
 
-		req, err := http.NewRequest(
-			"DELETE", utils.GetSupabaseAPIHost()+"/v1/projects/"+projectRef+"/secrets", reqBody)
+		resp, err := utils.GetSupabase().DeleteSecretsWithResponse(ctx, projectRef, args)
 		if err != nil {
 			return err
 		}
-		req.Header.Add("Authorization", "Bearer "+string(accessToken))
-		req.Header.Add("Content-Type", "application/json")
-		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("Unexpected error unsetting project secrets: %w", err)
-			}
 
-			return errors.New("Unexpected error unsetting project secrets: " + string(body))
+		if resp.StatusCode() != http.StatusOK {
+			return errors.New("Unexpected error unsetting project secrets: " + string(resp.Body))
 		}
 	}
 
diff --git a/internal/utils/misc.go b/internal/utils/misc.go
index 57f08122..77ad6e78 100644
--- a/internal/utils/misc.go
+++ b/internal/utils/misc.go
@@ -225,7 +225,7 @@ func AssertIsLinkedFS(fsys afero.Fs) error {
 func LoadProjectRef(fsys afero.Fs) (string, error) {
 	projectRefBytes, err := afero.ReadFile(fsys, ProjectRefPath)
 	if err != nil {
-		return "", err
+		return "", errors.New("Cannot find project ref. Have you run " + Aqua("supabase link") + "?")
 	}
 	projectRef := string(projectRefBytes)
 	if !ProjectRefPattern.MatchString(projectRef) {
diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go
index 15b2d071..929cfe1c 100644
--- a/pkg/api/client.gen.go
+++ b/pkg/api/client.gen.go
@@ -1015,7 +1015,7 @@ func (r GetProjectsResponse) StatusCode() int {
 type CreateProjectResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
-	JSON200      *ProjectResponse
+	JSON201      *ProjectResponse
 }
 
 // Status returns HTTPResponse.Status
@@ -1037,7 +1037,7 @@ func (r CreateProjectResponse) StatusCode() int {
 type GetFunctionsResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
-	JSON200      *[]map[string]interface{}
+	JSON200      *[]FunctionResponse
 }
 
 // Status returns HTTPResponse.Status
@@ -1059,7 +1059,7 @@ func (r GetFunctionsResponse) StatusCode() int {
 type CreateFunctionResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
-	JSON201      *map[string]interface{}
+	JSON201      *FunctionResponse
 }
 
 // Status returns HTTPResponse.Status
@@ -1102,7 +1102,7 @@ func (r DeleteFunctionResponse) StatusCode() int {
 type GetFunctionResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
-	JSON200      *map[string]interface{}
+	JSON200      *FunctionSlugResponse
 }
 
 // Status returns HTTPResponse.Status
@@ -1124,7 +1124,7 @@ func (r GetFunctionResponse) StatusCode() int {
 type UpdateFunctionResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
-	JSON200      *map[string]interface{}
+	JSON200      *FunctionResponse
 }
 
 // Status returns HTTPResponse.Status
@@ -1168,7 +1168,7 @@ func (r DeleteSecretsResponse) StatusCode() int {
 type GetSecretsResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
-	JSON200      *[]map[string]interface{}
+	JSON200      *[]SecretResponse
 }
 
 // Status returns HTTPResponse.Status
@@ -1456,12 +1456,12 @@ func ParseCreateProjectResponse(rsp *http.Response) (*CreateProjectResponse, err
 	}
 
 	switch {
-	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201:
 		var dest ProjectResponse
 		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
 			return nil, err
 		}
-		response.JSON200 = &dest
+		response.JSON201 = &dest
 
 	}
 
@@ -1483,7 +1483,7 @@ func ParseGetFunctionsResponse(rsp *http.Response) (*GetFunctionsResponse, error
 
 	switch {
 	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
-		var dest []map[string]interface{}
+		var dest []FunctionResponse
 		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
 			return nil, err
 		}
@@ -1509,7 +1509,7 @@ func ParseCreateFunctionResponse(rsp *http.Response) (*CreateFunctionResponse, e
 
 	switch {
 	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201:
-		var dest map[string]interface{}
+		var dest FunctionResponse
 		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
 			return nil, err
 		}
@@ -1551,7 +1551,7 @@ func ParseGetFunctionResponse(rsp *http.Response) (*GetFunctionResponse, error)
 
 	switch {
 	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
-		var dest map[string]interface{}
+		var dest FunctionSlugResponse
 		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
 			return nil, err
 		}
@@ -1577,7 +1577,7 @@ func ParseUpdateFunctionResponse(rsp *http.Response) (*UpdateFunctionResponse, e
 
 	switch {
 	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
-		var dest map[string]interface{}
+		var dest FunctionResponse
 		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
 			return nil, err
 		}
@@ -1629,7 +1629,7 @@ func ParseGetSecretsResponse(rsp *http.Response) (*GetSecretsResponse, error) {
 
 	switch {
 	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
-		var dest []map[string]interface{}
+		var dest []SecretResponse
 		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
 			return nil, err
 		}
diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go
index 66d0abc9..b1094762 100644
--- a/pkg/api/types.gen.go
+++ b/pkg/api/types.gen.go
@@ -29,6 +29,20 @@ const (
 	UsWest1      CreateProjectBodyRegion = "us-west-1"
 )
 
+// Defines values for FunctionResponseStatus.
+const (
+	FunctionResponseStatusACTIVE    FunctionResponseStatus = "ACTIVE"
+	FunctionResponseStatusREMOVED   FunctionResponseStatus = "REMOVED"
+	FunctionResponseStatusTHROTTLED FunctionResponseStatus = "THROTTLED"
+)
+
+// Defines values for FunctionSlugResponseStatus.
+const (
+	FunctionSlugResponseStatusACTIVE    FunctionSlugResponseStatus = "ACTIVE"
+	FunctionSlugResponseStatusREMOVED   FunctionSlugResponseStatus = "REMOVED"
+	FunctionSlugResponseStatusTHROTTLED FunctionSlugResponseStatus = "THROTTLED"
+)
+
 // CreateFunctionBody defines model for CreateFunctionBody.
 type CreateFunctionBody struct {
 	Body      string `json:"body"`
@@ -57,12 +71,43 @@ type CreateProjectBodyPlan string
 // CreateProjectBodyRegion defines model for CreateProjectBody.Region.
 type CreateProjectBodyRegion string
 
-// CreateSecretParams defines model for CreateSecretParams.
-type CreateSecretParams struct {
+// CreateSecretBody defines model for CreateSecretBody.
+type CreateSecretBody struct {
 	Name  string `json:"name"`
 	Value string `json:"value"`
 }
 
+// FunctionResponse defines model for FunctionResponse.
+type FunctionResponse struct {
+	CreatedAt float32                `json:"created_at"`
+	Id        string                 `json:"id"`
+	Name      string                 `json:"name"`
+	Slug      string                 `json:"slug"`
+	Status    FunctionResponseStatus `json:"status"`
+	UpdatedAt float32                `json:"updated_at"`
+	VerifyJwt *bool                  `json:"verify_jwt,omitempty"`
+	Version   float32                `json:"version"`
+}
+
+// FunctionResponseStatus defines model for FunctionResponse.Status.
+type FunctionResponseStatus string
+
+// FunctionSlugResponse defines model for FunctionSlugResponse.
+type FunctionSlugResponse struct {
+	Body      *string                    `json:"body,omitempty"`
+	CreatedAt float32                    `json:"created_at"`
+	Id        string                     `json:"id"`
+	Name      string                     `json:"name"`
+	Slug      string                     `json:"slug"`
+	Status    FunctionSlugResponseStatus `json:"status"`
+	UpdatedAt float32                    `json:"updated_at"`
+	VerifyJwt *bool                      `json:"verify_jwt,omitempty"`
+	Version   float32                    `json:"version"`
+}
+
+// FunctionSlugResponseStatus defines model for FunctionSlugResponse.Status.
+type FunctionSlugResponseStatus string
+
 // OrganizationResponse defines model for OrganizationResponse.
 type OrganizationResponse struct {
 	Id   string `json:"id"`
@@ -78,11 +123,17 @@ type ProjectResponse struct {
 	Region         string `json:"region"`
 }
 
+// SecretResponse defines model for SecretResponse.
+type SecretResponse struct {
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
 // UpdateFunctionBody defines model for UpdateFunctionBody.
 type UpdateFunctionBody struct {
-	Body      string `json:"body"`
-	Name      string `json:"name"`
-	VerifyJwt *bool  `json:"verify_jwt,omitempty"`
+	Body      *string `json:"body,omitempty"`
+	Name      *string `json:"name,omitempty"`
+	VerifyJwt *bool   `json:"verify_jwt,omitempty"`
 }
 
 // CreateOrganizationJSONBody defines parameters for CreateOrganization.
@@ -106,7 +157,7 @@ type UpdateFunctionJSONBody = UpdateFunctionBody
 type DeleteSecretsJSONBody = []string
 
 // CreateSecretsJSONBody defines parameters for CreateSecrets.
-type CreateSecretsJSONBody = []CreateSecretParams
+type CreateSecretsJSONBody = []CreateSecretBody
 
 // CreateOrganizationJSONRequestBody defines body for CreateOrganization for application/json ContentType.
 type CreateOrganizationJSONRequestBody = CreateOrganizationJSONBody


Test patch:
diff --git a/internal/link/link_test.go b/internal/link/link_test.go
index 427151a2..30eb2792 100644
--- a/internal/link/link_test.go
+++ b/internal/link/link_test.go
@@ -1,12 +1,14 @@
 package link
 
 import (
+	"context"
 	"errors"
 	"testing"
 
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/assert"
 	"github.com/supabase/cli/internal/testing/apitest"
+	"github.com/supabase/cli/pkg/api"
 	"gopkg.in/h2non/gock.v1"
 )
 
@@ -23,9 +25,9 @@ func TestProjectValidation(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Get("/v1/projects/" + project + "/functions").
 			Reply(200).
-			JSON([]string{})
+			JSON([]api.FunctionResponse{})
 		// Run test
-		assert.NoError(t, validateProjectRef(project, fsys))
+		assert.NoError(t, validateProjectRef(context.Background(), project, fsys))
 		// Validate file contents
 		// content, err := afero.ReadFile(fsys, utils.ProjectRefPath)
 		// assert.NoError(t, err)
@@ -33,14 +35,14 @@ func TestProjectValidation(t *testing.T) {
 	})
 
 	t.Run("throws error on invalid project ref", func(t *testing.T) {
-		assert.Error(t, validateProjectRef("malformed", afero.NewMemMapFs()))
+		assert.Error(t, validateProjectRef(context.Background(), "malformed", afero.NewMemMapFs()))
 	})
 
 	t.Run("throws error on failure to load token", func(t *testing.T) {
 		// Setup valid access token
 		project := apitest.RandomProjectRef()
 		fsys := afero.NewMemMapFs()
-		assert.Error(t, validateProjectRef(project, fsys))
+		assert.Error(t, validateProjectRef(context.Background(), project, fsys))
 	})
 
 	t.Run("throws error on network error", func(t *testing.T) {
@@ -56,7 +58,7 @@ func TestProjectValidation(t *testing.T) {
 			Get("/v1/projects/" + project + "/functions").
 			ReplyError(errors.New("network error"))
 		// Run test
-		assert.Error(t, validateProjectRef(project, fsys))
+		assert.Error(t, validateProjectRef(context.Background(), project, fsys))
 	})
 
 	t.Run("throws error on server unavailable", func(t *testing.T) {
@@ -73,6 +75,6 @@ func TestProjectValidation(t *testing.T) {
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
-		assert.Error(t, validateProjectRef(project, fsys))
+		assert.Error(t, validateProjectRef(context.Background(), project, fsys))
 	})
 }
diff --git a/internal/secrets/list/list_test.go b/internal/secrets/list/list_test.go
index cd5d5e47..f9a5e4bc 100644
--- a/internal/secrets/list/list_test.go
+++ b/internal/secrets/list/list_test.go
@@ -1,6 +1,7 @@
 package list
 
 import (
+	"context"
 	"errors"
 	"testing"
 
@@ -9,6 +10,7 @@ import (
 	"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"
 )
 
@@ -30,18 +32,18 @@ func TestSecretListCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Get("/v1/projects/" + project + "/secrets").
 			Reply(200).
-			JSON([]Secret{
+			JSON(api.CreateSecretsJSONBody{
 				{
 					Name:  "Test Secret",
 					Value: "dummy-secret-value",
 				},
 			})
 		// Run test
-		assert.NoError(t, Run(fsys))
+		assert.NoError(t, Run(context.Background(), fsys))
 	})
 
 	t.Run("throws error on missing config file", func(t *testing.T) {
-		assert.Error(t, Run(afero.NewMemMapFs()))
+		assert.Error(t, Run(context.Background(), afero.NewMemMapFs()))
 	})
 
 	t.Run("throws error on missing project ref", func(t *testing.T) {
@@ -50,7 +52,7 @@ func TestSecretListCommand(t *testing.T) {
 		_, err := fsys.Create(utils.ConfigPath)
 		require.NoError(t, err)
 		// Run test
-		assert.Error(t, Run(fsys))
+		assert.Error(t, Run(context.Background(), fsys))
 	})
 
 	t.Run("throws error on missing access token", func(t *testing.T) {
@@ -61,7 +63,7 @@ func TestSecretListCommand(t *testing.T) {
 		_, err = fsys.Create(utils.ProjectRefPath)
 		require.NoError(t, err)
 		// Run test
-		assert.Error(t, Run(fsys))
+		assert.Error(t, Run(context.Background(), fsys))
 	})
 
 	t.Run("throws error on network error", func(t *testing.T) {
@@ -82,7 +84,7 @@ func TestSecretListCommand(t *testing.T) {
 			Get("/v1/projects/" + project + "/secrets").
 			ReplyError(errors.New("network error"))
 		// Run test
-		assert.Error(t, Run(fsys))
+		assert.Error(t, Run(context.Background(), fsys))
 	})
 
 	t.Run("throws error on server unavailable", func(t *testing.T) {
@@ -104,7 +106,7 @@ func TestSecretListCommand(t *testing.T) {
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
-		assert.Error(t, Run(fsys))
+		assert.Error(t, Run(context.Background(), fsys))
 	})
 
 	t.Run("throws error on malformed json", func(t *testing.T) {
@@ -126,6 +128,6 @@ func TestSecretListCommand(t *testing.T) {
 			Reply(200).
 			JSON(map[string]string{})
 		// Run test
-		assert.Error(t, Run(fsys))
+		assert.Error(t, Run(context.Background(), fsys))
 	})
 }
diff --git a/internal/secrets/set/set_test.go b/internal/secrets/set/set_test.go
index f9e241ff..fa22a1db 100644
--- a/internal/secrets/set/set_test.go
+++ b/internal/secrets/set/set_test.go
@@ -1,6 +1,7 @@
 package set
 
 import (
+	"context"
 	"errors"
 	"os"
 	"testing"
@@ -8,14 +9,14 @@ import (
 	"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"
+	"github.com/supabase/cli/pkg/api"
 	"gopkg.in/h2non/gock.v1"
 )
 
 func TestSecretSetCommand(t *testing.T) {
-	dummy := list.Secret{Name: "my-name", Value: "my-value"}
+	dummy := api.CreateSecretBody{Name: "my-name", Value: "my-value"}
 	dummyEnv := dummy.Name + "=" + dummy.Value
 
 	t.Run("Sets secret via cli args", func(t *testing.T) {
@@ -35,10 +36,10 @@ func TestSecretSetCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects/" + project + "/secrets").
 			MatchType("json").
-			JSON([]list.Secret{dummy}).
+			JSON(api.CreateSecretsJSONBody{dummy}).
 			Reply(200)
 		// Run test
-		assert.NoError(t, Run("", []string{dummyEnv}, fsys))
+		assert.NoError(t, Run(context.Background(), "", []string{dummyEnv}, fsys))
 	})
 
 	t.Run("Sets secret value via env file", func(t *testing.T) {
@@ -64,14 +65,14 @@ func TestSecretSetCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects/" + project + "/secrets").
 			MatchType("json").
-			JSON([]list.Secret{dummy}).
+			JSON(api.CreateSecretsJSONBody{dummy}).
 			Reply(200)
 		// Run test
-		assert.NoError(t, Run(tmpfile.Name(), []string{}, fsys))
+		assert.NoError(t, Run(context.Background(), tmpfile.Name(), []string{}, fsys))
 	})
 
 	t.Run("throws error on missing config file", func(t *testing.T) {
-		assert.Error(t, Run("", []string{}, afero.NewMemMapFs()))
+		assert.Error(t, Run(context.Background(), "", []string{}, afero.NewMemMapFs()))
 	})
 
 	t.Run("throws error on missing project ref", func(t *testing.T) {
@@ -80,7 +81,7 @@ func TestSecretSetCommand(t *testing.T) {
 		_, err := fsys.Create(utils.ConfigPath)
 		require.NoError(t, err)
 		// Run test
-		assert.Error(t, Run("", []string{}, fsys))
+		assert.Error(t, Run(context.Background(), "", []string{}, fsys))
 	})
 
 	t.Run("throws error on missing access token", func(t *testing.T) {
@@ -91,7 +92,7 @@ func TestSecretSetCommand(t *testing.T) {
 		_, err = fsys.Create(utils.ProjectRefPath)
 		require.NoError(t, err)
 		// Run test
-		assert.Error(t, Run("", []string{}, fsys))
+		assert.Error(t, Run(context.Background(), "", []string{}, fsys))
 	})
 
 	t.Run("throws error on empty secret", func(t *testing.T) {
@@ -107,7 +108,23 @@ func TestSecretSetCommand(t *testing.T) {
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
 		// Run test
-		assert.Error(t, Run("", []string{}, fsys))
+		assert.Error(t, Run(context.Background(), "", []string{}, fsys))
+	})
+
+	t.Run("throws error on malformed 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(context.Background(), "", []string{"malformed"}, fsys))
 	})
 
 	t.Run("throws error on network error", func(t *testing.T) {
@@ -127,10 +144,10 @@ func TestSecretSetCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects/" + project + "/secrets").
 			MatchType("json").
-			JSON([]list.Secret{dummy}).
+			JSON(api.CreateSecretsJSONBody{dummy}).
 			ReplyError(errors.New("network error"))
 		// Run test
-		assert.Error(t, Run("", []string{dummyEnv}, fsys))
+		assert.Error(t, Run(context.Background(), "", []string{dummyEnv}, fsys))
 	})
 
 	t.Run("throws error on server unavailable", func(t *testing.T) {
@@ -150,10 +167,10 @@ func TestSecretSetCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects/" + project + "/secrets").
 			MatchType("json").
-			JSON([]list.Secret{dummy}).
+			JSON(api.CreateSecretsJSONBody{dummy}).
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
-		assert.Error(t, Run("", []string{dummyEnv}, fsys))
+		assert.Error(t, Run(context.Background(), "", []string{dummyEnv}, fsys))
 	})
 }
diff --git a/internal/secrets/unset/unset_test.go b/internal/secrets/unset/unset_test.go
index 554d00f8..ddc20fdf 100644
--- a/internal/secrets/unset/unset_test.go
+++ b/internal/secrets/unset/unset_test.go
@@ -1,6 +1,7 @@
 package unset
 
 import (
+	"context"
 	"errors"
 	"testing"
 
@@ -9,6 +10,7 @@ import (
 	"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"
 )
 
@@ -30,14 +32,14 @@ func TestSecretUnsetCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Delete("/v1/projects/" + project + "/secrets").
 			MatchType("json").
-			JSON([]string{"my-secret"}).
+			JSON(api.DeleteSecretsJSONBody{"my-secret"}).
 			Reply(200)
 		// Run test
-		assert.NoError(t, Run([]string{"my-secret"}, fsys))
+		assert.NoError(t, Run(context.Background(), []string{"my-secret"}, fsys))
 	})
 
 	t.Run("throws error on missing config file", func(t *testing.T) {
-		assert.Error(t, Run([]string{}, afero.NewMemMapFs()))
+		assert.Error(t, Run(context.Background(), []string{}, afero.NewMemMapFs()))
 	})
 
 	t.Run("throws error on missing project ref", func(t *testing.T) {
@@ -46,7 +48,7 @@ func TestSecretUnsetCommand(t *testing.T) {
 		_, err := fsys.Create(utils.ConfigPath)
 		require.NoError(t, err)
 		// Run test
-		assert.Error(t, Run([]string{}, fsys))
+		assert.Error(t, Run(context.Background(), []string{}, fsys))
 	})
 
 	t.Run("throws error on missing access token", func(t *testing.T) {
@@ -57,7 +59,7 @@ func TestSecretUnsetCommand(t *testing.T) {
 		_, err = fsys.Create(utils.ProjectRefPath)
 		require.NoError(t, err)
 		// Run test
-		assert.Error(t, Run([]string{}, fsys))
+		assert.Error(t, Run(context.Background(), []string{}, fsys))
 	})
 
 	t.Run("throws error on empty secret", func(t *testing.T) {
@@ -73,7 +75,7 @@ func TestSecretUnsetCommand(t *testing.T) {
 		token := apitest.RandomAccessToken(t)
 		t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
 		// Run test
-		assert.Error(t, Run([]string{}, fsys))
+		assert.Error(t, Run(context.Background(), []string{}, fsys))
 	})
 
 	t.Run("throws error on network error", func(t *testing.T) {
@@ -93,10 +95,10 @@ func TestSecretUnsetCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects/" + project + "/secrets").
 			MatchType("json").
-			JSON([]string{"my-secret"}).
+			JSON(api.DeleteSecretsJSONBody{"my-secret"}).
 			ReplyError(errors.New("network error"))
 		// Run test
-		assert.Error(t, Run([]string{"my-secret"}, fsys))
+		assert.Error(t, Run(context.Background(), []string{"my-secret"}, fsys))
 	})
 
 	t.Run("throws error on server unavailable", func(t *testing.T) {
@@ -116,10 +118,10 @@ func TestSecretUnsetCommand(t *testing.T) {
 		gock.New("https://api.supabase.io").
 			Post("/v1/projects/" + project + "/secrets").
 			MatchType("json").
-			JSON([]string{"my-secret"}).
+			JSON(api.DeleteSecretsJSONBody{"my-secret"}).
 			Reply(500).
 			JSON(map[string]string{"message": "unavailable"})
 		// Run test
-		assert.Error(t, Run([]string{"my-secret"}, fsys))
+		assert.Error(t, Run(context.Background(), []string{"my-secret"}, fsys))
 	})
 }
diff --git a/test/link_test.go b/test/link_test.go
index c10b0969..2fdeb743 100644
--- a/test/link_test.go
+++ b/test/link_test.go
@@ -2,6 +2,7 @@ package integration
 
 // Basic imports
 import (
+	"context"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -34,12 +35,14 @@ type LinkTestSuite struct {
 func (suite *LinkTestSuite) TestLink() {
 	// run command
 	link, _, err := suite.cmd.Find([]string{"link"})
+	link.SetContext(context.Background())
 	require.NoError(suite.T(), err)
 	key := "sbp_" + gonanoid.MustGenerate(supabase.KeyAlphabet, supabase.KeyLength)
 	os.Setenv("SUPABASE_ACCESS_TOKEN", key)
 	id := gonanoid.MustGenerate(supabase.IDAlphabet, supabase.IDLength)
 	require.NoError(suite.T(), link.Flags().Set("project-ref", id))
 	require.NoError(suite.T(), link.Flags().Set("password", "postgres"))
+
 	require.NoError(suite.T(), link.RunE(link, []string{}))
 
 	// check request details
diff --git a/test/secrets_test.go b/test/secrets_test.go
index f67bad34..f3dc79ad 100644
--- a/test/secrets_test.go
+++ b/test/secrets_test.go
@@ -2,6 +2,7 @@ package integration
 
 // Basic imports
 import (
+	"context"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -31,9 +32,10 @@ type SecretsTestSuite struct {
 }
 
 // test functions
-func (suite *SecretsTestSuite) TestLink() {
+func (suite *SecretsTestSuite) TestList() {
 	// run command
 	list, _, err := suite.cmd.Find([]string{"secrets", "list"})
+	list.SetContext(context.Background())
 	require.NoError(suite.T(), err)
 
 	key := "sbp_" + gonanoid.MustGenerate(supabase.KeyAlphabet, supabase.KeyLength)