github.com/supabase/cli@v1.168.1/internal/status/status_test.go (about)

     1  package status
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"io"
     8  	"net/http"
     9  	"os"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/docker/docker/api/types"
    14  	"github.com/spf13/afero"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  	"github.com/supabase/cli/internal/testing/apitest"
    18  	"github.com/supabase/cli/internal/utils"
    19  	"gopkg.in/h2non/gock.v1"
    20  )
    21  
    22  func TestStatusCommand(t *testing.T) {
    23  	t.Run("shows service status", func(t *testing.T) {
    24  		services := []string{
    25  			"supabase_db_",
    26  			"supabase_kong_",
    27  			"supabase_auth_",
    28  			"supabase_inbucket_",
    29  			"supabase_realtime_",
    30  			"supabase_rest_",
    31  			"supabase_storage_",
    32  			"supabase_imgproxy_",
    33  			"supabase_pg_meta_",
    34  			"supabase_studio_",
    35  			"supabase_analytics_",
    36  		}
    37  		// Setup in-memory fs
    38  		fsys := afero.NewMemMapFs()
    39  		require.NoError(t, utils.WriteConfig(fsys, false))
    40  		// Setup mock docker
    41  		require.NoError(t, apitest.MockDocker(utils.Docker))
    42  		defer gock.OffAll()
    43  		for _, container := range services {
    44  			gock.New(utils.Docker.DaemonHost()).
    45  				Get("/v" + utils.Docker.ClientVersion() + "/containers/" + container).
    46  				Reply(http.StatusOK).
    47  				JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
    48  					State: &types.ContainerState{Running: true},
    49  				}})
    50  		}
    51  		// Run test
    52  		assert.NoError(t, Run(context.Background(), CustomName{}, utils.OutputPretty, fsys))
    53  		// Check error
    54  		assert.Empty(t, apitest.ListUnmatchedRequests())
    55  	})
    56  
    57  	t.Run("throws error on missing config", func(t *testing.T) {
    58  		err := Run(context.Background(), CustomName{}, utils.OutputPretty, afero.NewMemMapFs())
    59  		assert.ErrorIs(t, err, os.ErrNotExist)
    60  	})
    61  
    62  	t.Run("throws error on invalid config", func(t *testing.T) {
    63  		// Setup in-memory fs
    64  		fsys := afero.NewMemMapFs()
    65  		require.NoError(t, afero.WriteFile(fsys, utils.ConfigPath, []byte("malformed"), 0644))
    66  		// Run test
    67  		err := Run(context.Background(), CustomName{}, utils.OutputPretty, fsys)
    68  		// Check error
    69  		assert.ErrorContains(t, err, "toml: line 0: unexpected EOF; expected key separator '='")
    70  	})
    71  
    72  	t.Run("throws error on missing docker", func(t *testing.T) {
    73  		// Setup in-memory fs
    74  		fsys := afero.NewMemMapFs()
    75  		require.NoError(t, utils.WriteConfig(fsys, false))
    76  		// Setup mock docker
    77  		require.NoError(t, apitest.MockDocker(utils.Docker))
    78  		defer gock.OffAll()
    79  		gock.New(utils.Docker.DaemonHost()).
    80  			Get("/v" + utils.Docker.ClientVersion() + "/containers/supabase_db_").
    81  			ReplyError(errors.New("network error"))
    82  		// Run test
    83  		err := Run(context.Background(), CustomName{}, utils.OutputPretty, fsys)
    84  		// Check error
    85  		assert.ErrorContains(t, err, "network error")
    86  		assert.Empty(t, apitest.ListUnmatchedRequests())
    87  	})
    88  }
    89  
    90  func TestServiceHealth(t *testing.T) {
    91  	services := []string{"supabase_db", "supabase_auth"}
    92  
    93  	t.Run("checks all services", func(t *testing.T) {
    94  		// Setup mock docker
    95  		require.NoError(t, apitest.MockDocker(utils.Docker))
    96  		defer gock.OffAll()
    97  		gock.New(utils.Docker.DaemonHost()).
    98  			Get("/v" + utils.Docker.ClientVersion() + "/containers/" + services[0] + "/json").
    99  			Reply(http.StatusOK).
   100  			JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
   101  				State: &types.ContainerState{
   102  					Running: true,
   103  					Health:  &types.Health{Status: "Unhealthy"},
   104  				},
   105  			}})
   106  		gock.New(utils.Docker.DaemonHost()).
   107  			Get("/v" + utils.Docker.ClientVersion() + "/containers/" + services[1] + "/json").
   108  			Reply(http.StatusOK).
   109  			JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{
   110  				State: &types.ContainerState{Status: "exited"},
   111  			}})
   112  		// Run test
   113  		var stderr bytes.Buffer
   114  		stopped := checkServiceHealth(context.Background(), services, &stderr)
   115  		// Check error
   116  		assert.Empty(t, stopped)
   117  		lines := strings.Split(strings.TrimSpace(stderr.String()), "\n")
   118  		assert.ElementsMatch(t, []string{
   119  			"supabase_db container is not ready: Unhealthy",
   120  			"supabase_auth container is not running: exited",
   121  		}, lines)
   122  		assert.Empty(t, apitest.ListUnmatchedRequests())
   123  	})
   124  
   125  	t.Run("throws error on missing container", func(t *testing.T) {
   126  		// Setup in-memory fs
   127  		fsys := afero.NewMemMapFs()
   128  		require.NoError(t, utils.WriteConfig(fsys, false))
   129  		// Setup mock docker
   130  		require.NoError(t, apitest.MockDocker(utils.Docker))
   131  		defer gock.OffAll()
   132  		for _, name := range services {
   133  			gock.New(utils.Docker.DaemonHost()).
   134  				Get("/v" + utils.Docker.ClientVersion() + "/containers/" + name + "/json").
   135  				Reply(http.StatusNotFound)
   136  		}
   137  		// Run test
   138  		stopped := checkServiceHealth(context.Background(), services, io.Discard)
   139  		// Check error
   140  		assert.ElementsMatch(t, services, stopped)
   141  		assert.Empty(t, apitest.ListUnmatchedRequests())
   142  	})
   143  }
   144  
   145  func TestPrintStatus(t *testing.T) {
   146  	utils.Config.Db.Port = 0
   147  	utils.Config.Hostname = "127.0.0.1"
   148  	utils.Config.Api.Enabled = false
   149  	utils.Config.Auth.Enabled = false
   150  	utils.Config.Storage.Enabled = false
   151  	utils.Config.Realtime.Enabled = false
   152  	utils.Config.Studio.Enabled = false
   153  	utils.Config.Analytics.Enabled = false
   154  	utils.Config.Inbucket.Enabled = false
   155  
   156  	t.Run("outputs env var", func(t *testing.T) {
   157  		utils.Config.Hostname = "127.0.0.1"
   158  		// Run test
   159  		var stdout bytes.Buffer
   160  		assert.NoError(t, printStatus(CustomName{DbURL: "DB_URL"}, utils.OutputEnv, &stdout))
   161  		// Check error
   162  		assert.Equal(t, "DB_URL=\"postgresql://postgres:postgres@127.0.0.1:0/postgres\"\n", stdout.String())
   163  	})
   164  
   165  	t.Run("outputs json object", func(t *testing.T) {
   166  		// Run test
   167  		var stdout bytes.Buffer
   168  		assert.NoError(t, printStatus(CustomName{DbURL: "DB_URL"}, utils.OutputJson, &stdout))
   169  		// Check error
   170  		assert.Equal(t, "{\n  \"DB_URL\": \"postgresql://postgres:postgres@127.0.0.1:0/postgres\"\n}\n", stdout.String())
   171  	})
   172  
   173  	t.Run("outputs yaml properties", func(t *testing.T) {
   174  		// Run test
   175  		var stdout bytes.Buffer
   176  		assert.NoError(t, printStatus(CustomName{DbURL: "DB_URL"}, utils.OutputYaml, &stdout))
   177  		// Check error
   178  		assert.Equal(t, "DB_URL: postgresql://postgres:postgres@127.0.0.1:0/postgres\n", stdout.String())
   179  	})
   180  
   181  	t.Run("outputs toml fields", func(t *testing.T) {
   182  		// Run test
   183  		var stdout bytes.Buffer
   184  		assert.NoError(t, printStatus(CustomName{DbURL: "DB_URL"}, utils.OutputToml, &stdout))
   185  		// Check error
   186  		assert.Equal(t, "DB_URL = \"postgresql://postgres:postgres@127.0.0.1:0/postgres\"\n", stdout.String())
   187  	})
   188  }