supabase-cli/internal/storage/mv/mv_test.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())
})
}