// MembershipRepo 仓储集成测试(t_membership 成员关系,登录两步协议支撑)。 // // 依赖 test_helpers_test.go 的 setup/insertFixture;未设置 UNIFIED_TEST_DSN 时跳过。 package repository import ( "context" stderrors "errors" "testing" apperrors "yuansiwei/unified-backend/internal/infra/errors" ) // TestMembershipRepo_ListByCanonicalUserID 多 membership 按 created_at 升序 / 无 membership 返非 nil 空切片。 func TestMembershipRepo_ListByCanonicalUserID(t *testing.T) { gdb := setup(t) ctx := context.Background() fx := insertFixture(t, gdb, ctx) repo := NewMembershipRepo(gdb) t.Run("命中_按created_at升序", func(t *testing.T) { list, err := repo.ListByCanonicalUserID(ctx, fx.canonical) if err != nil { t.Fatalf("ListByCanonicalUserID: %v", err) } if len(list) != 3 { t.Fatalf("期望 3 条 membership,实际 %d", len(list)) } // created_at 升序:instB(academic,-30s) → instA(teacher,-20s) → instA(finance,-10s) if list[0].InstitutionID != fx.instB || list[0].Role != "academic" { t.Errorf("第 1 条 = (%d, %s),期望 (%d, academic)", list[0].InstitutionID, list[0].Role, fx.instB) } if list[1].InstitutionID != fx.instA || list[1].Role != "teacher" { t.Errorf("第 2 条 = (%d, %s),期望 (%d, teacher)", list[1].InstitutionID, list[1].Role, fx.instA) } if list[2].InstitutionID != fx.instA || list[2].Role != "finance" { t.Errorf("第 3 条 = (%d, %s),期望 (%d, finance)", list[2].InstitutionID, list[2].Role, fx.instA) } }) t.Run("无membership_返回非nil空切片", func(t *testing.T) { list, err := repo.ListByCanonicalUserID(ctx, fx.other) if err != nil { t.Fatalf("ListByCanonicalUserID(无 membership 用户): %v", err) } if list == nil { t.Errorf("期望非 nil 空切片(JSON 序列化为 [] 而非 null),实际 nil") } if len(list) != 0 { t.Errorf("期望 0 条,实际 %d", len(list)) } }) } // TestMembershipRepo_GetByUserInstitutionCampus 命中 / 同作用域多角色取最新 / 未命中。 func TestMembershipRepo_GetByUserInstitutionCampus(t *testing.T) { gdb := setup(t) ctx := context.Background() fx := insertFixture(t, gdb, ctx) repo := NewMembershipRepo(gdb) t.Run("命中_单角色", func(t *testing.T) { // instB 下只有 academic 一条 m, err := repo.GetByUserInstitutionCampus(ctx, fx.canonical, fx.instB, 0) if err != nil { t.Fatalf("GetByUserInstitutionCampus(instB): %v", err) } if m.Role != "academic" { t.Errorf("instB role = %q,期望 academic", m.Role) } }) t.Run("同作用域多角色_取created_at最新", func(t *testing.T) { // instA 下有 teacher(-20s) + finance(-10s),finance 较新 → 应取 finance m, err := repo.GetByUserInstitutionCampus(ctx, fx.canonical, fx.instA, 0) if err != nil { t.Fatalf("GetByUserInstitutionCampus(instA): %v", err) } if m.Role != "finance" { t.Errorf("instA 多角色应取最新(finance),实际 %q", m.Role) } }) t.Run("未命中_返回ErrNotFound", func(t *testing.T) { // 该用户在 instC(不存在的机构)下无 membership instC := int64(999) _, err := repo.GetByUserInstitutionCampus(ctx, fx.canonical, instC, 0) if !stderrors.Is(err, apperrors.ErrNotFound) { t.Fatalf("期望 ErrNotFound,实际 %v", err) } }) }