493 lines
15 KiB
Go
493 lines
15 KiB
Go
package cp
|
|
|
|
import (
|
|
"context"
|
|
"io/fs"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/h2non/gock"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"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 TestStorageCP(t *testing.T) {
|
|
flags.ProjectRef = apitest.RandomProjectRef()
|
|
// Setup valid access token
|
|
token := apitest.RandomAccessToken(t)
|
|
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
|
|
|
|
t.Run("copy local to remote", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, "/tmp/file", []byte{}, 0644))
|
|
// 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/private/file").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := Run(context.Background(), "/tmp/file", "ss:///private/file", false, 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on missing file", 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)).
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.BucketResponse{})
|
|
// Run test
|
|
err := Run(context.Background(), "abstract.pdf", "ss:///private", true, 1, fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, fs.ErrNotExist)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("copy remote to local", 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)).
|
|
Get("/storage/v1/object/private/file").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///private/file", "abstract.pdf", false, 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
exists, err := afero.Exists(fsys, "abstract.pdf")
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
})
|
|
|
|
t.Run("throws error on missing bucket", 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)).
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.BucketResponse{})
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///private", ".", true, 1, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Object not found: /private")
|
|
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(), ":", ".", false, 1, 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(), ".", ":", false, 1, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "missing protocol scheme")
|
|
})
|
|
|
|
t.Run("throws error on unsupported operation", 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",
|
|
}})
|
|
// Run test
|
|
err := Run(context.Background(), ".", ".", false, 1, fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, errUnsupportedOperation)
|
|
})
|
|
}
|
|
|
|
func TestUploadAll(t *testing.T) {
|
|
t.Run("uploads directory to new bucket", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644))
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.BucketResponse{})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/tmp/readme.md").
|
|
Reply(http.StatusNotFound).
|
|
JSON(map[string]string{"error": "Bucket not found"})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON(storage.CreateBucketResponse{Name: "tmp"})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/tmp/readme.md").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := UploadStorageObjectAll(context.Background(), mockApi, "", "/tmp", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on failure to create bucket", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644))
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.BucketResponse{})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/tmp/readme.md").
|
|
Reply(http.StatusNotFound).
|
|
JSON(map[string]string{"error": "Bucket not found"})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/bucket").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := UploadStorageObjectAll(context.Background(), mockApi, "", "/tmp", 1, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("uploads directory to existing prefix", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644))
|
|
require.NoError(t, afero.WriteFile(fsys, "/tmp/docs/api.md", []byte{}, 0644))
|
|
// 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{{
|
|
Name: "dir",
|
|
}})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/private/dir/tmp/readme.md").
|
|
Reply(http.StatusOK)
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/private/dir/tmp/docs/api.md").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := UploadStorageObjectAll(context.Background(), mockApi, "/private/dir/", "/tmp", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("uploads file to existing bucket", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644))
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.BucketResponse{{
|
|
Id: "private",
|
|
Name: "private",
|
|
CreatedAt: "2023-10-13T17:48:58.491Z",
|
|
UpdatedAt: "2023-10-13T17:48:58.491Z",
|
|
}})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/private/readme.md").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := UploadStorageObjectAll(context.Background(), mockApi, "private", "/tmp/readme.md", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("uploads file to existing object", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
require.NoError(t, afero.WriteFile(fsys, "/tmp/readme.md", []byte{}, 0644))
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
fileObject := mockFile
|
|
fileObject.Name = "file"
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{fileObject})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/private/file").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := UploadStorageObjectAll(context.Background(), mockApi, "private/file", "/tmp/readme.md", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on service unavailable", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := UploadStorageObjectAll(context.Background(), mockApi, "missing", ".", 1, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|
|
|
|
func TestDownloadAll(t *testing.T) {
|
|
t.Run("downloads buckets to existing directory", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.BucketResponse{{
|
|
Id: "test",
|
|
Name: "test",
|
|
Public: true,
|
|
CreatedAt: "2023-10-13T17:48:58.491Z",
|
|
UpdatedAt: "2023-10-13T17:48:58.491Z",
|
|
}, {
|
|
Id: "private",
|
|
Name: "private",
|
|
CreatedAt: "2023-10-13T17:48:58.491Z",
|
|
UpdatedAt: "2023-10-13T17:48:58.491Z",
|
|
}})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/test").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
// Run test
|
|
err := DownloadStorageObjectAll(context.Background(), mockApi, "", "/", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
exists, err := afero.DirExists(fsys, "/private")
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
exists, err = afero.DirExists(fsys, "/test")
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
})
|
|
|
|
t.Run("downloads empty bucket to new directory", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/object/private").
|
|
Reply(http.StatusNotFound).
|
|
JSON(map[string]string{"error": "Not Found"})
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.BucketResponse{{
|
|
Id: "private",
|
|
Name: "private",
|
|
CreatedAt: "2023-10-13T17:48:58.491Z",
|
|
UpdatedAt: "2023-10-13T17:48:58.491Z",
|
|
}})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
// Run test
|
|
err := DownloadStorageObjectAll(context.Background(), mockApi, "/private", "/tmp", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
exists, err := afero.DirExists(fsys, "/private")
|
|
assert.NoError(t, err)
|
|
assert.False(t, exists)
|
|
exists, err = afero.DirExists(fsys, "/tmp")
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
})
|
|
|
|
t.Run("throws error on empty directory", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// 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 := DownloadStorageObjectAll(context.Background(), mockApi, "private/dir/", "/", 1, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Object not found: private/dir/")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
exists, err := afero.DirExists(fsys, "/private")
|
|
assert.NoError(t, err)
|
|
assert.False(t, exists)
|
|
})
|
|
|
|
t.Run("downloads objects to existing directory", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// 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").
|
|
Get("/storage/v1/object/private/tmp/abstract.pdf").
|
|
Reply(http.StatusOK)
|
|
// 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").
|
|
Get("/storage/v1/object/private/tmp/docs/readme.md").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := DownloadStorageObjectAll(context.Background(), mockApi, "private/tmp/", "/", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
exists, err := afero.Exists(fsys, "/tmp/abstract.pdf")
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
exists, err = afero.Exists(fsys, "/tmp/docs/readme.md")
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
})
|
|
|
|
t.Run("downloads object to existing file", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// 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").
|
|
Get("/storage/v1/object/private/abstract.pdf").
|
|
Reply(http.StatusOK)
|
|
// Run test
|
|
err := DownloadStorageObjectAll(context.Background(), mockApi, "/private/abstract.pdf", "/tmp/file", 1, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
exists, err := afero.DirExists(fsys, "/private")
|
|
assert.NoError(t, err)
|
|
assert.False(t, exists)
|
|
exists, err = afero.Exists(fsys, "/tmp/file")
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
})
|
|
}
|