327 lines
9.3 KiB
Go
327 lines
9.3 KiB
Go
package ls
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/h2non/gock"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/supabase/cli/internal/storage/client"
|
|
"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 TestStorageLS(t *testing.T) {
|
|
flags.ProjectRef = apitest.RandomProjectRef()
|
|
// Setup valid access token
|
|
token := apitest.RandomAccessToken(t)
|
|
t.Setenv("SUPABASE_ACCESS_TOKEN", string(token))
|
|
|
|
t.Run("lists buckets", 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:///", false, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
t.Run("throws error on invalid URL", func(t *testing.T) {
|
|
// Setup in-memory fs
|
|
fsys := afero.NewMemMapFs()
|
|
// Run test
|
|
err := Run(context.Background(), "", false, fsys)
|
|
// Check error
|
|
assert.ErrorIs(t, err, client.ErrInvalidURL)
|
|
})
|
|
|
|
t.Run("lists objects 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)).
|
|
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("https://" + utils.GetSupabaseHost(flags.ProjectRef)).
|
|
Post("/storage/v1/object/list/private").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
// Run test
|
|
err := Run(context.Background(), "ss:///", true, fsys)
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestListStoragePaths(t *testing.T) {
|
|
t.Run("lists bucket paths by prefix", func(t *testing.T) {
|
|
// 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",
|
|
}})
|
|
// Run test
|
|
paths, err := ListStoragePaths(context.Background(), mockApi, "te")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{"test/"}, paths)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on bucket service unavailable", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Get("/storage/v1/bucket").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
paths, err := ListStoragePaths(context.Background(), mockApi, "/")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, paths)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("lists object paths by prefix", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/bucket").
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{{
|
|
Name: "folder",
|
|
}, mockFile})
|
|
// Run test
|
|
paths, err := ListStoragePaths(context.Background(), mockApi, "bucket/")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{"folder/", "abstract.pdf"}, paths)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("throws error on object service unavailable", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/bucket").
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
paths, err := ListStoragePaths(context.Background(), mockApi, "bucket/")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.Empty(t, paths)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("lists object paths with pagination", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
expected := make([]string, storage.PAGE_LIMIT)
|
|
resp := make([]storage.ObjectResponse, storage.PAGE_LIMIT)
|
|
for i := 0; i < len(resp); i++ {
|
|
resp[i] = storage.ObjectResponse{Name: fmt.Sprintf("dir_%d", i)}
|
|
expected[i] = resp[i].Name + "/"
|
|
}
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/bucket").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "",
|
|
Search: "dir",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON(resp)
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/bucket").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "",
|
|
Search: "dir",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: storage.PAGE_LIMIT,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
// Run test
|
|
paths, err := ListStoragePaths(context.Background(), mockApi, "/bucket/dir")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.ElementsMatch(t, expected, paths)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|
|
|
|
func TestListStoragePathsAll(t *testing.T) {
|
|
t.Run("lists nested object paths", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
// List buckets
|
|
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",
|
|
}})
|
|
// List folders
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/test").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
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{{
|
|
Name: "folder",
|
|
}})
|
|
// List files
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "folder/",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{mockFile})
|
|
// Run test
|
|
paths, err := ListStoragePathsAll(context.Background(), mockApi, "")
|
|
// Check error
|
|
assert.NoError(t, err)
|
|
assert.ElementsMatch(t, []string{"private/folder/abstract.pdf", "test/"}, paths)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
|
|
t.Run("returns partial result on error", func(t *testing.T) {
|
|
// Setup mock api
|
|
defer gock.OffAll()
|
|
// List folders
|
|
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{{
|
|
Name: "error",
|
|
}, mockFile})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "empty/",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusOK).
|
|
JSON([]storage.ObjectResponse{})
|
|
gock.New("http://127.0.0.1").
|
|
Post("/storage/v1/object/list/private").
|
|
JSON(storage.ListObjectsQuery{
|
|
Prefix: "error/",
|
|
Search: "",
|
|
Limit: storage.PAGE_LIMIT,
|
|
Offset: 0,
|
|
}).
|
|
Reply(http.StatusServiceUnavailable)
|
|
// Run test
|
|
paths, err := ListStoragePathsAll(context.Background(), mockApi, "private/")
|
|
// Check error
|
|
assert.ErrorContains(t, err, "Error status 503:")
|
|
assert.ElementsMatch(t, []string{"private/abstract.pdf"}, paths)
|
|
assert.Empty(t, apitest.ListUnmatchedRequests())
|
|
})
|
|
}
|