supabase-cli/internal/start/start_test.go

280 lines
11 KiB
Go

package start
import (
"bytes"
"context"
"errors"
"net/http"
"regexp"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/h2non/gock"
"github.com/jackc/pgconn"
"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/config"
"github.com/supabase/cli/pkg/pgtest"
"github.com/supabase/cli/pkg/storage"
)
func TestStartCommand(t *testing.T) {
t.Run("throws error on malformed config", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644))
// Run test
err := Run(context.Background(), fsys, []string{}, false)
// Check error
assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there")
})
t.Run("throws error on missing docker", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
require.NoError(t, utils.WriteConfig(fsys, false))
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers").
ReplyError(errors.New("network error"))
// Run test
err := Run(context.Background(), fsys, []string{}, false)
// Check error
assert.ErrorContains(t, err, "network error")
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("show status if database is already running", func(t *testing.T) {
var running []types.Container
for _, name := range utils.GetDockerIds() {
running = append(running, types.Container{
Names: []string{name + "_test"},
})
}
// Setup in-memory fs
fsys := afero.NewMemMapFs()
require.NoError(t, utils.WriteConfig(fsys, false))
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers").
Reply(http.StatusOK).
JSON(types.ContainerJSON{})
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/supabase_db_start/json").
Reply(http.StatusOK).
JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
State: &types.ContainerState{Running: true},
}})
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/json").
Reply(http.StatusOK).
JSON(running)
// Run test
err := Run(context.Background(), fsys, []string{}, false)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
}
func TestDatabaseStart(t *testing.T) {
t.Run("starts database locally", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/networks/create").
Reply(http.StatusCreated).
JSON(network.CreateResponse{})
// Caches all dependencies
imageUrl := utils.GetRegistryImageUrl(utils.Config.Db.Image)
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/images/" + imageUrl + "/json").
Reply(http.StatusOK).
JSON(types.ImageInspect{})
for _, image := range config.Images.Services() {
service := utils.GetRegistryImageUrl(image)
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/images/" + service + "/json").
Reply(http.StatusOK).
JSON(types.ImageInspect{})
}
// Start postgres
utils.DbId = "test-postgres"
utils.ConfigId = "test-config"
utils.Config.Db.Port = 54322
utils.Config.Db.MajorVersion = 15
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId).
Reply(http.StatusNotFound)
apitest.MockDockerStart(utils.Docker, imageUrl, utils.DbId)
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Realtime.Image), "test-realtime")
require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-realtime", ""))
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Storage.Image), "test-storage")
require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-storage", ""))
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Auth.Image), "test-auth")
require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-auth", ""))
// Start services
utils.KongId = "test-kong"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Api.KongImage), utils.KongId)
utils.GotrueId = "test-gotrue"
utils.Config.Auth.EnableSignup = true
utils.Config.Auth.Email.EnableSignup = true
utils.Config.Auth.Email.DoubleConfirmChanges = true
utils.Config.Auth.Email.EnableConfirmations = true
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Auth.Image), utils.GotrueId)
utils.InbucketId = "test-inbucket"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Inbucket.Image), utils.InbucketId)
utils.RealtimeId = "test-realtime"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Realtime.Image), utils.RealtimeId)
utils.RestId = "test-rest"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Api.Image), utils.RestId)
utils.StorageId = "test-storage"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Storage.Image), utils.StorageId)
utils.ImgProxyId = "test-imgproxy"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Storage.ImgProxyImage), utils.ImgProxyId)
utils.EdgeRuntimeId = "test-edge-runtime"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.EdgeRuntime.Image), utils.EdgeRuntimeId)
utils.PgmetaId = "test-pgmeta"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Studio.PgmetaImage), utils.PgmetaId)
utils.StudioId = "test-studio"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Studio.Image), utils.StudioId)
utils.LogflareId = "test-logflare"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Analytics.Image), utils.LogflareId)
utils.VectorId = "test-vector"
apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.Config.Analytics.VectorImage), utils.VectorId)
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
// Setup health probes
started := []string{
utils.DbId, utils.KongId, utils.GotrueId, utils.InbucketId, utils.RealtimeId,
utils.StorageId, utils.ImgProxyId, utils.EdgeRuntimeId, utils.PgmetaId, utils.StudioId,
utils.LogflareId, utils.RestId, utils.VectorId,
}
for _, container := range started {
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/" + container + "/json").
Reply(http.StatusOK).
JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
State: &types.ContainerState{
Running: true,
Health: &types.Health{Status: types.Healthy},
},
}})
}
gock.New(utils.Config.Api.ExternalUrl).
Head("/rest-admin/v1/ready").
Reply(http.StatusOK)
gock.New(utils.Config.Api.ExternalUrl).
Head("/functions/v1/_internal/health").
Reply(http.StatusOK)
// Seed tenant services
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.StorageId + "/json").
Reply(http.StatusOK).
JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
State: &types.ContainerState{
Running: true,
Health: &types.Health{Status: types.Healthy},
},
}})
gock.New(utils.Config.Api.ExternalUrl).
Get("/storage/v1/bucket").
Reply(http.StatusOK).
JSON([]storage.BucketResponse{})
// Run test
err := utils.RunProgram(context.Background(), func(p utils.Program, ctx context.Context) error {
return run(p, context.Background(), fsys, []string{}, pgconn.Config{Host: utils.DbId}, conn.Intercept)
})
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("skips excluded containers", func(t *testing.T) {
// Setup in-memory fs
fsys := afero.NewMemMapFs()
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/networks/create").
Reply(http.StatusCreated).
JSON(network.CreateResponse{})
// Caches all dependencies
imageUrl := utils.GetRegistryImageUrl(utils.Config.Db.Image)
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/images/" + imageUrl + "/json").
Reply(http.StatusOK).
JSON(types.ImageInspect{})
// Start postgres
utils.DbId = "test-postgres"
utils.ConfigId = "test-config"
utils.Config.Db.Port = 54322
utils.Config.Db.MajorVersion = 15
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId).
Reply(http.StatusOK).
JSON(volume.Volume{})
apitest.MockDockerStart(utils.Docker, imageUrl, utils.DbId)
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json").
Reply(http.StatusOK).
JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
State: &types.ContainerState{
Running: true,
Health: &types.Health{Status: types.Healthy},
},
}})
// Run test
exclude := ExcludableContainers()
exclude = append(exclude, "invalid", exclude[0])
err := utils.RunProgram(context.Background(), func(p utils.Program, ctx context.Context) error {
return run(p, context.Background(), fsys, exclude, pgconn.Config{Host: utils.DbId})
})
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
}
func TestFormatMapForEnvConfig(t *testing.T) {
t.Run("It produces the correct format and removes the trailing comma", func(t *testing.T) {
output := bytes.Buffer{}
input := map[string]string{}
keys := [4]string{"123456", "234567", "345678", "456789"}
values := [4]string{"123456", "234567", "345678", "456789"}
expected := [4]string{
`^\w{6}:\w{6}$`,
`^\w{6}:\w{6},\w{6}:\w{6}$`,
`^\w{6}:\w{6},\w{6}:\w{6},\w{6}:\w{6}$`,
`^\w{6}:\w{6},\w{6}:\w{6},\w{6}:\w{6},\w{6}:\w{6}$`,
}
formatMapForEnvConfig(input, &output)
if len(output.Bytes()) > 0 {
t.Error("No values should be expected when empty map is provided")
}
for i := 0; i < 4; i++ {
output.Reset()
input[keys[i]] = values[i]
formatMapForEnvConfig(input, &output)
result := output.String()
assert.Regexp(t, regexp.MustCompile(expected[i]), result)
}
})
}