supabase-cli/internal/stop/stop_test.go

246 lines
8.6 KiB
Go

package stop
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"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"
)
func TestStopCommand(t *testing.T) {
t.Run("stops containers with backup", 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/json").
Reply(http.StatusOK).
JSON([]types.Container{})
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/containers/prune").
Reply(http.StatusOK).
JSON(container.PruneReport{})
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/networks/prune").
Reply(http.StatusOK).
JSON(network.PruneReport{})
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/volumes").
Reply(http.StatusOK).
JSON(volume.ListResponse{Volumes: []*volume.Volume{{
Name: utils.DbId,
}}})
// Run test
err := Run(context.Background(), true, "", false, fsys)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("stops all instances when --all flag is used", 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()
projects := []string{"project1", "project2"}
// Mock initial ContainerList for all containers
gock.New(utils.Docker.DaemonHost()).
Get("/v"+utils.Docker.ClientVersion()+"/containers/json").
MatchParam("all", "true").
Reply(http.StatusOK).
JSON([]types.Container{
{ID: "container1", Labels: map[string]string{utils.CliProjectLabel: "project1"}},
{ID: "container2", Labels: map[string]string{utils.CliProjectLabel: "project2"}},
})
// Mock initial VolumeList
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/volumes").
Reply(http.StatusOK).
JSON(volume.ListResponse{
Volumes: []*volume.Volume{
{Name: "volume1", Labels: map[string]string{utils.CliProjectLabel: "project1"}},
{Name: "volume2", Labels: map[string]string{utils.CliProjectLabel: "project2"}},
},
})
// Mock stopOneProject for each project
for _, projectId := range projects {
// Mock ContainerList for each project
gock.New(utils.Docker.DaemonHost()).
Get("/v"+utils.Docker.ClientVersion()+"/containers/json").
MatchParam("all", "1").
MatchParam("filters", fmt.Sprintf(`{"label":{"com.supabase.cli.project=%s":true}}`, projectId)).
Reply(http.StatusOK).
JSON([]types.Container{{ID: "container-" + projectId, State: "running"}})
// Mock container stop
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/containers/container-" + projectId + "/stop").
Reply(http.StatusOK)
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/containers/prune").
Reply(http.StatusOK).
JSON(container.PruneReport{})
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/networks/prune").
Reply(http.StatusOK).
JSON(network.PruneReport{})
gock.New(utils.Docker.DaemonHost()).
Get("/v"+utils.Docker.ClientVersion()+"/volumes").
MatchParam("filters", fmt.Sprintf(`{"label":{"com.supabase.cli.project=%s":true}}`, projectId)).
Reply(http.StatusOK).
JSON(volume.ListResponse{Volumes: []*volume.Volume{{Name: "volume-" + projectId}}})
}
// Mock final ContainerList to verify all containers are stopped
gock.New(utils.Docker.DaemonHost()).
Get("/v"+utils.Docker.ClientVersion()+"/containers/json").
MatchParam("all", "true").
Reply(http.StatusOK).
JSON([]types.Container{})
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/json").
Reply(http.StatusOK).
JSON([]types.Container{})
// Run test
err := Run(context.Background(), true, "", true, fsys)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
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(), false, "", false, fsys)
// Check error
assert.ErrorContains(t, err, "toml: expected = after a key, but the document ends there")
})
t.Run("throws error on stop failure", 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/json").
Reply(http.StatusServiceUnavailable)
// Run test
err := Run(context.Background(), false, "test", false, afero.NewReadOnlyFs(fsys))
// Check error
assert.ErrorContains(t, err, "request returned Service Unavailable for API route and version")
assert.Empty(t, apitest.ListUnmatchedRequests())
})
}
func TestStopServices(t *testing.T) {
t.Run("stops all services", func(t *testing.T) {
containers := []types.Container{{ID: "c1", State: "running"}, {ID: "c2"}}
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/json").
Reply(http.StatusOK).
JSON(containers)
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/containers/" + containers[0].ID + "/stop").
Reply(http.StatusOK)
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/containers/prune").
Reply(http.StatusOK).
JSON(container.PruneReport{})
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/networks/prune").
Reply(http.StatusOK).
JSON(network.PruneReport{})
// Run test
err := stop(context.Background(), true, io.Discard, utils.Config.ProjectId)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("removes data volumes", func(t *testing.T) {
utils.DbId = "test-db"
utils.ConfigId = "test-config"
utils.StorageId = "test-storage"
utils.EdgeRuntimeId = "test-functions"
utils.InbucketId = "test-inbucket"
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
apitest.MockDockerStop(utils.Docker)
// Run test
err := stop(context.Background(), false, io.Discard, utils.Config.ProjectId)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("skips all filter when removing data volumes with Docker version pre-v1.42", func(t *testing.T) {
utils.DbId = "test-db"
utils.ConfigId = "test-config"
utils.StorageId = "test-storage"
utils.EdgeRuntimeId = "test-functions"
utils.InbucketId = "test-inbucket"
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
require.NoError(t, client.WithVersion("1.41")(utils.Docker))
defer gock.OffAll()
apitest.MockDockerStop(utils.Docker)
// Run test
err := stop(context.Background(), false, io.Discard, utils.Config.ProjectId)
// Check error
assert.NoError(t, err)
assert.Empty(t, apitest.ListUnmatchedRequests())
})
t.Run("throws error on prune failure", func(t *testing.T) {
// Setup mock docker
require.NoError(t, apitest.MockDocker(utils.Docker))
defer gock.OffAll()
gock.New(utils.Docker.DaemonHost()).
Get("/v" + utils.Docker.ClientVersion() + "/containers/json").
Reply(http.StatusOK).
JSON([]types.Container{})
gock.New(utils.Docker.DaemonHost()).
Post("/v" + utils.Docker.ClientVersion() + "/containers/prune").
ReplyError(errors.New("network error"))
// Run test
err := stop(context.Background(), true, io.Discard, utils.Config.ProjectId)
// Check error
assert.ErrorContains(t, err, "network error")
assert.Empty(t, apitest.ListUnmatchedRequests())
})
}