package store import ( "context" "testing" "time" ) // 邀请码 = 把「加入权」发出去。这组测试钉死:正常入组、重复扫不重复消耗名额、 // 过期/撤销/超人数一律拒绝——每一条错了都是「陌生人白嫖共享租户积分」的口子。 func mkUser(t *testing.T, p *Postgres, openid string) string { t.Helper() u, err := p.CreateWechatUser(context.Background(), openid, "微信用户_"+openid) if err != nil { t.Fatalf("建用户失败: %v", err) } return u.ID } func TestRedeemInvite_HappyAndIdempotent(t *testing.T) { p := newTestStore(t) seedTenant(t, p, "t1") ctx := context.Background() inv, err := p.CreateInvite(ctx, "t1", "inviter", RoleMember, time.Now().Add(time.Hour), 0) if err != nil { t.Fatalf("建码失败: %v", err) } uid := mkUser(t, p, "o-1") name, ok := p.RedeemInvite(ctx, inv.Token, uid) if !ok { t.Fatal("正常令牌应入组成功") } if name != "T-t1" { t.Fatalf("应回租户名 T-t1,得 %q", name) } if r := p.MemberRole(ctx, "t1", uid); r != RoleMember { t.Fatalf("入组后应为 member,得 %q", r) } // 同一人重复扫:仍成功但不重复消耗名额。 if _, ok := p.RedeemInvite(ctx, inv.Token, uid); !ok { t.Fatal("重复扫应幂等成功") } got := p.GetInviteByToken(ctx, inv.Token) if got.UsedCount != 1 { t.Fatalf("同一人重复扫,UsedCount 应仍为 1,得 %d", got.UsedCount) } } func TestRedeemInvite_ExpiredAndRevoked(t *testing.T) { p := newTestStore(t) seedTenant(t, p, "t1") ctx := context.Background() expired, _ := p.CreateInvite(ctx, "t1", "inv", RoleMember, time.Now().Add(-time.Minute), 0) if _, ok := p.RedeemInvite(ctx, expired.Token, mkUser(t, p, "o-exp")); ok { t.Fatal("过期码不该能入组") } revoked, _ := p.CreateInvite(ctx, "t1", "inv", RoleMember, time.Now().Add(time.Hour), 0) if err := p.RevokeInvite(ctx, "t1", revoked.ID); err != nil { t.Fatalf("撤销失败: %v", err) } if _, ok := p.RedeemInvite(ctx, revoked.Token, mkUser(t, p, "o-rev")); ok { t.Fatal("已撤销码不该能入组") } } func TestRedeemInvite_MaxUses(t *testing.T) { p := newTestStore(t) seedTenant(t, p, "t1") ctx := context.Background() inv, _ := p.CreateInvite(ctx, "t1", "inv", RoleViewer, time.Now().Add(time.Hour), 2) if _, ok := p.RedeemInvite(ctx, inv.Token, mkUser(t, p, "u1")); !ok { t.Fatal("第 1 人应成功") } if _, ok := p.RedeemInvite(ctx, inv.Token, mkUser(t, p, "u2")); !ok { t.Fatal("第 2 人应成功") } if _, ok := p.RedeemInvite(ctx, inv.Token, mkUser(t, p, "u3")); ok { t.Fatal("第 3 人应被名额闸拦下") } if got := p.GetInviteByToken(ctx, inv.Token); got.UsedCount != 2 { t.Fatalf("UsedCount 应为 2,得 %d", got.UsedCount) } } func TestRedeemInvite_RejectsOwnerRole(t *testing.T) { p := newTestStore(t) seedTenant(t, p, "t1") if _, err := p.CreateInvite(context.Background(), "t1", "inv", RoleOwner, time.Now().Add(time.Hour), 0); err == nil { t.Fatal("二维码不该能生成 owner 邀请") } }