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  }