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