You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
6.2 KiB
154 lines
6.2 KiB
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/cloudreve/Cloudreve/v4/ent"
|
|
"github.com/cloudreve/Cloudreve/v4/ent/enttest"
|
|
entuser "github.com/cloudreve/Cloudreve/v4/ent/user"
|
|
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
|
|
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestIsValidShareChecksOwnerAccess(t *testing.T) {
|
|
permissions := &boolset.BooleanSet{}
|
|
boolset.Set(types.GroupPermissionShare, true, permissions)
|
|
allowedGroup := &ent.Group{Permissions: permissions}
|
|
|
|
tests := []struct {
|
|
name string
|
|
status entuser.Status
|
|
group *ent.Group
|
|
wantErr error
|
|
}{
|
|
{name: "active owner with share permission", status: entuser.StatusActive, group: allowedGroup},
|
|
{name: "active owner without share permission", status: entuser.StatusActive, group: &ent.Group{Permissions: &boolset.BooleanSet{}}, wantErr: ErrSourceFileInvalid},
|
|
{name: "missing group", status: entuser.StatusActive, wantErr: ErrSourceFileInvalid},
|
|
{name: "missing permissions", status: entuser.StatusActive, group: &ent.Group{}, wantErr: ErrSourceFileInvalid},
|
|
{name: "manually banned owner", status: entuser.StatusManualBanned, group: allowedGroup, wantErr: ErrOwnerInactive},
|
|
{name: "system banned owner", status: entuser.StatusSysBanned, group: allowedGroup, wantErr: ErrOwnerInactive},
|
|
{name: "inactive owner", status: entuser.StatusInactive, group: allowedGroup, wantErr: ErrOwnerInactive},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
owner := &ent.User{ID: 1, Status: tt.status}
|
|
owner.SetGroup(tt.group)
|
|
share := &ent.Share{}
|
|
share.SetUser(owner)
|
|
share.SetFile(&ent.File{OwnerID: owner.ID, FileChildren: 1})
|
|
|
|
require.ErrorIs(t, IsValidShare(share), tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsValidShareMultiFile(t *testing.T) {
|
|
permissions := &boolset.BooleanSet{}
|
|
boolset.Set(types.GroupPermissionShare, true, permissions)
|
|
group := &ent.Group{Permissions: permissions}
|
|
owner := &ent.User{ID: 1, Status: entuser.StatusActive}
|
|
owner.SetGroup(group)
|
|
|
|
alive := &ent.File{OwnerID: owner.ID, FileChildren: 1}
|
|
dead := &ent.File{OwnerID: owner.ID, FileChildren: 0}
|
|
foreign := &ent.File{OwnerID: 2, FileChildren: 1}
|
|
|
|
newShare := func(files ...*ent.File) *ent.Share {
|
|
s := &ent.Share{}
|
|
s.SetUser(owner)
|
|
s.SetFile(dead)
|
|
s.Edges.Files = files
|
|
return s
|
|
}
|
|
|
|
// Multi-file shares stay valid while at least one linked file is alive.
|
|
require.NoError(t, IsValidShare(newShare(dead, alive)))
|
|
require.NoError(t, IsValidShare(newShare(alive)))
|
|
require.ErrorIs(t, IsValidShare(newShare(dead)), ErrSourceFileInvalid)
|
|
require.ErrorIs(t, IsValidShare(newShare(foreign)), ErrSourceFileInvalid)
|
|
require.ErrorIs(t, IsValidShare(newShare(dead, foreign)), ErrSourceFileInvalid)
|
|
|
|
// Anchor-dead but linked-alive shares remain valid.
|
|
require.NoError(t, IsValidShare(newShare(dead, dead, alive)))
|
|
}
|
|
|
|
func TestShareUpsertFileIDs(t *testing.T) {
|
|
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
|
|
t.Cleanup(func() { require.NoError(t, client.Close()) })
|
|
ctx := context.Background()
|
|
|
|
permissions := &boolset.BooleanSet{}
|
|
boolset.Set(types.GroupPermissionShare, true, permissions)
|
|
group := client.Group.Create().SetName("g").SetPermissions(permissions).SaveX(ctx)
|
|
owner := client.User.Create().SetEmail("owner@example.com").SetNick("owner").SetGroup(group).SaveX(ctx)
|
|
root := client.File.Create().SetName(RootFolderName).SetType(int(types.FileTypeFolder)).SetOwner(owner).SaveX(ctx)
|
|
f1 := client.File.Create().SetName("a.txt").SetType(int(types.FileTypeFile)).SetOwner(owner).SetParent(root).SaveX(ctx)
|
|
f2 := client.File.Create().SetName("b.txt").SetType(int(types.FileTypeFile)).SetOwner(owner).SetParent(root).SaveX(ctx)
|
|
|
|
shareClient := NewShareClient(client, conf.SQLiteDB, nil)
|
|
|
|
// Multi-file share: files edge holds the full set, anchor included.
|
|
s, err := shareClient.Upsert(ctx, &CreateShareParams{
|
|
OwnerID: owner.ID,
|
|
FileID: f1.ID,
|
|
FileIDs: []int{f1.ID, f2.ID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
loadCtx := context.WithValue(ctx, LoadShareFiles{}, true)
|
|
loaded, err := shareClient.GetByID(loadCtx, s.ID)
|
|
require.NoError(t, err)
|
|
require.Len(t, loaded.Edges.Files, 2)
|
|
|
|
// Single-file share: no files edge — legacy behavior unchanged.
|
|
single, err := shareClient.Upsert(ctx, &CreateShareParams{
|
|
OwnerID: owner.ID,
|
|
FileID: f1.ID,
|
|
FileIDs: []int{f1.ID},
|
|
})
|
|
require.NoError(t, err)
|
|
loaded, err = shareClient.GetByID(loadCtx, single.ID)
|
|
require.NoError(t, err)
|
|
require.Empty(t, loaded.Edges.Files)
|
|
}
|
|
|
|
func TestShareClientRevalidatesOwnerGroup(t *testing.T) {
|
|
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
|
|
t.Cleanup(func() { require.NoError(t, client.Close()) })
|
|
ctx := context.Background()
|
|
|
|
permissions := &boolset.BooleanSet{}
|
|
boolset.Set(types.GroupPermissionShare, true, permissions)
|
|
group := client.Group.Create().SetName("sharing enabled").SetPermissions(permissions).SaveX(ctx)
|
|
restrictedGroup := client.Group.Create().SetName("sharing disabled").SetPermissions(&boolset.BooleanSet{}).SaveX(ctx)
|
|
owner := client.User.Create().SetEmail("owner@example.com").SetNick("owner").SetGroup(group).SaveX(ctx)
|
|
root := client.File.Create().SetName(RootFolderName).SetType(int(types.FileTypeFolder)).SetOwner(owner).SaveX(ctx)
|
|
file := client.File.Create().SetName("shared.txt").SetType(int(types.FileTypeFile)).SetOwner(owner).SetParent(root).SaveX(ctx)
|
|
share := client.Share.Create().SetUser(owner).SetFile(file).SaveX(ctx)
|
|
shareClient := NewShareClient(client, conf.SQLiteDB, nil)
|
|
|
|
// Share-info and listing callers only request the owner and file edges.
|
|
ctx = context.WithValue(ctx, LoadShareUser{}, true)
|
|
ctx = context.WithValue(ctx, LoadShareFile{}, true)
|
|
checkShare := func(wantErr error) {
|
|
t.Helper()
|
|
current, err := shareClient.GetByID(ctx, share.ID)
|
|
require.NoError(t, err)
|
|
require.ErrorIs(t, IsValidShare(current), wantErr)
|
|
}
|
|
|
|
checkShare(nil)
|
|
client.Group.UpdateOne(group).SetPermissions(&boolset.BooleanSet{}).SaveX(ctx)
|
|
checkShare(ErrSourceFileInvalid)
|
|
client.Group.UpdateOne(group).SetPermissions(permissions).SaveX(ctx)
|
|
checkShare(nil)
|
|
client.User.UpdateOne(owner).SetGroup(restrictedGroup).SaveX(ctx)
|
|
checkShare(ErrSourceFileInvalid)
|
|
client.User.UpdateOne(owner).SetGroup(group).SaveX(ctx)
|
|
checkShare(nil)
|
|
}
|