172 lines
6.0 KiB
Go
172 lines
6.0 KiB
Go
|
|
package usecase_test
|
|||
|
|
|
|||
|
|
// T219 — M6 invite integration (spec §9.16 IV-01~IV-13)
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"apps/backend/internal/module/member/domain"
|
|||
|
|
"apps/backend/internal/module/member/usecase"
|
|||
|
|
|
|||
|
|
"github.com/stretchr/testify/require"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func TestIV_01_GetMyNetwork(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
m, _ := seedMember(t, store, svc, "iv01@test.local")
|
|||
|
|
net, err := svc.GetMyNetwork(context.Background(), m.UID)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
require.Equal(t, m.UID, net.Me.UID)
|
|||
|
|
require.NotEmpty(t, net.Me.InviteCode)
|
|||
|
|
require.Nil(t, net.Me.ParentUID)
|
|||
|
|
require.Empty(t, net.Downlines)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_02_ApplyLegalCode(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
inv, _ := seedMember(t, store, svc, "iv02-inv@test.local")
|
|||
|
|
me, _ := seedMember(t, store, svc, "iv02-me@test.local")
|
|||
|
|
net, err := svc.ApplyInviteCode(context.Background(), me.UID, inv.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
require.NotNil(t, net.Me.ParentUID)
|
|||
|
|
require.Equal(t, inv.UID, *net.Me.ParentUID)
|
|||
|
|
require.NotNil(t, net.Upline)
|
|||
|
|
require.Equal(t, inv.UID, net.Upline.UID)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_03_AlreadyBound(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
a, _ := seedMember(t, store, svc, "iv03a@test.local")
|
|||
|
|
b, _ := seedMember(t, store, svc, "iv03b@test.local")
|
|||
|
|
c, _ := seedMember(t, store, svc, "iv03c@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
_, err = svc.ApplyInviteCode(context.Background(), b.UID, c.InviteCode)
|
|||
|
|
require.ErrorIs(t, err, domain.ErrInviteAlreadyBound)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_04_ApplySelfCode(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
me, _ := seedMember(t, store, svc, "iv04@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), me.UID, me.InviteCode)
|
|||
|
|
require.ErrorIs(t, err, domain.ErrInviteCodeSelf)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_05_ApplyUnknownCode(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
me, _ := seedMember(t, store, svc, "iv05@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), me.UID, "NO-SUCH-CODE")
|
|||
|
|
require.ErrorIs(t, err, domain.ErrInviteCodeNotFound)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_06_ApplyEmptyCode(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
me, _ := seedMember(t, store, svc, "iv06@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), me.UID, " ")
|
|||
|
|
require.ErrorIs(t, err, domain.ErrInviteCodeRequired)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_07_DownlinesCount(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
inv, _ := seedMember(t, store, svc, "iv07-inv@test.local")
|
|||
|
|
d1, _ := seedMember(t, store, svc, "iv07-d1@test.local")
|
|||
|
|
d2, _ := seedMember(t, store, svc, "iv07-d2@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), d1.UID, inv.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
_, err = svc.ApplyInviteCode(context.Background(), d2.UID, inv.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
net, err := svc.GetMyNetwork(context.Background(), inv.UID)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
require.Len(t, net.Downlines, 2)
|
|||
|
|
require.Equal(t, 2, net.TotalDownlineCount)
|
|||
|
|
require.Equal(t, 2, net.Me.DownlineCount)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_08_GetTree(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
root, _ := seedMember(t, store, svc, "iv08-root@test.local")
|
|||
|
|
child, _ := seedMember(t, store, svc, "iv08-c@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), child.UID, root.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
forest, err := svc.GetInviteTree(context.Background())
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
require.NotEmpty(t, forest)
|
|||
|
|
// find root
|
|||
|
|
var found bool
|
|||
|
|
var walk func([]usecase.InviteTreeNode)
|
|||
|
|
walk = func(nodes []usecase.InviteTreeNode) {
|
|||
|
|
for _, n := range nodes {
|
|||
|
|
if n.UID == root.UID {
|
|||
|
|
found = true
|
|||
|
|
require.NotEmpty(t, n.Children)
|
|||
|
|
}
|
|||
|
|
walk(n.Children)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
walk(forest)
|
|||
|
|
require.True(t, found)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_09_MoveLegal(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
admin, _ := seedAdmin(t, store, svc, "iv09-admin@test.local")
|
|||
|
|
a, _ := seedMember(t, store, svc, "iv09-a@test.local")
|
|||
|
|
b, _ := seedMember(t, store, svc, "iv09-b@test.local")
|
|||
|
|
// b under a
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
// move b to root
|
|||
|
|
brief, err := svc.MoveInviteMember(context.Background(), admin.UID, b.UID, nil)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
require.Nil(t, brief.ParentUID)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_10_NoCycle(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
admin, _ := seedAdmin(t, store, svc, "iv10-admin@test.local")
|
|||
|
|
a, _ := seedMember(t, store, svc, "iv10-a@test.local")
|
|||
|
|
b, _ := seedMember(t, store, svc, "iv10-b@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
// move a under b → cycle
|
|||
|
|
_, err = svc.MoveInviteMember(context.Background(), admin.UID, a.UID, &b.UID)
|
|||
|
|
require.ErrorIs(t, err, domain.ErrInviteCycle)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_11_MoveToSelf(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
admin, _ := seedAdmin(t, store, svc, "iv11-admin@test.local")
|
|||
|
|
a, _ := seedMember(t, store, svc, "iv11-a@test.local")
|
|||
|
|
_, err := svc.MoveInviteMember(context.Background(), admin.UID, a.UID, &a.UID)
|
|||
|
|
require.ErrorIs(t, err, domain.ErrInviteSelfParent)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_12_MemberCannotMove(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
mem, _ := seedMember(t, store, svc, "iv12-mem@test.local")
|
|||
|
|
a, _ := seedMember(t, store, svc, "iv12-a@test.local")
|
|||
|
|
b, _ := seedMember(t, store, svc, "iv12-b@test.local")
|
|||
|
|
_, err := svc.MoveInviteMember(context.Background(), mem.UID, a.UID, &b.UID)
|
|||
|
|
require.ErrorIs(t, err, domain.ErrInviteNeedAdmin)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestIV_13_ListParentCandidatesExcludeSubtree(t *testing.T) {
|
|||
|
|
store, svc := newM1Svc()
|
|||
|
|
a, _ := seedMember(t, store, svc, "iv13-a@test.local")
|
|||
|
|
b, _ := seedMember(t, store, svc, "iv13-b@test.local")
|
|||
|
|
c, _ := seedMember(t, store, svc, "iv13-c@test.local")
|
|||
|
|
_, err := svc.ApplyInviteCode(context.Background(), b.UID, a.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
_, err = svc.ApplyInviteCode(context.Background(), c.UID, b.InviteCode)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
// exclude subtree of a → no a,b,c
|
|||
|
|
cands, err := svc.ListParentCandidates(context.Background(), a.UID)
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
for _, x := range cands {
|
|||
|
|
require.NotEqual(t, a.UID, x.UID)
|
|||
|
|
require.NotEqual(t, b.UID, x.UID)
|
|||
|
|
require.NotEqual(t, c.UID, x.UID)
|
|||
|
|
}
|
|||
|
|
}
|