parent
1d091c4100
commit
7f11ec9e88
File diff suppressed because one or more lines are too long
@ -0,0 +1,159 @@
|
|||||||
|
package inventory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/ent/enttest"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
|
||||||
|
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newTestClient opens an in-memory SQLite ent client with the schema created.
|
||||||
|
func newTestClient(t *testing.T) *ent.Client {
|
||||||
|
t.Helper()
|
||||||
|
client := enttest.Open(t, "sqlite3", "file:apptest?mode=memory&cache=shared&_fk=1")
|
||||||
|
t.Cleanup(func() { _ = client.Close() })
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePolicy(t *testing.T, client *ent.Client, name string) *ent.StoragePolicy {
|
||||||
|
t.Helper()
|
||||||
|
p, err := client.StoragePolicy.Create().SetName(name).SetType("local").Save(context.Background())
|
||||||
|
require.NoError(t, err)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMultiStoragePolicyPerGroup covers APP-100: a group may hold several allowed
|
||||||
|
// policies, ListPoliciesByGroup returns the full set (always including the default),
|
||||||
|
// and membership can be decided from it.
|
||||||
|
func TestMultiStoragePolicyPerGroup(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
client := newTestClient(t)
|
||||||
|
gc := NewGroupClient(client, "sqlite", nil)
|
||||||
|
sc := NewStoragePolicyClient(client, nil)
|
||||||
|
|
||||||
|
p1 := makePolicy(t, client, "local-1")
|
||||||
|
p2 := makePolicy(t, client, "s3-2")
|
||||||
|
p3 := makePolicy(t, client, "unassigned-3")
|
||||||
|
|
||||||
|
// Create a group with default = p1 and allowed = {p1, p2}.
|
||||||
|
created, err := gc.Upsert(ctx, &ent.Group{
|
||||||
|
Name: "multi",
|
||||||
|
Permissions: &boolset.BooleanSet{},
|
||||||
|
Settings: &types.GroupSetting{},
|
||||||
|
Edges: ent.GroupEdges{
|
||||||
|
StoragePolicies: p1,
|
||||||
|
StoragePoliciesAllowed: []*ent.StoragePolicy{p1, p2},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// The allowed set must be exactly {p1, p2}.
|
||||||
|
allowed, err := sc.ListPoliciesByGroup(ctx, created)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ids := policyIDSet(allowed)
|
||||||
|
assert.ElementsMatch(t, []int{p1.ID, p2.ID}, keysOf(ids), "allowed set should be {p1,p2}")
|
||||||
|
|
||||||
|
// Membership: p1/p2 allowed, p3 rejected (the guard used at upload time).
|
||||||
|
assert.True(t, ids[p1.ID], "p1 should be allowed")
|
||||||
|
assert.True(t, ids[p2.ID], "p2 should be allowed")
|
||||||
|
assert.False(t, ids[p3.ID], "p3 must NOT be allowed (out-of-set upload rejected)")
|
||||||
|
|
||||||
|
// The default remains p1.
|
||||||
|
def, err := sc.GetByGroup(ctx, created)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, p1.ID, def.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSinglePolicyGroupNoRegression covers the "no regression" criterion: a group
|
||||||
|
// created with only a default policy still resolves that policy as its allowed set.
|
||||||
|
func TestSinglePolicyGroupNoRegression(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
client := newTestClient(t)
|
||||||
|
gc := NewGroupClient(client, "sqlite", nil)
|
||||||
|
sc := NewStoragePolicyClient(client, nil)
|
||||||
|
|
||||||
|
p1 := makePolicy(t, client, "only")
|
||||||
|
|
||||||
|
created, err := gc.Upsert(ctx, &ent.Group{
|
||||||
|
Name: "single",
|
||||||
|
Permissions: &boolset.BooleanSet{},
|
||||||
|
Settings: &types.GroupSetting{},
|
||||||
|
Edges: ent.GroupEdges{StoragePolicies: p1},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
allowed, err := sc.ListPoliciesByGroup(ctx, created)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ids := policyIDSet(allowed)
|
||||||
|
// Even though no allowed edge was set, the default is guaranteed to be a member.
|
||||||
|
assert.ElementsMatch(t, []int{p1.ID}, keysOf(ids))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBackfillGroupAvailablePolicies verifies the idempotent migration that seeds the
|
||||||
|
// allowed set from the legacy single storage_policy_id.
|
||||||
|
func TestBackfillGroupAvailablePolicies(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
client := newTestClient(t)
|
||||||
|
sc := NewStoragePolicyClient(client, nil)
|
||||||
|
|
||||||
|
p1 := makePolicy(t, client, "legacy")
|
||||||
|
|
||||||
|
// Legacy group: only storage_policy_id set, no allowed edge.
|
||||||
|
legacy, err := client.Group.Create().
|
||||||
|
SetName("legacy").
|
||||||
|
SetPermissions(&boolset.BooleanSet{}).
|
||||||
|
SetSettings(&types.GroupSetting{}).
|
||||||
|
SetStoragePolicyID(p1.ID).
|
||||||
|
Save(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Before backfill: empty allowed set at the edge level.
|
||||||
|
pre, err := client.Group.QueryStoragePoliciesAllowed(legacy).All(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, pre, 0)
|
||||||
|
|
||||||
|
// Run the backfill twice to assert idempotency.
|
||||||
|
require.NoError(t, migrateGroupAvailablePolicies(logging.NewConsoleLogger(logging.LevelError), client, ctx))
|
||||||
|
require.NoError(t, migrateGroupAvailablePolicies(logging.NewConsoleLogger(logging.LevelError), client, ctx))
|
||||||
|
|
||||||
|
post, err := client.Group.QueryStoragePoliciesAllowed(legacy).All(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.ElementsMatch(t, []int{p1.ID}, policyIDsOf(post), "backfill should seed the default into the allowed set exactly once")
|
||||||
|
|
||||||
|
// And ListPoliciesByGroup now reflects it.
|
||||||
|
reloaded, err := client.Group.Get(ctx, legacy.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
allowed, err := sc.ListPoliciesByGroup(ctx, reloaded)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.ElementsMatch(t, []int{p1.ID}, policyIDsOf(allowed))
|
||||||
|
}
|
||||||
|
|
||||||
|
func policyIDSet(ps []*ent.StoragePolicy) map[int]bool {
|
||||||
|
m := make(map[int]bool, len(ps))
|
||||||
|
for _, p := range ps {
|
||||||
|
m[p.ID] = true
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func keysOf(m map[int]bool) []int {
|
||||||
|
out := make([]int, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
out = append(out, k)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func policyIDsOf(ps []*ent.StoragePolicy) []int {
|
||||||
|
out := make([]int, 0, len(ps))
|
||||||
|
for _, p := range ps {
|
||||||
|
out = append(out, p.ID)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
Loading…
Reference in new issue