github.com/supabase/cli@v1.168.1/internal/functions/download/download_test.go (about) 1 package download 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "context" 7 "errors" 8 "fmt" 9 "log" 10 "net/http" 11 "os" 12 "testing" 13 14 "github.com/spf13/afero" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 "github.com/supabase/cli/internal/testing/apitest" 18 "github.com/supabase/cli/internal/utils" 19 "github.com/supabase/cli/pkg/api" 20 "gopkg.in/h2non/gock.v1" 21 ) 22 23 func TestMain(m *testing.M) { 24 // Setup fake deno binary 25 if len(os.Args) > 1 && (os.Args[1] == "bundle" || os.Args[1] == "upgrade" || os.Args[1] == "run") { 26 msg := os.Getenv("TEST_DENO_ERROR") 27 if msg != "" { 28 fmt.Fprintln(os.Stderr, msg) 29 os.Exit(1) 30 } 31 os.Exit(0) 32 } 33 denoPath, err := os.Executable() 34 if err != nil { 35 log.Fatalln(err) 36 } 37 utils.DenoPathOverride = denoPath 38 // Run test suite 39 os.Exit(m.Run()) 40 } 41 42 func TestDownloadCommand(t *testing.T) { 43 const slug = "test-func" 44 45 t.Run("downloads eszip bundle", func(t *testing.T) { 46 // Setup in-memory fs 47 fsys := afero.NewMemMapFs() 48 // Setup valid project ref 49 project := apitest.RandomProjectRef() 50 // Setup valid access token 51 token := apitest.RandomAccessToken(t) 52 t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) 53 // Setup valid deno path 54 _, err := fsys.Create(utils.DenoPathOverride) 55 require.NoError(t, err) 56 // Setup mock api 57 defer gock.OffAll() 58 gock.New(utils.DefaultApiHost). 59 Get("/v1/projects/" + project + "/functions/" + slug). 60 Reply(http.StatusOK). 61 JSON(api.FunctionResponse{Id: "1"}) 62 gock.New(utils.DefaultApiHost). 63 Get("/v1/projects/" + project + "/functions/" + slug + "/body"). 64 Reply(http.StatusOK) 65 // Run test 66 err = Run(context.Background(), slug, project, true, fsys) 67 // Check error 68 assert.NoError(t, err) 69 assert.Empty(t, apitest.ListUnmatchedRequests()) 70 }) 71 72 t.Run("throws error on malformed slug", func(t *testing.T) { 73 // Setup in-memory fs 74 fsys := afero.NewMemMapFs() 75 // Setup valid project ref 76 project := apitest.RandomProjectRef() 77 // Run test 78 err := Run(context.Background(), "@", project, true, fsys) 79 // Check error 80 assert.ErrorContains(t, err, "Invalid Function name.") 81 }) 82 83 t.Run("throws error on failure to install deno", func(t *testing.T) { 84 // Setup in-memory fs 85 fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) 86 // Setup valid project ref 87 project := apitest.RandomProjectRef() 88 // Run test 89 err := Run(context.Background(), slug, project, true, fsys) 90 // Check error 91 assert.ErrorContains(t, err, "operation not permitted") 92 }) 93 94 t.Run("throws error on copy failure", func(t *testing.T) { 95 // Setup in-memory fs 96 fsys := afero.NewMemMapFs() 97 // Setup valid project ref 98 project := apitest.RandomProjectRef() 99 // Setup valid deno path 100 _, err := fsys.Create(utils.DenoPathOverride) 101 require.NoError(t, err) 102 // Run test 103 err = Run(context.Background(), slug, project, true, afero.NewReadOnlyFs(fsys)) 104 // Check error 105 assert.ErrorContains(t, err, "operation not permitted") 106 }) 107 108 t.Run("throws error on missing function", func(t *testing.T) { 109 // Setup in-memory fs 110 fsys := afero.NewMemMapFs() 111 // Setup valid project ref 112 project := apitest.RandomProjectRef() 113 // Setup valid access token 114 token := apitest.RandomAccessToken(t) 115 t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) 116 // Setup valid deno path 117 _, err := fsys.Create(utils.DenoPathOverride) 118 require.NoError(t, err) 119 // Setup mock api 120 defer gock.OffAll() 121 gock.New(utils.DefaultApiHost). 122 Get("/v1/projects/" + project + "/functions/" + slug). 123 Reply(http.StatusNotFound). 124 JSON(map[string]string{"message": "Function not found"}) 125 // Run test 126 err = Run(context.Background(), slug, project, true, fsys) 127 // Check error 128 assert.ErrorContains(t, err, "Function test-func does not exist on the Supabase project.") 129 }) 130 } 131 132 func TestDownloadFunction(t *testing.T) { 133 const slug = "test-func" 134 // Setup valid project ref 135 project := apitest.RandomProjectRef() 136 // Setup valid access token 137 token := apitest.RandomAccessToken(t) 138 t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) 139 140 t.Run("throws error on network error", func(t *testing.T) { 141 // Setup mock api 142 defer gock.OffAll() 143 gock.New(utils.DefaultApiHost). 144 Get("/v1/projects/" + project + "/functions/" + slug). 145 Reply(http.StatusOK). 146 JSON(api.FunctionResponse{Id: "1"}) 147 gock.New(utils.DefaultApiHost). 148 Get("/v1/projects/" + project + "/functions/" + slug + "/body"). 149 ReplyError(errors.New("network error")) 150 // Run test 151 err := downloadFunction(context.Background(), project, slug, "") 152 // Check error 153 assert.ErrorContains(t, err, "network error") 154 }) 155 156 t.Run("throws error on service unavailable", func(t *testing.T) { 157 // Setup mock api 158 defer gock.OffAll() 159 gock.New(utils.DefaultApiHost). 160 Get("/v1/projects/" + project + "/functions/" + slug). 161 Reply(http.StatusOK). 162 JSON(api.FunctionResponse{Id: "1"}) 163 gock.New(utils.DefaultApiHost). 164 Get("/v1/projects/" + project + "/functions/" + slug + "/body"). 165 Reply(http.StatusServiceUnavailable) 166 // Run test 167 err := downloadFunction(context.Background(), project, slug, "") 168 // Check error 169 assert.ErrorContains(t, err, "Unexpected error downloading Function:") 170 }) 171 172 t.Run("throws error on extract failure", func(t *testing.T) { 173 // Setup deno error 174 t.Setenv("TEST_DENO_ERROR", "extract failed") 175 var body bytes.Buffer 176 archive := zip.NewWriter(&body) 177 w, err := archive.Create("deno") 178 require.NoError(t, err) 179 _, err = w.Write([]byte("binary")) 180 require.NoError(t, err) 181 require.NoError(t, archive.Close()) 182 // Setup mock api 183 defer gock.OffAll() 184 gock.New(utils.DefaultApiHost). 185 Get("/v1/projects/" + project + "/functions/" + slug). 186 Reply(http.StatusOK). 187 JSON(api.FunctionResponse{Id: "1"}) 188 gock.New(utils.DefaultApiHost). 189 Get("/v1/projects/" + project + "/functions/" + slug + "/body"). 190 Reply(http.StatusOK) 191 // Run test 192 err = downloadFunction(context.Background(), project, slug, "") 193 // Check error 194 assert.ErrorContains(t, err, "Error downloading function: exit status 1\nextract failed\n") 195 assert.Empty(t, apitest.ListUnmatchedRequests()) 196 }) 197 } 198 199 func TestGetMetadata(t *testing.T) { 200 const slug = "test-func" 201 project := apitest.RandomProjectRef() 202 // Setup valid access token 203 token := apitest.RandomAccessToken(t) 204 t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) 205 206 t.Run("fallback to default paths", func(t *testing.T) { 207 // Setup mock api 208 defer gock.OffAll() 209 gock.New(utils.DefaultApiHost). 210 Get("/v1/projects/" + project + "/functions/" + slug). 211 Reply(http.StatusOK). 212 JSON(api.FunctionResponse{Id: "1"}) 213 // Run test 214 meta, err := getFunctionMetadata(context.Background(), project, slug) 215 // Check error 216 assert.NoError(t, err) 217 assert.Equal(t, legacyEntrypointPath, *meta.EntrypointPath) 218 assert.Equal(t, legacyImportMapPath, *meta.ImportMapPath) 219 }) 220 221 t.Run("throws error on network error", func(t *testing.T) { 222 // Setup mock api 223 defer gock.OffAll() 224 gock.New(utils.DefaultApiHost). 225 Get("/v1/projects/" + project + "/functions/" + slug). 226 ReplyError(errors.New("network error")) 227 // Run test 228 meta, err := getFunctionMetadata(context.Background(), project, slug) 229 // Check error 230 assert.ErrorContains(t, err, "network error") 231 assert.Nil(t, meta) 232 }) 233 234 t.Run("throws error on service unavailable", func(t *testing.T) { 235 // Setup mock api 236 defer gock.OffAll() 237 gock.New(utils.DefaultApiHost). 238 Get("/v1/projects/" + project + "/functions/" + slug). 239 Reply(http.StatusServiceUnavailable) 240 // Run test 241 meta, err := getFunctionMetadata(context.Background(), project, slug) 242 // Check error 243 assert.ErrorContains(t, err, "Failed to download Function test-func on the Supabase project:") 244 assert.Nil(t, meta) 245 }) 246 }