github.com/Redstoneguy129/cli@v0.0.0-20230211220159-15dca4e91917/internal/db/start/start_test.go (about) 1 package start 2 3 import ( 4 "context" 5 "errors" 6 "io" 7 "io/fs" 8 "net/http" 9 "os" 10 "strings" 11 "testing" 12 13 "github.com/Redstoneguy129/cli/internal/testing/apitest" 14 "github.com/Redstoneguy129/cli/internal/testing/pgtest" 15 "github.com/Redstoneguy129/cli/internal/utils" 16 "github.com/Redstoneguy129/cli/internal/utils/parser" 17 "github.com/docker/docker/api/types" 18 "github.com/jackc/pgerrcode" 19 "github.com/spf13/afero" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 "gopkg.in/h2non/gock.v1" 23 ) 24 25 type StatErrorFs struct { 26 afero.MemMapFs 27 DenyPath string 28 } 29 30 func (m *StatErrorFs) Stat(name string) (fs.FileInfo, error) { 31 if strings.HasPrefix(name, m.DenyPath) { 32 return nil, fs.ErrPermission 33 } 34 return m.MemMapFs.Stat(name) 35 } 36 37 type OpenErrorFs struct { 38 afero.MemMapFs 39 DenyPath string 40 } 41 42 func (m *OpenErrorFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { 43 if strings.HasPrefix(name, m.DenyPath) { 44 return nil, fs.ErrPermission 45 } 46 return m.MemMapFs.OpenFile(name, flag, perm) 47 } 48 49 func TestInitDatabase(t *testing.T) { 50 t.Run("throws error on permission denied", func(t *testing.T) { 51 // Setup in-memory fs 52 fsys := afero.NewReadOnlyFs(afero.NewMemMapFs()) 53 // Run test 54 err := initDatabase(context.Background(), fsys, io.Discard) 55 // Check error 56 assert.ErrorContains(t, err, "operation not permitted") 57 }) 58 59 t.Run("throws error on stat failure", func(t *testing.T) { 60 // Setup in-memory fs 61 fsys := &StatErrorFs{DenyPath: utils.CurrBranchPath} 62 // Setup mock docker 63 // Run test 64 err := initDatabase(context.Background(), fsys, io.Discard) 65 // Check error 66 assert.ErrorContains(t, err, "permission denied") 67 }) 68 69 t.Run("throws error on write failure", func(t *testing.T) { 70 // Setup in-memory fs 71 fsys := &OpenErrorFs{DenyPath: utils.CurrBranchPath} 72 // Run test 73 err := initDatabase(context.Background(), fsys, io.Discard) 74 // Check error 75 assert.ErrorContains(t, err, "permission denied") 76 }) 77 78 t.Run("throws error on connect failure", func(t *testing.T) { 79 utils.Config.Db.Port = 0 80 // Setup in-memory fs 81 fsys := afero.NewMemMapFs() 82 // Run test 83 err := initDatabase(context.Background(), fsys, io.Discard) 84 // Check error 85 assert.ErrorContains(t, err, "invalid port") 86 }) 87 88 t.Run("throws error on exec failure", func(t *testing.T) { 89 utils.Config.Db.Port = 5432 90 // Setup in-memory fs 91 fsys := afero.NewMemMapFs() 92 // Setup mock postgres 93 conn := pgtest.NewConn() 94 defer conn.Close(t) 95 globals, err := parser.SplitAndTrim(strings.NewReader(utils.GlobalsSql)) 96 require.NoError(t, err) 97 for _, line := range globals { 98 trim := strings.TrimSpace(strings.TrimRight(line, ";")) 99 if len(trim) > 0 { 100 conn.Query(trim) 101 } 102 } 103 conn.ReplyError(pgerrcode.DuplicateObject, `role "postgres" already exists`) 104 // Run test 105 err = initDatabase(context.Background(), fsys, io.Discard, conn.Intercept) 106 // Check error 107 assert.ErrorContains(t, err, `ERROR: role "postgres" already exists (SQLSTATE 42710)`) 108 }) 109 } 110 111 func TestStartDatabase(t *testing.T) { 112 t.Cleanup(func() { 113 utils.Containers = []string{} 114 utils.Volumes = []string{} 115 }) 116 117 t.Run("initialise main branch", func(t *testing.T) { 118 utils.DbImage = utils.Pg15Image 119 utils.Config.Db.MajorVersion = 15 120 utils.DbId = "supabase_db_test" 121 utils.Config.Db.Port = 5432 122 utils.InitialSchemaSql = "CREATE SCHEMA public" 123 // Setup in-memory fs 124 fsys := afero.NewMemMapFs() 125 // Setup mock docker 126 require.NoError(t, apitest.MockDocker(utils.Docker)) 127 defer gock.OffAll() 128 gock.New(utils.Docker.DaemonHost()). 129 Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). 130 Reply(http.StatusNotFound). 131 JSON(types.Volume{}) 132 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.DbImage), utils.DbId) 133 gock.New(utils.Docker.DaemonHost()). 134 Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). 135 Reply(http.StatusOK). 136 JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ 137 State: &types.ContainerState{ 138 Running: true, 139 Health: &types.Health{Status: "healthy"}, 140 }, 141 }}) 142 // Setup mock postgres 143 conn := pgtest.NewConn() 144 defer conn.Close(t) 145 globals, err := parser.SplitAndTrim(strings.NewReader(utils.GlobalsSql)) 146 require.NoError(t, err) 147 for _, line := range globals { 148 trim := strings.TrimSpace(strings.TrimRight(line, ";")) 149 if len(trim) > 0 { 150 conn.Query(trim) 151 } 152 } 153 conn.Query(utils.InitialSchemaSql).Reply("CREATE SCHEMA") 154 // Run test 155 err = StartDatabase(context.Background(), fsys, io.Discard, conn.Intercept) 156 // Check error 157 assert.NoError(t, err) 158 assert.Empty(t, apitest.ListUnmatchedRequests()) 159 // Check current branch 160 contents, err := afero.ReadFile(fsys, utils.CurrBranchPath) 161 assert.NoError(t, err) 162 assert.Equal(t, []byte("main"), contents) 163 }) 164 165 t.Run("recover from backup volume", func(t *testing.T) { 166 utils.DbImage = utils.Pg15Image 167 utils.Config.Db.MajorVersion = 15 168 utils.DbId = "supabase_db_test" 169 // Setup in-memory fs 170 fsys := afero.NewMemMapFs() 171 // Setup mock docker 172 require.NoError(t, apitest.MockDocker(utils.Docker)) 173 defer gock.OffAll() 174 gock.New(utils.Docker.DaemonHost()). 175 Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). 176 Reply(http.StatusOK). 177 JSON(types.Volume{}) 178 apitest.MockDockerStart(utils.Docker, utils.GetRegistryImageUrl(utils.DbImage), utils.DbId) 179 gock.New(utils.Docker.DaemonHost()). 180 Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). 181 Reply(http.StatusOK). 182 JSON(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{ 183 State: &types.ContainerState{ 184 Running: true, 185 Health: &types.Health{Status: "healthy"}, 186 }, 187 }}) 188 // Run test 189 err := StartDatabase(context.Background(), fsys, io.Discard) 190 // Check error 191 assert.NoError(t, err) 192 assert.Empty(t, apitest.ListUnmatchedRequests()) 193 // Check current branch 194 contents, err := afero.ReadFile(fsys, utils.CurrBranchPath) 195 assert.NoError(t, err) 196 assert.Equal(t, []byte("main"), contents) 197 }) 198 199 t.Run("throws error on start failure", func(t *testing.T) { 200 utils.DbImage = utils.Pg15Image 201 utils.Config.Db.MajorVersion = 15 202 utils.DbId = "supabase_db_test" 203 // Setup in-memory fs 204 fsys := afero.NewMemMapFs() 205 // Setup mock docker 206 require.NoError(t, apitest.MockDocker(utils.Docker)) 207 defer gock.OffAll() 208 gock.New(utils.Docker.DaemonHost()). 209 Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). 210 ReplyError(errors.New("network error")) 211 gock.New(utils.Docker.DaemonHost()). 212 Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.DbImage) + "/json"). 213 Reply(http.StatusInternalServerError) 214 // Run test 215 err := StartDatabase(context.Background(), fsys, io.Discard) 216 // Check error 217 assert.ErrorContains(t, err, "request returned Internal Server Error for API route and version") 218 assert.Empty(t, apitest.ListUnmatchedRequests()) 219 }) 220 } 221 222 func TestStartCommand(t *testing.T) { 223 t.Run("throws error on missing config", func(t *testing.T) { 224 // Setup in-memory fs 225 fsys := afero.NewMemMapFs() 226 // Run test 227 err := Run(context.Background(), fsys) 228 // Check error 229 assert.ErrorIs(t, err, os.ErrNotExist) 230 }) 231 232 t.Run("throws error on missing docker", func(t *testing.T) { 233 // Setup in-memory fs 234 fsys := afero.NewMemMapFs() 235 require.NoError(t, utils.WriteConfig(fsys, false)) 236 // Setup mock docker 237 require.NoError(t, apitest.MockDocker(utils.Docker)) 238 defer gock.OffAll() 239 gock.New(utils.Docker.DaemonHost()). 240 Head("/_ping"). 241 ReplyError(errors.New("network error")) 242 gock.New(utils.Docker.DaemonHost()). 243 Get("/_ping"). 244 ReplyError(errors.New("network error")) 245 // Run test 246 err := Run(context.Background(), fsys) 247 // Check error 248 assert.ErrorContains(t, err, "network error") 249 assert.Empty(t, apitest.ListUnmatchedRequests()) 250 }) 251 252 t.Run("exits if already started", func(t *testing.T) { 253 // Setup in-memory fs 254 fsys := afero.NewMemMapFs() 255 require.NoError(t, utils.WriteConfig(fsys, false)) 256 // Setup mock docker 257 require.NoError(t, apitest.MockDocker(utils.Docker)) 258 defer gock.OffAll() 259 gock.New("http:///var/run/docker.sock"). 260 Head("/_ping"). 261 Reply(http.StatusOK). 262 SetHeader("API-Version", utils.Docker.ClientVersion()). 263 SetHeader("OSType", "linux") 264 gock.New(utils.Docker.DaemonHost()). 265 Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). 266 Reply(http.StatusOK). 267 JSON(types.ContainerJSON{}) 268 // Run test 269 err := Run(context.Background(), fsys) 270 // Check error 271 assert.NoError(t, err) 272 assert.Empty(t, apitest.ListUnmatchedRequests()) 273 }) 274 275 t.Run("throws error on start failure", func(t *testing.T) { 276 // Setup in-memory fs 277 fsys := afero.NewMemMapFs() 278 require.NoError(t, utils.WriteConfig(fsys, false)) 279 // Setup mock docker 280 require.NoError(t, apitest.MockDocker(utils.Docker)) 281 defer gock.OffAll() 282 gock.New("http:///var/run/docker.sock"). 283 Head("/_ping"). 284 Reply(http.StatusOK). 285 SetHeader("API-Version", utils.Docker.ClientVersion()). 286 SetHeader("OSType", "linux") 287 gock.New(utils.Docker.DaemonHost()). 288 Get("/v" + utils.Docker.ClientVersion() + "/containers/" + utils.DbId + "/json"). 289 Reply(http.StatusNotFound) 290 // Fail to start 291 gock.New(utils.Docker.DaemonHost()). 292 Get("/v" + utils.Docker.ClientVersion() + "/volumes/" + utils.DbId). 293 ReplyError(errors.New("network error")) 294 gock.New(utils.Docker.DaemonHost()). 295 Get("/v" + utils.Docker.ClientVersion() + "/images/" + utils.GetRegistryImageUrl(utils.Pg15Image) + "/json"). 296 ReplyError(errors.New("network error")) 297 // Cleanup resources 298 gock.New(utils.Docker.DaemonHost()). 299 Delete("/v" + utils.Docker.ClientVersion() + "/networks/"). 300 Reply(http.StatusOK) 301 // Run test 302 err := Run(context.Background(), fsys) 303 // Check error 304 assert.ErrorContains(t, err, "network error") 305 assert.Empty(t, apitest.ListUnmatchedRequests()) 306 }) 307 }