github.com/supabase/cli@v1.168.1/internal/storage/rm/rm_test.go (about) 1 package rm 2 3 import ( 4 "context" 5 "net/http" 6 "testing" 7 8 "github.com/spf13/afero" 9 "github.com/stretchr/testify/assert" 10 "github.com/supabase/cli/internal/testing/apitest" 11 "github.com/supabase/cli/internal/testing/fstest" 12 "github.com/supabase/cli/internal/utils" 13 "github.com/supabase/cli/internal/utils/flags" 14 "github.com/supabase/cli/pkg/api" 15 "github.com/supabase/cli/pkg/fetcher" 16 "github.com/supabase/cli/pkg/storage" 17 "gopkg.in/h2non/gock.v1" 18 ) 19 20 var mockFile = storage.ObjectResponse{ 21 Name: "abstract.pdf", 22 Id: utils.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"), 23 UpdatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), 24 CreatedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), 25 LastAccessedAt: utils.Ptr("2023-10-13T18:08:22.068Z"), 26 Metadata: &storage.ObjectMetadata{ 27 ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`, 28 Size: 82702, 29 Mimetype: "application/pdf", 30 CacheControl: "max-age=3600", 31 LastModified: "2023-10-13T18:08:22.000Z", 32 ContentLength: 82702, 33 HttpStatusCode: 200, 34 }, 35 } 36 37 var mockApi = storage.StorageAPI{Fetcher: fetcher.NewFetcher( 38 "http://127.0.0.1", 39 )} 40 41 func TestStorageRM(t *testing.T) { 42 flags.ProjectRef = apitest.RandomProjectRef() 43 // Setup valid access token 44 token := apitest.RandomAccessToken(t) 45 t.Setenv("SUPABASE_ACCESS_TOKEN", string(token)) 46 47 t.Run("throws error on invalid url", func(t *testing.T) { 48 // Setup in-memory fs 49 fsys := afero.NewMemMapFs() 50 // Run test 51 err := Run(context.Background(), []string{":"}, false, fsys) 52 // Check error 53 assert.ErrorContains(t, err, "missing protocol scheme") 54 }) 55 56 t.Run("throws error on missing bucket", func(t *testing.T) { 57 // Setup in-memory fs 58 fsys := afero.NewMemMapFs() 59 // Run test 60 err := Run(context.Background(), []string{"ss:///"}, false, fsys) 61 // Check error 62 assert.ErrorIs(t, err, errMissingBucket) 63 }) 64 65 t.Run("throws error on missing flag", func(t *testing.T) { 66 // Setup in-memory fs 67 fsys := afero.NewMemMapFs() 68 // Run test 69 err := Run(context.Background(), []string{"ss:///private/"}, false, fsys) 70 // Check error 71 assert.ErrorIs(t, err, errMissingFlag) 72 }) 73 74 t.Run("removes multiple objects", func(t *testing.T) { 75 defer fstest.MockStdin(t, "y")() 76 // Setup in-memory fs 77 fsys := afero.NewMemMapFs() 78 // Setup mock api 79 defer gock.OffAll() 80 gock.New(utils.DefaultApiHost). 81 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 82 Reply(http.StatusOK). 83 JSON([]api.ApiKeyResponse{{ 84 Name: "service_role", 85 ApiKey: "service-key", 86 }}) 87 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 88 Delete("/storage/v1/object/private"). 89 JSON(storage.DeleteObjectsRequest{Prefixes: []string{ 90 "abstract.pdf", 91 "docs/readme.md", 92 }}). 93 Reply(http.StatusOK). 94 JSON([]storage.DeleteObjectsResponse{{ 95 BucketId: "private", 96 Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436", 97 Name: "abstract.pdf", 98 Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9", 99 UpdatedAt: "2023-10-13T18:08:22.068Z", 100 CreatedAt: "2023-10-13T18:08:22.068Z", 101 LastAccessedAt: "2023-10-13T18:08:22.068Z", 102 }}) 103 // Run test 104 err := Run(context.Background(), []string{ 105 "ss:///private/abstract.pdf", 106 "ss:///private/docs/readme.md", 107 }, false, fsys) 108 // Check error 109 assert.NoError(t, err) 110 assert.Empty(t, apitest.ListUnmatchedRequests()) 111 }) 112 113 t.Run("removes buckets and directories", func(t *testing.T) { 114 defer fstest.MockStdin(t, "y")() 115 // Setup in-memory fs 116 fsys := afero.NewMemMapFs() 117 // Setup mock api 118 defer gock.OffAll() 119 gock.New(utils.DefaultApiHost). 120 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 121 Reply(http.StatusOK). 122 JSON([]api.ApiKeyResponse{{ 123 Name: "service_role", 124 ApiKey: "service-key", 125 }}) 126 // Delete /test/ bucket 127 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 128 Post("/storage/v1/object/list/test"). 129 Reply(http.StatusOK). 130 JSON([]storage.ObjectResponse{}) 131 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 132 Delete("/storage/v1/object/test"). 133 JSON(storage.DeleteObjectsRequest{Prefixes: []string{ 134 "", 135 }}). 136 Reply(http.StatusOK). 137 JSON([]storage.DeleteObjectsResponse{}) 138 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 139 Post("/storage/v1/object/list/test"). 140 Reply(http.StatusOK). 141 JSON([]storage.ObjectResponse{}) 142 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 143 Delete("/storage/v1/bucket/test"). 144 Reply(http.StatusNotFound). 145 JSON(map[string]string{"error": "Bucket not found"}) 146 // Delete /private/docs/ directory 147 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 148 Delete("/storage/v1/object/private"). 149 JSON(storage.DeleteObjectsRequest{Prefixes: []string{ 150 "docs", 151 }}). 152 Reply(http.StatusOK). 153 JSON([]storage.DeleteObjectsResponse{}) 154 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 155 Post("/storage/v1/object/list/private"). 156 Reply(http.StatusOK). 157 JSON([]storage.ObjectResponse{mockFile}) 158 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 159 Delete("/storage/v1/object/private"). 160 JSON(storage.DeleteObjectsRequest{Prefixes: []string{ 161 "docs/abstract.pdf", 162 }}). 163 Reply(http.StatusOK). 164 JSON([]storage.DeleteObjectsResponse{{ 165 BucketId: "private", 166 Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436", 167 Name: "abstract.pdf", 168 Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9", 169 UpdatedAt: "2023-10-13T18:08:22.068Z", 170 CreatedAt: "2023-10-13T18:08:22.068Z", 171 LastAccessedAt: "2023-10-13T18:08:22.068Z", 172 }}) 173 // Run test 174 err := Run(context.Background(), []string{ 175 "ss:///test", 176 "ss:///private/docs", 177 }, true, fsys) 178 // Check error 179 assert.NoError(t, err) 180 assert.Empty(t, apitest.ListUnmatchedRequests()) 181 }) 182 183 t.Run("throws error on delete failure", func(t *testing.T) { 184 defer fstest.MockStdin(t, "y")() 185 // Setup in-memory fs 186 fsys := afero.NewMemMapFs() 187 // Setup mock api 188 defer gock.OffAll() 189 gock.New(utils.DefaultApiHost). 190 Get("/v1/projects/" + flags.ProjectRef + "/api-keys"). 191 Reply(http.StatusOK). 192 JSON([]api.ApiKeyResponse{{ 193 Name: "service_role", 194 ApiKey: "service-key", 195 }}) 196 gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)). 197 Delete("/storage/v1/object/private"). 198 Reply(http.StatusServiceUnavailable) 199 // Run test 200 err := Run(context.Background(), []string{"ss:///private"}, true, fsys) 201 // Check error 202 assert.ErrorContains(t, err, "Error status 503:") 203 assert.Empty(t, apitest.ListUnmatchedRequests()) 204 }) 205 } 206 207 func TestRemoveAll(t *testing.T) { 208 t.Run("removes objects by prefix", func(t *testing.T) { 209 // Setup mock api 210 defer gock.OffAll() 211 // List /private/tmp/ 212 gock.New("http://127.0.0.1"). 213 Post("/storage/v1/object/list/private"). 214 JSON(storage.ListObjectsQuery{ 215 Prefix: "tmp/", 216 Search: "", 217 Limit: storage.PAGE_LIMIT, 218 Offset: 0, 219 }). 220 Reply(http.StatusOK). 221 JSON([]storage.ObjectResponse{{ 222 Name: "docs", 223 }}) 224 // List /private/docs/ 225 readme := mockFile 226 readme.Name = "readme.md" 227 gock.New("http://127.0.0.1"). 228 Post("/storage/v1/object/list/private"). 229 JSON(storage.ListObjectsQuery{ 230 Prefix: "tmp/docs/", 231 Search: "", 232 Limit: storage.PAGE_LIMIT, 233 Offset: 0, 234 }). 235 Reply(http.StatusOK). 236 JSON([]storage.ObjectResponse{mockFile, readme}) 237 gock.New("http://127.0.0.1"). 238 Delete("/storage/v1/object/private"). 239 JSON(storage.DeleteObjectsRequest{Prefixes: []string{ 240 "tmp/docs/abstract.pdf", 241 "tmp/docs/readme.md", 242 }}). 243 Reply(http.StatusOK). 244 JSON([]storage.DeleteObjectsResponse{{ 245 BucketId: "private", 246 Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436", 247 Name: "abstract.pdf", 248 Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9", 249 UpdatedAt: "2023-10-13T18:08:22.068Z", 250 CreatedAt: "2023-10-13T18:08:22.068Z", 251 LastAccessedAt: "2023-10-13T18:08:22.068Z", 252 }, { 253 BucketId: "private", 254 Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436", 255 Name: "readme.md", 256 Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9", 257 UpdatedAt: "2023-10-13T18:08:22.068Z", 258 CreatedAt: "2023-10-13T18:08:22.068Z", 259 LastAccessedAt: "2023-10-13T18:08:22.068Z", 260 }}) 261 // Run test 262 err := RemoveStoragePathAll(context.Background(), mockApi, "private", "tmp/") 263 // Check error 264 assert.NoError(t, err) 265 assert.Empty(t, apitest.ListUnmatchedRequests()) 266 }) 267 268 t.Run("removes empty bucket", func(t *testing.T) { 269 // Setup mock api 270 defer gock.OffAll() 271 gock.New("http://127.0.0.1"). 272 Post("/storage/v1/object/list/private"). 273 Reply(http.StatusOK). 274 JSON([]storage.ObjectResponse{}) 275 gock.New("http://127.0.0.1"). 276 Delete("/storage/v1/bucket/private"). 277 Reply(http.StatusOK). 278 JSON(storage.DeleteBucketResponse{Message: "Successfully deleted"}) 279 // Run test 280 err := RemoveStoragePathAll(context.Background(), mockApi, "private", "") 281 // Check error 282 assert.NoError(t, err) 283 assert.Empty(t, apitest.ListUnmatchedRequests()) 284 }) 285 286 t.Run("throws error on empty directory", func(t *testing.T) { 287 // Setup mock api 288 defer gock.OffAll() 289 gock.New("http://127.0.0.1"). 290 Post("/storage/v1/object/list/private"). 291 Reply(http.StatusOK). 292 JSON([]storage.ObjectResponse{}) 293 // Run test 294 err := RemoveStoragePathAll(context.Background(), mockApi, "private", "dir") 295 // Check error 296 assert.ErrorContains(t, err, "Object not found: private/dir") 297 assert.Empty(t, apitest.ListUnmatchedRequests()) 298 }) 299 300 t.Run("throws error on service unavailable", func(t *testing.T) { 301 // Setup mock api 302 defer gock.OffAll() 303 gock.New("http://127.0.0.1"). 304 Post("/storage/v1/object/list/private"). 305 Reply(http.StatusServiceUnavailable) 306 // Run test 307 err := RemoveStoragePathAll(context.Background(), mockApi, "private", "") 308 // Check error 309 assert.ErrorContains(t, err, "Error status 503:") 310 assert.Empty(t, apitest.ListUnmatchedRequests()) 311 }) 312 313 t.Run("throws error on delete failure", func(t *testing.T) { 314 // Setup mock api 315 defer gock.OffAll() 316 gock.New("http://127.0.0.1"). 317 Post("/storage/v1/object/list/private"). 318 Reply(http.StatusOK). 319 JSON([]storage.ObjectResponse{mockFile}) 320 gock.New("http://127.0.0.1"). 321 Delete("/storage/v1/object/private"). 322 Reply(http.StatusServiceUnavailable) 323 // Run test 324 err := RemoveStoragePathAll(context.Background(), mockApi, "private", "") 325 // Check error 326 assert.ErrorContains(t, err, "Error status 503:") 327 assert.Empty(t, apitest.ListUnmatchedRequests()) 328 }) 329 }