s3api: fix bucket-root listing w/ delimiter (#7827)
* s3api: fix bucket-root listing w/ delimiter * test: improve mock robustness for bucket-root listing test - Make testListEntriesStream implement interface explicitly without embedding - Add prefix filtering logic to testFilerClient to simulate real filer behavior - Special-case prefix='/' to not filter for bucket root compatibility - Add required imports for metadata and strings packages This addresses review comments about test mock brittleness and accuracy. * test: add clarifying comment for mock filtering behavior Add detailed comment explaining which ListEntriesRequest parameters are implemented (Prefix) vs ignored (Limit, StartFromFileName, etc.) in the test mock to improve code documentation and future maintenance. * logging * less logs * less check if already locked
This commit is contained in:
@@ -1,14 +1,63 @@
|
||||
package s3api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||
"github.com/stretchr/testify/assert"
|
||||
grpc "google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type testListEntriesStream struct {
|
||||
entries []*filer_pb.Entry
|
||||
idx int
|
||||
}
|
||||
|
||||
func (s *testListEntriesStream) Recv() (*filer_pb.ListEntriesResponse, error) {
|
||||
if s.idx >= len(s.entries) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
resp := &filer_pb.ListEntriesResponse{Entry: s.entries[s.idx]}
|
||||
s.idx++
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *testListEntriesStream) Header() (metadata.MD, error) { return metadata.MD{}, nil }
|
||||
func (s *testListEntriesStream) Trailer() metadata.MD { return metadata.MD{} }
|
||||
func (s *testListEntriesStream) Close() error { return nil }
|
||||
func (s *testListEntriesStream) Context() context.Context { return context.Background() }
|
||||
func (s *testListEntriesStream) SendMsg(m interface{}) error { return nil }
|
||||
func (s *testListEntriesStream) RecvMsg(m interface{}) error { return nil }
|
||||
func (s *testListEntriesStream) CloseSend() error { return nil }
|
||||
|
||||
type testFilerClient struct {
|
||||
filer_pb.SeaweedFilerClient
|
||||
entriesByDir map[string][]*filer_pb.Entry
|
||||
}
|
||||
|
||||
func (c *testFilerClient) ListEntries(ctx context.Context, in *filer_pb.ListEntriesRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[filer_pb.ListEntriesResponse], error) {
|
||||
entries := c.entriesByDir[in.Directory]
|
||||
// Simplified mock: implements basic prefix filtering but ignores Limit, StartFromFileName, and InclusiveStartFrom
|
||||
// to keep test logic focused. Prefix "/" is treated as no filter for bucket root compatibility.
|
||||
if in.Prefix != "" && in.Prefix != "/" {
|
||||
filtered := make([]*filer_pb.Entry, 0)
|
||||
for _, e := range entries {
|
||||
if strings.HasPrefix(e.Name, in.Prefix) {
|
||||
filtered = append(filtered, e)
|
||||
}
|
||||
}
|
||||
entries = filtered
|
||||
}
|
||||
return &testListEntriesStream{entries: entries}, nil
|
||||
}
|
||||
|
||||
func TestListObjectsHandler(t *testing.T) {
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/v2-RESTBucketGET.html
|
||||
@@ -53,6 +102,13 @@ func Test_normalizePrefixMarker(t *testing.T) {
|
||||
wantAlignedPrefix string
|
||||
wantAlignedMarker string
|
||||
}{
|
||||
{"bucket root listing with delimiter",
|
||||
args{"/",
|
||||
""},
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{"prefix is a directory",
|
||||
args{"/parentDir/data/",
|
||||
""},
|
||||
@@ -146,6 +202,31 @@ func TestAllowUnorderedParameterValidation(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDoListFilerEntries_BucketRootPrefixSlashDelimiterSlash_ListsDirectories(t *testing.T) {
|
||||
// Regression test for a bug where doListFilerEntries returned early when
|
||||
// prefix == "/" && delimiter == "/", causing bucket-root folder listings
|
||||
// (e.g. Veeam v13) to return empty results.
|
||||
|
||||
s3a := &S3ApiServer{}
|
||||
client := &testFilerClient{
|
||||
entriesByDir: map[string][]*filer_pb.Entry{
|
||||
"/buckets/test-bucket": {
|
||||
{Name: "Veeam", IsDirectory: true, Attributes: &filer_pb.FuseAttributes{}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cursor := &ListingCursor{maxKeys: 1000}
|
||||
seen := make([]string, 0)
|
||||
_, err := s3a.doListFilerEntries(client, "/buckets/test-bucket", "/", cursor, "", "/", false, func(dir string, entry *filer_pb.Entry) {
|
||||
if entry.IsDirectory {
|
||||
seen = append(seen, entry.Name)
|
||||
}
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, seen, "Veeam")
|
||||
}
|
||||
|
||||
func TestAllowUnorderedWithDelimiterValidation(t *testing.T) {
|
||||
t.Run("should return error when allow-unordered=true and delimiter are both present", func(t *testing.T) {
|
||||
// Create a request with both allow-unordered=true and delimiter
|
||||
|
||||
Reference in New Issue
Block a user