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