thread-master/apps/backend/internal/module/member/usecase/invite_test.go

172 lines
6.0 KiB
Go
Raw Normal View History

2026-07-13 03:18:08 +00:00
package usecase_test
// T219 — M6 invite integration (spec §9.16 IV-01IV-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)
}
}