351 lines
13 KiB
Go
351 lines
13 KiB
Go
package deploy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"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/internal/utils/flags"
|
|
"github.com/supabase/cli/pkg/api"
|
|
"github.com/supabase/cli/pkg/cast"
|
|
"github.com/supabase/cli/pkg/config"
|
|
)
|
|
|
|
func TestDeployCommand(t *testing.T) {
|
|
flags.ProjectRef = apitest.RandomProjectRef()
|
|
const slug = "test-func"
|
|
const containerId = "test-container"
|
|
imageUrl := utils.GetRegistryImageUrl(utils.Config.EdgeRuntime.Image)
|
|
|
|
t.Run("deploys multiple functions", func(t *testing.T) {
|
|
functions := []string{slug, slug + "-2"}
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// 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/" + flags.ProjectRef + "/functions").
|
|
Reply(http.StatusOK).
|
|
JSON([]api.FunctionResponse{})
|
|
for i := range functions {
|
|
// Do not match slug to avoid flakey tests
|
|
gock.New(utils.DefaultApiHost).
|
|
Post("/v1/projects/" + flags.ProjectRef + "/functions").
|
|
Reply(http.StatusCreated).
|
|
JSON(api.FunctionResponse{Id: fmt.Sprintf("%d", i)})
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
apitest.MockDockerStart(utils.Docker, imageUrl, containerId)
|
|
require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "bundled"))
|
|
}
|
|
// Setup output file
|
|
for _, v := range functions {
|
|
outputDir := filepath.Join(utils.TempDir, fmt.Sprintf(".output_%s", v))
|
|
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
|
|
}
|
|
// Run test
|
|
noVerifyJWT := true
|
|
err = Run(context.Background(), functions, true, &noVerifyJWT, "", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("deploys functions from config", func(t *testing.T) {
|
|
t.Cleanup(func() { clear(utils.Config.Functions) })
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
f, err := fsys.OpenFile(utils.ConfigPath, os.O_APPEND|os.O_WRONLY, 0600)
|
|
require.NoError(t, err)
|
|
_, err = f.WriteString(`
|
|
[functions.` + slug + `]
|
|
import_map = "./import_map.json"
|
|
`)
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.Close())
|
|
importMapPath, err := filepath.Abs(filepath.Join(utils.SupabaseDirPath, "import_map.json"))
|
|
require.NoError(t, err)
|
|
require.NoError(t, afero.WriteFile(fsys, importMapPath, []byte("{}"), 0644))
|
|
// Setup function entrypoint
|
|
entrypointPath := filepath.Join(utils.FunctionsDir, slug, "index.ts")
|
|
require.NoError(t, afero.WriteFile(fsys, entrypointPath, []byte{}, 0644))
|
|
ignorePath := filepath.Join(utils.FunctionsDir, "_ignore", "index.ts")
|
|
require.NoError(t, afero.WriteFile(fsys, ignorePath, []byte{}, 0644))
|
|
// 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/" + flags.ProjectRef + "/functions").
|
|
Reply(http.StatusOK).
|
|
JSON([]api.FunctionResponse{})
|
|
gock.New(utils.DefaultApiHost).
|
|
Post("/v1/projects/"+flags.ProjectRef+"/functions").
|
|
MatchParam("slug", slug).
|
|
ParamPresent("import_map_path").
|
|
Reply(http.StatusCreated).
|
|
JSON(api.FunctionResponse{Id: "1"})
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
apitest.MockDockerStart(utils.Docker, imageUrl, containerId)
|
|
require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "bundled"))
|
|
// Setup output file
|
|
outputDir := filepath.Join(utils.TempDir, fmt.Sprintf(".output_%s", slug))
|
|
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
|
|
// Run test
|
|
err = Run(context.Background(), nil, true, nil, "", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("skip disabled functions from config", func(t *testing.T) {
|
|
t.Cleanup(func() { clear(utils.Config.Functions) })
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
f, err := fsys.OpenFile(utils.ConfigPath, os.O_APPEND|os.O_WRONLY, 0600)
|
|
require.NoError(t, err)
|
|
_, err = f.WriteString(`
|
|
[functions.disabled-func]
|
|
enabled = false
|
|
import_map = "./import_map.json"
|
|
`)
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.Close())
|
|
importMapPath, err := filepath.Abs(filepath.Join(utils.SupabaseDirPath, "import_map.json"))
|
|
require.NoError(t, err)
|
|
require.NoError(t, afero.WriteFile(fsys, importMapPath, []byte("{}"), 0644))
|
|
// Setup function entrypoints
|
|
require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.FunctionsDir, "enabled-func", "index.ts"), []byte{}, 0644))
|
|
require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.FunctionsDir, "disabled-func", "index.ts"), []byte{}, 0644))
|
|
// 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/" + flags.ProjectRef + "/functions").
|
|
Reply(http.StatusOK).
|
|
JSON([]api.FunctionResponse{})
|
|
gock.New(utils.DefaultApiHost).
|
|
Post("/v1/projects/"+flags.ProjectRef+"/functions").
|
|
MatchParam("slug", "enabled-func").
|
|
Reply(http.StatusCreated).
|
|
JSON(api.FunctionResponse{Id: "1"})
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
apitest.MockDockerStart(utils.Docker, imageUrl, containerId)
|
|
require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "bundled"))
|
|
// Setup output file
|
|
outputDir := filepath.Join(utils.TempDir, ".output_enabled-func")
|
|
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
|
|
// Run test
|
|
err = Run(context.Background(), nil, true, nil, "", 1, 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()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Run test
|
|
err := Run(context.Background(), []string{"_invalid"}, true, nil, "", 1, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Invalid Function name.")
|
|
})
|
|
|
|
t.Run("throws error on empty functions", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
// Run test
|
|
err := Run(context.Background(), nil, true, nil, "", 1, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "No Functions specified or found in supabase/functions")
|
|
})
|
|
|
|
t.Run("verify_jwt param falls back to config", func(t *testing.T) {
|
|
t.Cleanup(func() { clear(utils.Config.Functions) })
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
f, err := fsys.OpenFile(utils.ConfigPath, os.O_APPEND|os.O_WRONLY, 0600)
|
|
require.NoError(t, err)
|
|
_, err = f.WriteString(`
|
|
[functions.` + slug + `]
|
|
verify_jwt = false
|
|
`)
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.Close())
|
|
// 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/" + flags.ProjectRef + "/functions").
|
|
Reply(http.StatusOK).
|
|
JSON([]api.FunctionResponse{})
|
|
gock.New(utils.DefaultApiHost).
|
|
Post("/v1/projects/"+flags.ProjectRef+"/functions").
|
|
MatchParam("verify_jwt", "false").
|
|
Reply(http.StatusCreated).
|
|
JSON(api.FunctionResponse{Id: "1"})
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
apitest.MockDockerStart(utils.Docker, imageUrl, containerId)
|
|
require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "bundled"))
|
|
// Setup output file
|
|
outputDir := filepath.Join(utils.TempDir, fmt.Sprintf(".output_%s", slug))
|
|
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
|
|
// Run test
|
|
assert.NoError(t, Run(context.Background(), []string{slug}, true, nil, "", 1, fsys))
|
|
// Validate api
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("verify_jwt flag overrides config", func(t *testing.T) {
|
|
t.Cleanup(func() { clear(utils.Config.Functions) })
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, utils.WriteConfig(fsys, false))
|
|
f, err := fsys.OpenFile(utils.ConfigPath, os.O_APPEND|os.O_WRONLY, 0600)
|
|
require.NoError(t, err)
|
|
_, err = f.WriteString(`
|
|
[functions.` + slug + `]
|
|
verify_jwt = false
|
|
`)
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.Close())
|
|
// 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/" + flags.ProjectRef + "/functions").
|
|
Reply(http.StatusOK).
|
|
JSON([]api.FunctionResponse{})
|
|
gock.New(utils.DefaultApiHost).
|
|
Post("/v1/projects/"+flags.ProjectRef+"/functions").
|
|
MatchParam("verify_jwt", "true").
|
|
Reply(http.StatusCreated).
|
|
JSON(api.FunctionResponse{Id: "1"})
|
|
// Setup mock docker
|
|
require.NoError(t, apitest.MockDocker(utils.Docker))
|
|
apitest.MockDockerStart(utils.Docker, imageUrl, containerId)
|
|
require.NoError(t, apitest.MockDockerLogs(utils.Docker, containerId, "bundled"))
|
|
// Setup output file
|
|
outputDir := filepath.Join(utils.TempDir, fmt.Sprintf(".output_%s", slug))
|
|
require.NoError(t, afero.WriteFile(fsys, filepath.Join(outputDir, "output.eszip"), []byte(""), 0644))
|
|
// Run test
|
|
noVerifyJwt := false
|
|
assert.NoError(t, Run(context.Background(), []string{slug}, true, &noVerifyJwt, "", 1, fsys))
|
|
// Validate api
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|
|
|
|
func TestImportMapPath(t *testing.T) {
|
|
t.Run("loads import map from default location", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, utils.FallbackImportMapPath, []byte("{}"), 0644))
|
|
// Run test
|
|
fc, err := GetFunctionConfig([]string{"test"}, "", nil, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, utils.FallbackImportMapPath, fc["test"].ImportMap)
|
|
})
|
|
|
|
t.Run("per function config takes precedence", func(t *testing.T) {
|
|
t.Cleanup(func() { clear(utils.Config.Functions) })
|
|
slug := "hello"
|
|
utils.Config.Functions = config.FunctionConfig{
|
|
slug: {ImportMap: "import_map.json"},
|
|
}
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, utils.FallbackImportMapPath, []byte("{}"), 0644))
|
|
// Run test
|
|
fc, err := GetFunctionConfig([]string{slug}, "", nil, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "import_map.json", fc[slug].ImportMap)
|
|
})
|
|
|
|
t.Run("overrides with cli flag", func(t *testing.T) {
|
|
t.Cleanup(func() { clear(utils.Config.Functions) })
|
|
slug := "hello"
|
|
utils.Config.Functions = config.FunctionConfig{
|
|
slug: {ImportMap: "import_map.json"},
|
|
}
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Custom global import map loaded via cli flag
|
|
customImportMapPath := filepath.Join(utils.FunctionsDir, "custom_import_map.json")
|
|
require.NoError(t, afero.WriteFile(fsys, customImportMapPath, []byte("{}"), 0644))
|
|
// Create fallback import map to test precedence order
|
|
require.NoError(t, afero.WriteFile(fsys, utils.FallbackImportMapPath, []byte("{}"), 0644))
|
|
// Run test
|
|
fc, err := GetFunctionConfig([]string{slug}, customImportMapPath, cast.Ptr(false), fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, customImportMapPath, fc[slug].ImportMap)
|
|
})
|
|
|
|
t.Run("returns empty string if no fallback", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
fc, err := GetFunctionConfig([]string{"test"}, "", nil, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, fc["test"].ImportMap)
|
|
})
|
|
|
|
t.Run("preserves absolute path", func(t *testing.T) {
|
|
path := "/tmp/import_map.json"
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, utils.FallbackImportMapPath, []byte("{}"), 0644))
|
|
// Run test
|
|
fc, err := GetFunctionConfig([]string{"test"}, path, nil, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, path, fc["test"].ImportMap)
|
|
})
|
|
}
|