315 lines
9.5 KiB
Go
315 lines
9.5 KiB
Go
package mv
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/h2non/gock"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/supabase/cli/internal/testing/apitest"
|
|
"github.com/supabase/cli/internal/utils"
|
|
"github.com/supabase/cli/internal/utils/flags"
|
|
"github.com/supabase/cli/pkg/api"
|
|
"github.com/supabase/cli/pkg/cast"
|
|
"github.com/supabase/cli/pkg/fetcher"
|
|
"github.com/supabase/cli/pkg/storage"
|
|
)
|
|
|
|
var mockFile = storage.ObjectResponse{
|
|
Name: "abstract.pdf",
|
|
Id: cast.Ptr("9b7f9f48-17a6-4ca8-b14a-39b0205a63e9"),
|
|
UpdatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"),
|
|
CreatedAt: cast.Ptr("2023-10-13T18:08:22.068Z"),
|
|
LastAccessedAt: cast.Ptr("2023-10-13T18:08:22.068Z"),
|
|
Metadata: &storage.ObjectMetadata{
|
|
ETag: `"887ea9be3c68e6f2fca7fd2d7c77d8fe"`,
|
|
Size: 82702,
|
|
Mimetype: "application/pdf",
|
|
CacheControl: "max-age=3600",
|
|
LastModified: "2023-10-13T18:08:22.000Z",
|
|
ContentLength: 82702,
|
|
HttpStatusCode: 200,
|
|
},
|
|
}
|
|
|
|
var mockApi = storage.StorageAPI{Fetcher: fetcher.NewFetcher(
|
|
"http://127.0.0.1",
|
|
)}
|
|
|
|
func TestStorageMV(t *testing.T) {
|
|
flags.ProjectRef = apitest.RandomProjectRef()
|
|
// Setup valid access token
|
|
token := apitest.RandomAccessToken(t)
|
|
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
|
|
|
|
t.Run("moves single object", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New(utils.DefaultApiHost).
|
|
Get("/v1/projects/" + flags.ProjectRef + "/api-keys").
|
|
Reply(http.StatusOK).
|
|
JSON([]api.ApiKeyResponse{{
|
|
Name: "service_role",
|
|
ApiKey: "service-key",
|
|
}})
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Post("/storage/v1/object/move").
|
|
JSON(storage.MoveObjectRequest{
|
|
BucketId: "private",
|
|
SourceKey: "readme.md",
|
|
DestinationKey: "docs/file",
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON(storage.MoveObjectResponse{Message: "Successfully moved"})
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///private/readme.md", "ss:///private/docs/file", false, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("moves directory when recursive", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New(utils.DefaultApiHost).
|
|
Get("/v1/projects/" + flags.ProjectRef + "/api-keys").
|
|
Reply(http.StatusOK).
|
|
JSON([]api.ApiKeyResponse{{
|
|
Name: "service_role",
|
|
ApiKey: "service-key",
|
|
}})
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Post("/storage/v1/object/move").
|
|
JSON(storage.MoveObjectRequest{
|
|
BucketId: "private",
|
|
SourceKey: "",
|
|
DestinationKey: "docs",
|
|
}).
|
|
Reply(http.StatusNotFound).
|
|
JSON(map[string]string{"error": "not_found"})
|
|
// List bucket /private/
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{mockFile})
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Post("/storage/v1/object/move").
|
|
JSON(storage.MoveObjectRequest{
|
|
BucketId: "private",
|
|
SourceKey: "abstract.pdf",
|
|
DestinationKey: "docs/abstract.pdf",
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON(storage.MoveObjectResponse{Message: "Successfully moved"})
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///private", "ss:///private/docs", true, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on invalid src", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), ":", "ss:///", false, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "missing protocol scheme")
|
|
})
|
|
|
|
t.Run("throws error on invalid dst", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///", ":", false, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "missing protocol scheme")
|
|
})
|
|
|
|
t.Run("throws error on missing object path", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///", "ss:///", false, fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, errMissingPath)
|
|
})
|
|
|
|
t.Run("throws error on bucket mismatch", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///bucket/docs", "ss:///private", false, fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, errUnsupportedMove)
|
|
})
|
|
}
|
|
|
|
func TestMoveAll(t *testing.T) {
|
|
t.Run("rename directory within bucket", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
// Lists /private/tmp directory
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "tmp/",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{{
|
|
Name: "docs",
|
|
}, mockFile})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/move").
|
|
JSON(storage.MoveObjectRequest{
|
|
BucketId: "private",
|
|
SourceKey: "tmp/abstract.pdf",
|
|
DestinationKey: "dir/abstract.pdf",
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON(storage.MoveObjectResponse{Message: "Successfully moved"})
|
|
// Lists /private/tmp/docs directory
|
|
readme := mockFile
|
|
readme.Name = "readme.md"
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "tmp/docs/",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{readme})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/move").
|
|
JSON(storage.MoveObjectRequest{
|
|
BucketId: "private",
|
|
SourceKey: "tmp/docs/readme.md",
|
|
DestinationKey: "dir/docs/readme.md",
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON(storage.MoveObjectResponse{Message: "Successfully moved"})
|
|
// Run test
|
|
err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private/dir")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("moves object into directory", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
// Lists /private/ bucket
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{mockFile})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/move").
|
|
JSON(storage.MoveObjectRequest{
|
|
BucketId: "private",
|
|
SourceKey: "abstract.pdf",
|
|
DestinationKey: "dir/abstract.pdf",
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON(storage.MoveObjectResponse{Message: "Successfully moved"})
|
|
// Run test
|
|
err := MoveStorageObjectAll(context.Background(), mockApi, "private/", "private/dir")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("moves object out of directory", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
// Lists /private/tmp/ directory
|
|
readme := mockFile
|
|
readme.Name = "readme.md"
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "tmp/",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{readme})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/move").
|
|
JSON(storage.MoveObjectRequest{
|
|
BucketId: "private",
|
|
SourceKey: "tmp/readme.md",
|
|
DestinationKey: "readme.md",
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON(storage.MoveObjectResponse{Message: "Successfully moved"})
|
|
// Run test
|
|
err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on service unavailable", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on move failure", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{mockFile})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/move").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on missing object", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
// Run test
|
|
err := MoveStorageObjectAll(context.Background(), mockApi, "private/tmp/", "private")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Object not found: private/tmp/")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|