package download import ( "context" "errors" "fmt" "log" "net/http" "os" "testing" "github.com/h2non/gock" "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" ) func TestMain(m *testing.M) { // Setup fake deno binary if len(os.Args) > 1 && (os.Args[1] == "bundle" || os.Args[1] == "upgrade" || os.Args[1] == "run") { msg := os.Getenv("TEST_DENO_ERROR") if msg != "" { fmt.Fprintln(os.Stderr, msg) os.Exit(1) } os.Exit(0) } denoPath, err := os.Executable() if err != nil { log.Fatalln(err) } utils.DenoPathOverride = denoPath // Run test suite os.Exit(m.Run()) } func TestDownloadCommand(t *testing.T) { const slug = "test-func" t.Run("downloads eszip bundle", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Setup valid project ref project := apitest.RandomProjectRef() // Setup valid access token token := apitest.RandomAccessToken(t) t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup valid deno path _, err := fsys.Create(utils.DenoPathOverride) require.NoError(t, err) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). Reply(http.StatusOK). JSON(api.FunctionResponse{Id: "1"}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug + "/body"). Reply(http.StatusOK) // Run test err = Run(context.Background(), slug, project, true, fsys) // Check error assert.NoError(t, err) assert.Empty(t, apitest.ListUnmatchedRequests()) }) t.Run("throws error on malformed slug", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Setup valid project ref project := apitest.RandomProjectRef() // Run test err := Run(context.Background(), "@", project, true, fsys) // Check error assert.ErrorContains(t, err, "Invalid Function name.") }) t.Run("throws error on failure to install deno", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) // Setup valid project ref project := apitest.RandomProjectRef() // Run test err := Run(context.Background(), slug, project, true, fsys) // Check error assert.ErrorContains(t, err, "operation not permitted") }) t.Run("throws error on copy failure", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Setup valid project ref project := apitest.RandomProjectRef() // Setup valid deno path _, err := fsys.Create(utils.DenoPathOverride) require.NoError(t, err) // Run test err = Run(context.Background(), slug, project, true, afero.NewReadOnlyFs(fsys)) // Check error assert.ErrorContains(t, err, "operation not permitted") }) t.Run("throws error on missing function", func(t *testing.T) { // Setup in-memory fs fsys := afero.NewMemMapFs() // Setup valid project ref project := apitest.RandomProjectRef() // Setup valid access token token := apitest.RandomAccessToken(t) t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) // Setup valid deno path _, err := fsys.Create(utils.DenoPathOverride) require.NoError(t, err) // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). Reply(http.StatusNotFound). JSON(map[string]string{"message": "Function not found"}) // Run test err = Run(context.Background(), slug, project, true, fsys) // Check error assert.ErrorContains(t, err, "Function test-func does not exist on the Supabase project.") }) } func TestDownloadFunction(t *testing.T) { const slug = "test-func" // Setup valid project ref project := apitest.RandomProjectRef() // Setup valid access token token := apitest.RandomAccessToken(t) t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) t.Run("throws error on network error", func(t *testing.T) { // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). Reply(http.StatusOK). JSON(api.FunctionResponse{Id: "1"}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug + "/body"). ReplyError(errors.New("network error")) // Run test err := downloadFunction(context.Background(), project, slug, "") // Check error assert.ErrorContains(t, err, "network error") }) t.Run("throws error on service unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). Reply(http.StatusOK). JSON(api.FunctionResponse{Id: "1"}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug + "/body"). Reply(http.StatusServiceUnavailable) // Run test err := downloadFunction(context.Background(), project, slug, "") // Check error assert.ErrorContains(t, err, "Unexpected error downloading Function:") }) t.Run("throws error on extract failure", func(t *testing.T) { // Setup deno error t.Setenv("TEST_DENO_ERROR", "extract failed") // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). Reply(http.StatusOK). JSON(api.FunctionResponse{Id: "1"}) gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug + "/body"). Reply(http.StatusOK) // Run test err := downloadFunction(context.Background(), project, slug, "") // Check error assert.ErrorContains(t, err, "Error downloading function: exit status 1\nextract failed\n") assert.Empty(t, apitest.ListUnmatchedRequests()) }) } func TestGetMetadata(t *testing.T) { const slug = "test-func" project := apitest.RandomProjectRef() // Setup valid access token token := apitest.RandomAccessToken(t) t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) t.Run("fallback to default paths", func(t *testing.T) { // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). Reply(http.StatusOK). JSON(api.FunctionResponse{Id: "1"}) // Run test meta, err := getFunctionMetadata(context.Background(), project, slug) // Check error assert.NoError(t, err) assert.Equal(t, legacyEntrypointPath, *meta.EntrypointPath) assert.Equal(t, legacyImportMapPath, *meta.ImportMapPath) }) t.Run("throws error on network error", func(t *testing.T) { // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). ReplyError(errors.New("network error")) // Run test meta, err := getFunctionMetadata(context.Background(), project, slug) // Check error assert.ErrorContains(t, err, "network error") assert.Nil(t, meta) }) t.Run("throws error on service unavailable", func(t *testing.T) { // Setup mock api defer gock.OffAll() gock.New(utils.DefaultApiHost). Get("/v1/projects/" + project + "/functions/" + slug). Reply(http.StatusServiceUnavailable) // Run test meta, err := getFunctionMetadata(context.Background(), project, slug) // Check error assert.ErrorContains(t, err, "Failed to download Function test-func on the Supabase project:") assert.Nil(t, meta) }) }