github.com/supabase/cli@v1.168.1/internal/storage/cp/cp_test.go (about) 1 package cp 2 3 import ( 4 "context" 5 "io/fs" 6 "net/http" 7 "testing" 8 9 "github.com/spf13/afero" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 "github.com/supabase/cli/internal/testing/apitest" 13 "github.com/supabase/cli/internal/utils" 14 "github.com/supabase/cli/internal/utils/flags" 15 "github.com/supabase/cli/pkg/api" 16 "github.com/supabase/cli/pkg/fetcher" 17 "github.com/supabase/cli/pkg/storage" 18 "gopkg.in/h2non/gock.v1" 19 ) 20 21 var mockFile = storage.ObjectResponse{ 22 Name: "abstract.pdf", 23 Id: utils.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), 24 UpdatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), 25 CreatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), 26 LastAccessedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), 27 Metadata: &storage.ObjectMetadata{ 28 ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, 29 Size: 82702, 30 Mimetype: "application/pdf", 31 CacheControl: "max-age=3600", 32 LastModified: "2023-10-13T18:08:22.000Z", 33 ContentLength: 82702, 34 HttpStatusCode: 200, 35 }, 36 } 37 38 var mockApi = storage.StorageAPI{Fetcher: fetcher.NewFetcher( 39 "http://127.0.0.1", 40 )} 41 42 func TestStorageCP(t *testing.T) { 43 flags.ProjectRef = apitest.RandomProjectRef() 44 // Setup valid access token 45 token := apitest.RandomAccessToken(t) 46 t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) 47 48 t.Run("copy local to remote", func(t *testing.T) { 49 // Setup in-memory fs 50 fsys := afero.NewMemMapFs() 51 require.NoError(t, afero.WriteFile(fsys, "/tmp/file", []byte{}, 0644)) 52 // Setup mock api 53 defer gock.OffAll() 54 gock.New(utils.DefaultApiHost). 55 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 56 Reply(http.StatusOK). 57 JSON([]api.ApiKeyResponse{{ 58 Name: "service_role", 59 ApiKey: "service-key", 60 }}) 61 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 62 Post("/storage/v1/object/private/file"). 63 Reply(http.StatusOK) 64 // Run test 65 err := Run(context.Background(), "/tmp/file", "ss:///private/file", false, 1, fsys) 66 // Check error 67 assert.NoError(t, err) 68 assert.Empty(t, apitest.ListUnmatchedRequests()) 69 }) 70 71 t.Run("throws error on missing file", func(t *testing.T) { 72 // Setup in-memory fs 73 fsys := afero.NewMemMapFs() 74 // Setup mock api 75 defer gock.OffAll() 76 gock.New(utils.DefaultApiHost). 77 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 78 Reply(http.StatusOK). 79 JSON([]api.ApiKeyResponse{{ 80 Name: "service_role", 81 ApiKey: "service-key", 82 }}) 83 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 84 Get("/storage/v1/bucket"). 85 Reply(http.StatusOK). 86 JSON([]storage.BucketResponse{}) 87 // Run test 88 err := Run(context.Background(), "abstract.pdf", "ss:///private", true, 1, fsys) 89 // Check error 90 assert.ErrorIs(t, err, fs.ErrNotExist) 91 assert.Empty(t, apitest.ListUnmatchedRequests()) 92 }) 93 94 t.Run("copy remote to local", func(t *testing.T) { 95 // Setup in-memory fs 96 fsys := afero.NewMemMapFs() 97 // Setup mock api 98 defer gock.OffAll() 99 gock.New(utils.DefaultApiHost). 100 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 101 Reply(http.StatusOK). 102 JSON([]api.ApiKeyResponse{{ 103 Name: "service_role", 104 ApiKey: "service-key", 105 }}) 106 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 107 Get("/storage/v1/object/private/file"). 108 Reply(http.StatusOK) 109 // Run test 110 err := Run(context.Background(), "ss:///private/file", "abstract.pdf", false, 1, fsys) 111 // Check error 112 assert.NoError(t, err) 113 assert.Empty(t, apitest.ListUnmatchedRequests()) 114 exists, err := afero.Exists(fsys, "abstract.pdf") 115 assert.NoError(t, err) 116 assert.True(t, exists) 117 }) 118 119 t.Run("throws error on missing bucket", func(t *testing.T) { 120 // Setup in-memory fs 121 fsys := afero.NewMemMapFs() 122 // Setup mock api 123 defer gock.OffAll() 124 gock.New(utils.DefaultApiHost). 125 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 126 Reply(http.StatusOK). 127 JSON([]api.ApiKeyResponse{{ 128 Name: "service_role", 129 ApiKey: "service-key", 130 }}) 131 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 132 Get("/storage/v1/bucket"). 133 Reply(http.StatusOK). 134 JSON([]storage.BucketResponse{}) 135 // Run test 136 err := Run(context.Background(), "ss:///private", ".", true, 1, fsys) 137 // Check error 138 assert.ErrorContains(t, err, "Object not found: /private") 139 assert.Empty(t, apitest.ListUnmatchedRequests()) 140 }) 141 142 t.Run("throws error on invalid src", func(t *testing.T) { 143 // Setup in-memory fs 144 fsys := afero.NewMemMapFs() 145 // Run test 146 err := Run(context.Background(), ":", ".", false, 1, fsys) 147 // Check error 148 assert.ErrorContains(t, err, "missing protocol scheme") 149 }) 150 151 t.Run("throws error on invalid dst", func(t *testing.T) { 152 // Setup in-memory fs 153 fsys := afero.NewMemMapFs() 154 // Run test 155 err := Run(context.Background(), ".", ":", false, 1, fsys) 156 // Check error 157 assert.ErrorContains(t, err, "missing protocol scheme") 158 }) 159 160 t.Run("throws error on unsupported operation", func(t *testing.T) { 161 // Setup in-memory fs 162 fsys := afero.NewMemMapFs() 163 // Setup mock api 164 defer gock.OffAll() 165 gock.New(utils.DefaultApiHost). 166 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 167 Reply(http.StatusOK). 168 JSON([]api.ApiKeyResponse{{ 169 Name: "service_role", 170 ApiKey: "service-key", 171 }}) 172 // Run test 173 err := Run(context.Background(), ".", ".", false, 1, fsys) 174 // Check error 175 assert.ErrorIs(t, err, errUnsupportedOperation) 176 }) 177 } 178 179 func TestUploadAll(t *testing.T) { 180 t.Run("uploads directory to new bucket", func(t *testing.T) { 181 // Setup in-memory fs 182 fsys := afero.NewMemMapFs() 183 require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) 184 // Setup mock api 185 defer gock.OffAll() 186 gock.New("http://127.0.0.1"). 187 Get("/storage/v1/bucket"). 188 Reply(http.StatusOK). 189 JSON([]storage.BucketResponse{}) 190 gock.New("http://127.0.0.1"). 191 Post("/storage/v1/object/tmp/readme.md"). 192 Reply(http.StatusNotFound). 193 JSON(map[string]string{"error": "Bucket not found"}) 194 gock.New("http://127.0.0.1"). 195 Post("/storage/v1/bucket"). 196 Reply(http.StatusOK). 197 JSON(storage.CreateBucketResponse{Name: "tmp"}) 198 gock.New("http://127.0.0.1"). 199 Post("/storage/v1/object/tmp/readme.md"). 200 Reply(http.StatusOK) 201 // Run test 202 err := UploadStorageObjectAll(context.Background(), mockApi, "", "/tmp", 1, fsys) 203 // Check error 204 assert.NoError(t, err) 205 assert.Empty(t, apitest.ListUnmatchedRequests()) 206 }) 207 208 t.Run("throws error on failure to create bucket", func(t *testing.T) { 209 // Setup in-memory fs 210 fsys := afero.NewMemMapFs() 211 require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) 212 // Setup mock api 213 defer gock.OffAll() 214 gock.New("http://127.0.0.1"). 215 Get("/storage/v1/bucket"). 216 Reply(http.StatusOK). 217 JSON([]storage.BucketResponse{}) 218 gock.New("http://127.0.0.1"). 219 Post("/storage/v1/object/tmp/readme.md"). 220 Reply(http.StatusNotFound). 221 JSON(map[string]string{"error": "Bucket not found"}) 222 gock.New("http://127.0.0.1"). 223 Post("/storage/v1/bucket"). 224 Reply(http.StatusServiceUnavailable) 225 // Run test 226 err := UploadStorageObjectAll(context.Background(), mockApi, "", "/tmp", 1, fsys) 227 // Check error 228 assert.ErrorContains(t, err, "Error status 503:") 229 assert.Empty(t, apitest.ListUnmatchedRequests()) 230 }) 231 232 t.Run("uploads directory to existing prefix", func(t *testing.T) { 233 // Setup in-memory fs 234 fsys := afero.NewMemMapFs() 235 require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) 236 require.NoError(t, afero.WriteFile(fsys, "/tmp/docs/api.md", []byte{}, 0644)) 237 // Setup mock api 238 defer gock.OffAll() 239 gock.New("http://127.0.0.1"). 240 Post("/storage/v1/object/list/private"). 241 Reply(http.StatusOK). 242 JSON([]storage.ObjectResponse{{ 243 Name: "dir", 244 }}) 245 gock.New("http://127.0.0.1"). 246 Post("/storage/v1/object/private/dir/tmp/readme.md"). 247 Reply(http.StatusOK) 248 gock.New("http://127.0.0.1"). 249 Post("/storage/v1/object/private/dir/tmp/docs/api.md"). 250 Reply(http.StatusOK) 251 // Run test 252 err := UploadStorageObjectAll(context.Background(), mockApi, "/private/dir/", "/tmp", 1, fsys) 253 // Check error 254 assert.NoError(t, err) 255 assert.Empty(t, apitest.ListUnmatchedRequests()) 256 }) 257 258 t.Run("uploads file to existing bucket", func(t *testing.T) { 259 // Setup in-memory fs 260 fsys := afero.NewMemMapFs() 261 require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) 262 // Setup mock api 263 defer gock.OffAll() 264 gock.New("http://127.0.0.1"). 265 Get("/storage/v1/bucket"). 266 Reply(http.StatusOK). 267 JSON([]storage.BucketResponse{{ 268 Id: "private", 269 Name: "private", 270 CreatedAt: "2023-10-13T17:48:58.491Z", 271 UpdatedAt: "2023-10-13T17:48:58.491Z", 272 }}) 273 gock.New("http://127.0.0.1"). 274 Post("/storage/v1/object/private/readme.md"). 275 Reply(http.StatusOK) 276 // Run test 277 err := UploadStorageObjectAll(context.Background(), mockApi, "private", "/tmp/readme.md", 1, fsys) 278 // Check error 279 assert.NoError(t, err) 280 assert.Empty(t, apitest.ListUnmatchedRequests()) 281 }) 282 283 t.Run("uploads file to existing object", func(t *testing.T) { 284 // Setup in-memory fs 285 fsys := afero.NewMemMapFs() 286 require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644)) 287 // Setup mock api 288 defer gock.OffAll() 289 fileObject := mockFile 290 fileObject.Name = "file" 291 gock.New("http://127.0.0.1"). 292 Post("/storage/v1/object/list/private"). 293 Reply(http.StatusOK). 294 JSON([]storage.ObjectResponse{fileObject}) 295 gock.New("http://127.0.0.1"). 296 Post("/storage/v1/object/private/file"). 297 Reply(http.StatusOK) 298 // Run test 299 err := UploadStorageObjectAll(context.Background(), mockApi, "private/file", "/tmp/readme.md", 1, fsys) 300 // Check error 301 assert.NoError(t, err) 302 assert.Empty(t, apitest.ListUnmatchedRequests()) 303 }) 304 305 t.Run("throws error on service unavailable", func(t *testing.T) { 306 // Setup in-memory fs 307 fsys := afero.NewMemMapFs() 308 // Setup mock api 309 defer gock.OffAll() 310 gock.New("http://127.0.0.1"). 311 Get("/storage/v1/bucket"). 312 Reply(http.StatusServiceUnavailable) 313 // Run test 314 err := UploadStorageObjectAll(context.Background(), mockApi, "", ".", 1, fsys) 315 // Check error 316 assert.ErrorContains(t, err, "Error status 503:") 317 assert.Empty(t, apitest.ListUnmatchedRequests()) 318 }) 319 } 320 321 func TestDownloadAll(t *testing.T) { 322 t.Run("downloads buckets to existing directory", func(t *testing.T) { 323 // Setup in-memory fs 324 fsys := afero.NewMemMapFs() 325 // Setup mock api 326 defer gock.OffAll() 327 gock.New("http://127.0.0.1"). 328 Get("/storage/v1/bucket"). 329 Reply(http.StatusOK). 330 JSON([]storage.BucketResponse{{ 331 Id: "test", 332 Name: "test", 333 Public: true, 334 CreatedAt: "2023-10-13T17:48:58.491Z", 335 UpdatedAt: "2023-10-13T17:48:58.491Z", 336 }, { 337 Id: "private", 338 Name: "private", 339 CreatedAt: "2023-10-13T17:48:58.491Z", 340 UpdatedAt: "2023-10-13T17:48:58.491Z", 341 }}) 342 gock.New("http://127.0.0.1"). 343 Post("/storage/v1/object/list/private"). 344 Reply(http.StatusOK). 345 JSON([]storage.ObjectResponse{}) 346 gock.New("http://127.0.0.1"). 347 Post("/storage/v1/object/list/test"). 348 Reply(http.StatusOK). 349 JSON([]storage.ObjectResponse{}) 350 // Run test 351 err := DownloadStorageObjectAll(context.Background(), mockApi, "", "/", 1, fsys) 352 // Check error 353 assert.NoError(t, err) 354 assert.Empty(t, apitest.ListUnmatchedRequests()) 355 exists, err := afero.DirExists(fsys, "/private") 356 assert.NoError(t, err) 357 assert.True(t, exists) 358 exists, err = afero.DirExists(fsys, "/test") 359 assert.NoError(t, err) 360 assert.True(t, exists) 361 }) 362 363 t.Run("downloads empty bucket to new directory", func(t *testing.T) { 364 // Setup in-memory fs 365 fsys := afero.NewMemMapFs() 366 // Setup mock api 367 defer gock.OffAll() 368 gock.New("http://127.0.0.1"). 369 Get("/storage/v1/object/private"). 370 Reply(http.StatusNotFound). 371 JSON(map[string]string{"error": "Not Found"}) 372 gock.New("http://127.0.0.1"). 373 Get("/storage/v1/bucket"). 374 Reply(http.StatusOK). 375 JSON([]storage.BucketResponse{{ 376 Id: "private", 377 Name: "private", 378 CreatedAt: "2023-10-13T17:48:58.491Z", 379 UpdatedAt: "2023-10-13T17:48:58.491Z", 380 }}) 381 gock.New("http://127.0.0.1"). 382 Post("/storage/v1/object/list/private"). 383 Reply(http.StatusOK). 384 JSON([]storage.ObjectResponse{}) 385 // Run test 386 err := DownloadStorageObjectAll(context.Background(), mockApi, "/private", "/tmp", 1, fsys) 387 // Check error 388 assert.NoError(t, err) 389 assert.Empty(t, apitest.ListUnmatchedRequests()) 390 exists, err := afero.DirExists(fsys, "/private") 391 assert.NoError(t, err) 392 assert.False(t, exists) 393 exists, err = afero.DirExists(fsys, "/tmp") 394 assert.NoError(t, err) 395 assert.True(t, exists) 396 }) 397 398 t.Run("throws error on empty directory", func(t *testing.T) { 399 // Setup in-memory fs 400 fsys := afero.NewMemMapFs() 401 // Setup mock api 402 defer gock.OffAll() 403 gock.New("http://127.0.0.1"). 404 Post("/storage/v1/object/list/private"). 405 Reply(http.StatusOK). 406 JSON([]storage.ObjectResponse{}) 407 // Run test 408 err := DownloadStorageObjectAll(context.Background(), mockApi, "private/dir/", "/", 1, fsys) 409 // Check error 410 assert.ErrorContains(t, err, "Object not found: private/dir/") 411 assert.Empty(t, apitest.ListUnmatchedRequests()) 412 exists, err := afero.DirExists(fsys, "/private") 413 assert.NoError(t, err) 414 assert.False(t, exists) 415 }) 416 417 t.Run("downloads objects to existing directory", func(t *testing.T) { 418 // Setup in-memory fs 419 fsys := afero.NewMemMapFs() 420 // Setup mock api 421 defer gock.OffAll() 422 // Lists /private/tmp directory 423 gock.New("http://127.0.0.1"). 424 Post("/storage/v1/object/list/private"). 425 JSON(storage.ListObjectsQuery{ 426 Prefix: "tmp/", 427 Search: "", 428 Limit: storage.PAGE_LIMIT, 429 Offset: 0, 430 }). 431 Reply(http.StatusOK). 432 JSON([]storage.ObjectResponse{{ 433 Name: "docs", 434 }, mockFile}) 435 gock.New("http://127.0.0.1"). 436 Get("/storage/v1/object/private/tmp/abstract.pdf"). 437 Reply(http.StatusOK) 438 // Lists /private/tmp/docs directory 439 readme := mockFile 440 readme.Name = "readme.md" 441 gock.New("http://127.0.0.1"). 442 Post("/storage/v1/object/list/private"). 443 JSON(storage.ListObjectsQuery{ 444 Prefix: "tmp/docs/", 445 Search: "", 446 Limit: storage.PAGE_LIMIT, 447 Offset: 0, 448 }). 449 Reply(http.StatusOK). 450 JSON([]storage.ObjectResponse{readme}) 451 gock.New("http://127.0.0.1"). 452 Get("/storage/v1/object/private/tmp/docs/readme.md"). 453 Reply(http.StatusOK) 454 // Run test 455 err := DownloadStorageObjectAll(context.Background(), mockApi, "private/tmp/", "/", 1, fsys) 456 // Check error 457 assert.NoError(t, err) 458 assert.Empty(t, apitest.ListUnmatchedRequests()) 459 exists, err := afero.Exists(fsys, "/tmp/abstract.pdf") 460 assert.NoError(t, err) 461 assert.True(t, exists) 462 exists, err = afero.Exists(fsys, "/tmp/docs/readme.md") 463 assert.NoError(t, err) 464 assert.True(t, exists) 465 }) 466 467 t.Run("downloads object to existing file", func(t *testing.T) { 468 // Setup in-memory fs 469 fsys := afero.NewMemMapFs() 470 // Setup mock api 471 defer gock.OffAll() 472 gock.New("http://127.0.0.1"). 473 Post("/storage/v1/object/list/private"). 474 Reply(http.StatusOK). 475 JSON([]storage.ObjectResponse{mockFile}) 476 gock.New("http://127.0.0.1"). 477 Get("/storage/v1/object/private/abstract.pdf"). 478 Reply(http.StatusOK) 479 // Run test 480 err := DownloadStorageObjectAll(context.Background(), mockApi, "/private/abstract.pdf", "/tmp/file", 1, fsys) 481 // Check error 482 assert.NoError(t, err) 483 assert.Empty(t, apitest.ListUnmatchedRequests()) 484 exists, err := afero.DirExists(fsys, "/private") 485 assert.NoError(t, err) 486 assert.False(t, exists) 487 exists, err = afero.Exists(fsys, "/tmp/file") 488 assert.NoError(t, err) 489 assert.True(t, exists) 490 }) 491 }