supabase-cli/internal/functions/deploy/upload_test.go

218 lines
6.1 KiB
Go

package deploy
import (
"bytes"
"context"
"embed"
"errors"
"io"
"mime/multipart"
"net/http"
"os"
"path"
"testing"
"github.com/h2non/gock"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"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"
)
//go:embed testdata
var testImports embed.FS
type MockFS struct {
mock.Mock
}
func (m *MockFS) ReadFile(srcPath string, w io.Writer) error {
_ = m.Called(srcPath)
data, err := testImports.ReadFile(srcPath)
if err != nil {
return err
}
if _, err := w.Write(data); err != nil {
return err
}
return nil
}
func TestImportPaths(t *testing.T) {
t.Run("iterates all import paths", func(t *testing.T) {
// Setup in-memory fs
fsys := MockFS{}
fsys.On("ReadFile", "/modules/my-module.ts").Once()
fsys.On("ReadFile", "testdata/modules/imports.ts").Once()
fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once()
// Run test
im := utils.ImportMap{}
err := walkImportPaths("testdata/modules/imports.ts", im, fsys.ReadFile)
// Check error
assert.NoError(t, err)
fsys.AssertExpectations(t)
})
t.Run("iterates with import map", func(t *testing.T) {
// Setup in-memory fs
fsys := MockFS{}
fsys.On("ReadFile", "/modules/my-module.ts").Once()
fsys.On("ReadFile", "testdata/modules/imports.ts").Once()
fsys.On("ReadFile", "testdata/geometries/Geometries.js").Once()
fsys.On("ReadFile", "testdata/shared/whatever.ts").Once()
fsys.On("ReadFile", "testdata/shared/mod.ts").Once()
fsys.On("ReadFile", "testdata/nested/index.ts").Once()
// Run test
im := utils.ImportMap{Imports: map[string]string{
"module-name/": "../shared/",
}}
err := walkImportPaths("testdata/modules/imports.ts", im, fsys.ReadFile)
// Check error
assert.NoError(t, err)
fsys.AssertExpectations(t)
})
}
func assertFormEqual(t *testing.T, actual []byte) {
snapshot := path.Join("testdata", path.Base(t.Name())+".form")
expected, err := testImports.ReadFile(snapshot)
if errors.Is(err, os.ErrNotExist) {
assert.NoError(t, os.WriteFile(snapshot, actual, 0600))
}
assert.Equal(t, string(expected), string(actual))
}
func TestWriteForm(t *testing.T) {
t.Run("writes import map", func(t *testing.T) {
var buf bytes.Buffer
form := multipart.NewWriter(&buf)
require.NoError(t, form.SetBoundary("test"))
// Setup in-memory fs
fsys := afero.FromIOFS{FS: testImports}
// Run test
err := writeForm(form, api.FunctionDeployMetadata{
Name: cast.Ptr("nested"),
VerifyJwt: cast.Ptr(true),
EntrypointPath: "testdata/nested/index.ts",
ImportMapPath: cast.Ptr("testdata/nested/deno.json"),
StaticPatterns: cast.Ptr([]string{"testdata/*/*.js"}),
}, fsys)
// Check error
assert.NoError(t, err)
assertFormEqual(t, buf.Bytes())
})
t.Run("throws error on missing file", func(t *testing.T) {
var buf bytes.Buffer
form := multipart.NewWriter(&buf)
require.NoError(t, form.SetBoundary("test"))
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Run test
err := writeForm(form, api.FunctionDeployMetadata{
ImportMapPath: cast.Ptr("testdata/import_map.json"),
}, fsys)
// Check error
assert.ErrorIs(t, err, os.ErrNotExist)
})
t.Run("throws error on directory path", func(t *testing.T) {
var buf bytes.Buffer
form := multipart.NewWriter(&buf)
require.NoError(t, form.SetBoundary("test"))
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Run test
err := writeForm(form, api.FunctionDeployMetadata{
StaticPatterns: cast.Ptr([]string{"testdata"}),
}, fsys)
// Check error
assert.ErrorContains(t, err, "file path is a directory:")
})
}
func TestDeployAll(t *testing.T) {
flags.ProjectRef = apitest.RandomProjectRef()
// Setup valid access token
token := apitest.RandomAccessToken(t)
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
t.Run("deploys single slug", func(t *testing.T) {
c := config.FunctionConfig{"demo": {
Enabled: true,
Entrypoint: "testdata/shared/whatever.ts",
}}
// Setup in-memory fs
fsys := afero.FromIOFS{FS: testImports}
// Setup mock api
defer gock.OffAll()
gock.New(utils.DefaultApiHost).
Post("/v1/projects/"+flags.ProjectRef+"/functions/deploy").
MatchParam("slug", "demo").
Reply(http.StatusCreated).
JSON(api.DeployFunctionResponse{})
// Run test
err := deploy(context.Background(), c, 1, fsys)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("deploys multiple slugs", func(t *testing.T) {
c := config.FunctionConfig{
"test-ts": {
Enabled: true,
Entrypoint: "testdata/shared/whatever.ts",
},
"test-js": {
Enabled: true,
Entrypoint: "testdata/geometries/Geometries.js",
},
}
// Setup in-memory fs
fsys := afero.FromIOFS{FS: testImports}
// Setup mock api
defer gock.OffAll()
for slug := range c {
gock.New(utils.DefaultApiHost).
Post("/v1/projects/"+flags.ProjectRef+"/functions/deploy").
MatchParam("slug", slug).
Reply(http.StatusCreated).
JSON(api.DeployFunctionResponse{Id: slug})
}
gock.New(utils.DefaultApiHost).
Put("/v1/projects/" + flags.ProjectRef + "/functions").
Reply(http.StatusOK).
JSON(api.BulkUpdateFunctionResponse{})
// Run test
err := deploy(context.Background(), c, 1, fsys)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("throws error on network failure", func(t *testing.T) {
errNetwork := errors.New("network")
c := config.FunctionConfig{"demo": {Enabled: true}}
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Setup mock api
defer gock.OffAll()
gock.New(utils.DefaultApiHost).
Post("/v1/projects/"+flags.ProjectRef+"/functions/deploy").
MatchParam("slug", "demo").
ReplyError(errNetwork)
// Run test
err := deploy(context.Background(), c, 1, fsys)
// Check error
assert.ErrorIs(t, err, errNetwork)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
}