331 lines
10 KiB
Go
331 lines
10 KiB
Go
package rm
|
|
|
|
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/testing/fstest"
|
|
"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 TestStorageRM(t *testing.T) {
|
|
flags.ProjectRef = apitest.RandomProjectRef()
|
|
// Setup valid access token
|
|
token := apitest.RandomAccessToken(t)
|
|
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
|
|
|
|
t.Run("throws error on invalid url", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), []string{":"}, false, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "missing protocol scheme")
|
|
})
|
|
|
|
t.Run("throws error on missing bucket", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), []string{"ss:///"}, false, fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, errMissingBucket)
|
|
})
|
|
|
|
t.Run("throws error on missing flag", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), []string{"ss:///private/"}, false, fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, errMissingFlag)
|
|
})
|
|
|
|
t.Run("removes multiple objects", func(t *testing.T) {
|
|
t.Cleanup(fstest.MockStdin(t, "y"))
|
|
// 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)).
|
|
Delete("/storage/v1/object/private").
|
|
JSON(storage.DeleteObjectsRequest{Prefixes: []string{
|
|
"abstract.pdf",
|
|
"docs/readme.md",
|
|
}}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.DeleteObjectsResponse{{
|
|
BucketId: "private",
|
|
Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436",
|
|
Name: "abstract.pdf",
|
|
Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9",
|
|
UpdatedAt: "2023-10-13T18:08:22.068Z",
|
|
CreatedAt: "2023-10-13T18:08:22.068Z",
|
|
LastAccessedAt: "2023-10-13T18:08:22.068Z",
|
|
}})
|
|
// Run test
|
|
err := Run(context.Background(), []string{
|
|
"ss:///private/abstract.pdf",
|
|
"ss:///private/docs/readme.md",
|
|
}, false, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("removes buckets and directories", func(t *testing.T) {
|
|
t.Cleanup(fstest.MockStdin(t, "y"))
|
|
// 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",
|
|
}})
|
|
// Delete /test/ bucket
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Post("/storage/v1/object/list/test").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Delete("/storage/v1/object/test").
|
|
JSON(storage.DeleteObjectsRequest{Prefixes: []string{
|
|
"",
|
|
}}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.DeleteObjectsResponse{})
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Post("/storage/v1/object/list/test").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Delete("/storage/v1/bucket/test").
|
|
Reply(http.StatusNotFound).
|
|
JSON(map[string]string{"error": "Bucket not found"})
|
|
// Delete /private/docs/ directory
|
|
gock.New("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Delete("/storage/v1/object/private").
|
|
JSON(storage.DeleteObjectsRequest{Prefixes: []string{
|
|
"docs",
|
|
}}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.DeleteObjectsResponse{})
|
|
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)).
|
|
Delete("/storage/v1/object/private").
|
|
JSON(storage.DeleteObjectsRequest{Prefixes: []string{
|
|
"docs/abstract.pdf",
|
|
}}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.DeleteObjectsResponse{{
|
|
BucketId: "private",
|
|
Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436",
|
|
Name: "abstract.pdf",
|
|
Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9",
|
|
UpdatedAt: "2023-10-13T18:08:22.068Z",
|
|
CreatedAt: "2023-10-13T18:08:22.068Z",
|
|
LastAccessedAt: "2023-10-13T18:08:22.068Z",
|
|
}})
|
|
// Run test
|
|
err := Run(context.Background(), []string{
|
|
"ss:///test",
|
|
"ss:///private/docs",
|
|
}, true, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on delete failure", func(t *testing.T) {
|
|
t.Cleanup(fstest.MockStdin(t, "y"))
|
|
// 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)).
|
|
Delete("/storage/v1/object/private").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := Run(context.Background(), []string{"ss:///private"}, true, fsys)
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|
|
|
|
func TestRemoveAll(t *testing.T) {
|
|
t.Run("removes objects by prefix", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
// List /private/tmp/
|
|
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",
|
|
}})
|
|
// List /private/docs/
|
|
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{mockFile, readme})
|
|
gock.New("http://127.0.0.1").
|
|
Delete("/storage/v1/object/private").
|
|
JSON(storage.DeleteObjectsRequest{Prefixes: []string{
|
|
"tmp/docs/abstract.pdf",
|
|
"tmp/docs/readme.md",
|
|
}}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.DeleteObjectsResponse{{
|
|
BucketId: "private",
|
|
Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436",
|
|
Name: "abstract.pdf",
|
|
Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9",
|
|
UpdatedAt: "2023-10-13T18:08:22.068Z",
|
|
CreatedAt: "2023-10-13T18:08:22.068Z",
|
|
LastAccessedAt: "2023-10-13T18:08:22.068Z",
|
|
}, {
|
|
BucketId: "private",
|
|
Version: "cf5c5c53-ee73-4806-84e3-7d92c954b436",
|
|
Name: "readme.md",
|
|
Id: "9b7f9f48-17a6-4ca8-b14a-39b0205a63e9",
|
|
UpdatedAt: "2023-10-13T18:08:22.068Z",
|
|
CreatedAt: "2023-10-13T18:08:22.068Z",
|
|
LastAccessedAt: "2023-10-13T18:08:22.068Z",
|
|
}})
|
|
// Run test
|
|
err := RemoveStoragePathAll(context.Background(), mockApi, "private", "tmp/")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("removes empty bucket", 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{})
|
|
gock.New("http://127.0.0.1").
|
|
Delete("/storage/v1/bucket/private").
|
|
Reply(http.StatusOK).
|
|
JSON(storage.DeleteBucketResponse{Message: "Successfully deleted"})
|
|
// Run test
|
|
err := RemoveStoragePathAll(context.Background(), mockApi, "private", "")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on empty directory", 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 := RemoveStoragePathAll(context.Background(), mockApi, "private", "dir")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Object not found: private/dir")
|
|
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 := RemoveStoragePathAll(context.Background(), mockApi, "private", "")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on delete 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").
|
|
Delete("/storage/v1/object/private").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
err := RemoveStoragePathAll(context.Background(), mockApi, "private", "")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|