github.com/supabase/cli@v1.168.1/internal/start/start_test.go (about) 1 package start 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "net/http" 8 "os" 9 "regexp" 10 "testing" 11 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/volume" 14 "github.com/jackc/pgconn" 15 "github.com/spf13/afero" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 "github.com/supabase/cli/internal/testing/apitest" 19 "github.com/supabase/cli/internal/testing/pgtest" 20 "github.com/supabase/cli/internal/utils" 21 "gopkg.in/h2non/gock.v1" 22 ) 23 24 func TestStartCommand(t *testing.T) { 25 t.Run("throws error on missing config", func(t *testing.T) { 26 err := Run(context.Background(), afero.NewMemMapFs(), []string{}, false) 27 assert.ErrorIs(t, err, os.ErrNotExist) 28 }) 29 30 t.Run("throws error on invalid config", func(t *testing.T) { 31 // Setup in-memory fs 32 fsys := afero.NewMemMapFs() 33 require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644)) 34 // Run test 35 err := Run(context.Background(), fsys, []string{}, false) 36 // Check error 37 assert.ErrorContains(t, err, "toml: line 0: unexpected EOF; expected key separator '='") 38 }) 39 40 t.Run("throws error on missing docker", func(t *testing.T) { 41 // Setup in-memory fs 42 fsys := afero.NewMemMapFs() 43 require.NoError(t, utils.WriteConfig(fsys, false)) 44 // Setup mock docker 45 require.NoError(t, apitest.MockDocker(utils.Docker)) 46 defer gock.OffAll() 47 gock.New(utils.Docker.DaemonHost()). 48 Get("/v" + utils.Docker.ClientVersion() + "/containers"). 49 ReplyError(errors.New("network error")) 50 // Run test 51 err := Run(context.Background(), fsys, []string{}, false) 52 // Check error 53 assert.ErrorContains(t, err, "network error") 54 assert.Empty(t, apitest.ListUnmatchedRequests()) 55 }) 56 57 t.Run("noop if database is already running", func(t *testing.T) { 58 // Setup in-memory fs 59 fsys := afero.NewMemMapFs() 60 require.NoError(t, utils.WriteConfig(fsys, false)) 61 // Setup mock docker 62 require.NoError(t, apitest.MockDocker(utils.Docker)) 63 defer gock.OffAll() 64 gock.New(utils.Docker.DaemonHost()). 65 Get("/v" + utils.Docker.ClientVersion() + "/containers"). 66 Reply(http.StatusOK). 67 JSON(types.ContainerJSON{}) 68 // Run test 69 err := Run(context.Background(), fsys, []string{}, false) 70 // Check error 71 assert.NoError(t, err) 72 assert.Empty(t, apitest.ListUnmatchedRequests()) 73 }) 74 } 75 76 func TestDatabaseStart(t *testing.T) { 77 t.Run("starts database locally", func(t *testing.T) { 78 // Setup in-memory fs 79 fsys := afero.NewMemMapFs() 80 // Setup mock docker 81 require.NoError(t, apitest.MockDocker(utils.Docker)) 82 defer gock.OffAll() 83 gock.New(utils.Docker.DaemonHost()). 84 Post("/v" + utils.Docker.ClientVersion() + "/networks/create"). 85 Reply(http.StatusCreated). 86 JSON(types.NetworkCreateResponse{}) 87 // Caches all dependencies 88 utils.Config.Db.Image = utils.Pg15Image 89 imageUrl := utils.GetRegistryImageUrl(utils.Config.Db.Image) 90 gock.New(utils.Docker.DaemonHost()). 91 Get("/v" + utils.Docker.ClientVersion() + "/images/" + imageUrl + "/json"). 92 Reply(http.StatusOK). 93 JSON(types.ImageInspect{}) 94 for _, image := range utils.ServiceImages { 95 service := utils.GetRegistryImageUrl(image) 96 gock.New(utils.Docker.DaemonHost()). 97 Get("/v" + utils.Docker.ClientVersion() + "/images/" + service + "/json"). 98 Reply(http.StatusOK). 99 JSON(types.ImageInspect{}) 100 } 101 // Start postgres 102 utils.DbId = "test-postgres" 103 utils.ConfigId = "test-config" 104 utils.Config.Db.Port = 54322 105 utils.Config.Db.MajorVersion = 15 106 gock.New(utils.Docker.DaemonHost()). 107 Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). 108 Reply(http.StatusNotFound) 109 apitest.MockDockerStart(utils.Docker, imageUrl, utils.DbId) 110 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.RealtimeImage), "test-realtime") 111 require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-realtime", "")) 112 utils.Config.Storage.Image = utils.StorageImage 113 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.StorageImage), "test-storage") 114 require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-storage", "")) 115 utils.Config.Auth.Image = utils.GotrueImage 116 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.GotrueImage), "test-auth") 117 require.NoError(t, apitest.MockDockerLogs(utils.Docker, "test-auth", "")) 118 // Start services 119 utils.KongId = "test-kong" 120 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.KongImage), utils.KongId) 121 utils.GotrueId = "test-gotrue" 122 utils.Config.Auth.EnableSignup = true 123 utils.Config.Auth.Email.EnableSignup = true 124 utils.Config.Auth.Email.DoubleConfirmChanges = true 125 utils.Config.Auth.Email.EnableConfirmations = true 126 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.GotrueImage), utils.GotrueId) 127 utils.InbucketId = "test-inbucket" 128 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.InbucketImage), utils.InbucketId) 129 utils.RealtimeId = "test-realtime" 130 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.RealtimeImage), utils.RealtimeId) 131 utils.RestId = "test-rest" 132 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.PostgrestImage), utils.RestId) 133 utils.StorageId = "test-storage" 134 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.StorageImage), utils.StorageId) 135 utils.ImgProxyId = "test-imgproxy" 136 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.ImageProxyImage), utils.ImgProxyId) 137 utils.DifferId = "test-differ" 138 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.DifferImage), utils.DifferId) 139 utils.EdgeRuntimeId = "test-edge-runtime" 140 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.EdgeRuntimeImage), utils.EdgeRuntimeId) 141 utils.PgmetaId = "test-pgmeta" 142 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.PgmetaImage), utils.PgmetaId) 143 utils.StudioId = "test-studio" 144 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.StudioImage), utils.StudioId) 145 utils.LogflareId = "test-logflare" 146 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.LogflareImage), utils.LogflareId) 147 // Setup mock postgres 148 conn := pgtest.NewConn() 149 defer conn.Close(t) 150 // Setup health probes 151 started := []string{ 152 utils.DbId, utils.KongId, utils.GotrueId, utils.InbucketId, utils.RealtimeId, 153 utils.StorageId, utils.ImgProxyId, utils.EdgeRuntimeId, utils.PgmetaId, utils.StudioId, 154 utils.LogflareId, utils.RestId, 155 } 156 for _, container := range started { 157 gock.New(utils.Docker.DaemonHost()). 158 Get("/v" + utils.Docker.ClientVersion() + "/containers/" + container + "/json"). 159 Reply(http.StatusOK). 160 JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ 161 State: &types.ContainerState{ 162 Running: true, 163 Health: &types.Health{Status: "healthy"}, 164 }, 165 }}) 166 } 167 gock.New("127.0.0.1"). 168 Head("/rest-admin/v1/ready"). 169 Reply(http.StatusOK) 170 gock.New("127.0.0.1"). 171 Head("/functions/v1/_internal/health"). 172 Reply(http.StatusOK) 173 // Run test 174 err := utils.RunProgram(context.Background(), func(p utils.Program, ctx context.Context) error { 175 return run(p, context.Background(), fsys, []string{}, pgconn.Config{Host: utils.DbId}, conn.Intercept) 176 }) 177 // Check error 178 assert.NoError(t, err) 179 assert.Empty(t, apitest.ListUnmatchedRequests()) 180 }) 181 182 t.Run("skips excluded containers", func(t *testing.T) { 183 // Setup in-memory fs 184 fsys := afero.NewMemMapFs() 185 // Setup mock docker 186 require.NoError(t, apitest.MockDocker(utils.Docker)) 187 defer gock.OffAll() 188 gock.New(utils.Docker.DaemonHost()). 189 Post("/v" + utils.Docker.ClientVersion() + "/networks/create"). 190 Reply(http.StatusCreated). 191 JSON(types.NetworkCreateResponse{}) 192 // Caches all dependencies 193 utils.Config.Db.Image = utils.Pg15Image 194 imageUrl := utils.GetRegistryImageUrl(utils.Config.Db.Image) 195 gock.New(utils.Docker.DaemonHost()). 196 Get("/v" + utils.Docker.ClientVersion() + "/images/" + imageUrl + "/json"). 197 Reply(http.StatusOK). 198 JSON(types.ImageInspect{}) 199 // Start postgres 200 utils.DbId = "test-postgres" 201 utils.ConfigId = "test-config" 202 utils.Config.Db.Port = 54322 203 utils.Config.Db.MajorVersion = 15 204 gock.New(utils.Docker.DaemonHost()). 205 Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). 206 Reply(http.StatusOK). 207 JSON(volume.Volume{}) 208 apitest.MockDockerStart(utils.Docker, imageUrl, utils.DbId) 209 gock.New(utils.Docker.DaemonHost()). 210 Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). 211 Reply(http.StatusOK). 212 JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ 213 State: &types.ContainerState{ 214 Running: true, 215 Health: &types.Health{Status: "healthy"}, 216 }, 217 }}) 218 // Run test 219 exclude := ExcludableContainers() 220 exclude = append(exclude, "invalid", exclude[0]) 221 err := utils.RunProgram(context.Background(), func(p utils.Program, ctx context.Context) error { 222 return run(p, context.Background(), fsys, exclude, pgconn.Config{Host: utils.DbId}) 223 }) 224 // Check error 225 assert.NoError(t, err) 226 assert.Empty(t, apitest.ListUnmatchedRequests()) 227 }) 228 } 229 230 func TestFormatMapForEnvConfig(t *testing.T) { 231 t.Run("It produces the correct format and removes the trailing comma", func(t *testing.T) { 232 output := bytes.Buffer{} 233 input := map[string]string{} 234 235 keys := [4]string{"123456", "234567", "345678", "456789"} 236 values := [4]string{"123456", "234567", "345678", "456789"} 237 expected := [4]string{ 238 `^\w{6}:\w{6}$`, 239 `^\w{6}:\w{6},\w{6}:\w{6}$`, 240 `^\w{6}:\w{6},\w{6}:\w{6},\w{6}:\w{6}$`, 241 `^\w{6}:\w{6},\w{6}:\w{6},\w{6}:\w{6},\w{6}:\w{6}$`, 242 } 243 formatMapForEnvConfig(input, &output) 244 if len(output.Bytes()) > 0 { 245 t.Error("No values should be expected when empty map is provided") 246 } 247 for i := 0; i < 4; i++ { 248 output.Reset() 249 input[keys[i]] = values[i] 250 formatMapForEnvConfig(input, &output) 251 result := output.String() 252 assert.Regexp(t, regexp.MustCompile(expected[i]), result) 253 } 254 }) 255 }