| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- // cmd/api 装配单测(AC-11 硬要点 5):DivideService 必须注入「首次扣费上课记录」端口。
- //
- // 这是 D11(撤销首扣订单 → 改绑保留分成)在生产生效的前提:
- // 未注入时 DivideService 只能走"回滚删除"分支 → 撤销首扣订单会把真实分成整组冲掉(少发钱)。
- // 本用例用真实 sqlite + 真实 RecordRepo 跑一遍 DeleteLog 的两条分支,防止"注入被误删"回归。
- package main
- import (
- "context"
- "path/filepath"
- "testing"
- "time"
- gormsqlite "github.com/glebarez/sqlite"
- "gorm.io/gorm"
- "yuansiwei/unified-backend/internal/academic/model"
- "yuansiwei/unified-backend/internal/academic/money"
- )
- // newWiringDB 装配用例库(分账明细 + 打卡明细 2 表)。
- //
- // 逐表迁移(SQLite 索引名库级唯一;两张表都有 uk_biz_no/index,见 service 测试同名助手注释)。
- func newWiringDB(t *testing.T) *gorm.DB {
- t.Helper()
- dsn := filepath.Join(t.TempDir(), "academic_wiring_test.db")
- db, err := gorm.Open(gormsqlite.Open(dsn), &gorm.Config{})
- if err != nil {
- t.Fatalf("gorm.Open(装配文件库) 失败: %v", err)
- }
- t.Cleanup(func() {
- if sqlDB, e := db.DB(); e == nil {
- _ = sqlDB.Close()
- }
- })
- for _, tbl := range []any{&model.UserDivideInto{}, &model.UserDivideIntoLog{}, &model.CoursesRecordStudent{}} {
- if err := db.AutoMigrate(tbl); err != nil {
- t.Fatalf("AutoMigrate(%T) 失败: %v", tbl, err)
- }
- stmt := &gorm.Statement{DB: db}
- if err := stmt.Parse(tbl); err != nil {
- t.Fatalf("解析表名(%T) 失败: %v", tbl, err)
- }
- var names []string
- if err := db.Raw("SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND name NOT LIKE 'sqlite_%'",
- stmt.Schema.Table).Scan(&names).Error; err != nil {
- t.Fatalf("读取索引清单失败: %v", err)
- }
- for _, name := range names {
- if err := db.Exec("DROP INDEX IF EXISTS `" + name + "`").Error; err != nil {
- t.Fatalf("删除索引(%s) 失败: %v", name, err)
- }
- }
- }
- return db
- }
- // TestAcademicWiringFirstChargedInjected 装配断言:端口已注入,且 D11 改绑分支真实生效。
- func TestAcademicWiringFirstChargedInjected(t *testing.T) {
- db := newWiringDB(t)
- svc := newAcademicServices(db)
- if svc == nil || svc.Divide == nil {
- t.Fatal("newAcademicServices 未装配 DivideService")
- }
- if !svc.FirstChargedInjected {
- t.Fatal("FirstChargedInjected=false —— 首次扣费端口未注入(D11 改绑分支将永远不生效)")
- }
- ctx := context.Background()
- student := "01HSTUDENT000000000000042"
- // ① 学生存在首扣记录(record_id=900,cost>0 且 end_time 非空)
- endTime := time.Date(2026, 4, 1, 10, 0, 0, 0, time.UTC)
- if err := db.Create(&model.CoursesRecordStudent{
- BizNo: "RS-FIRST", CoursesID: 1, InstitutionID: 1, CampusID: 10, RecordID: 900,
- StudentID: student, Type: model.CoursesRecordStudentTypeNormal,
- StartTime: time.Date(2026, 4, 1, 9, 0, 0, 0, time.UTC), EndTime: &endTime, Cost: "50.00",
- }).Error; err != nil {
- t.Fatalf("seed 首扣打卡明细失败: %v", err)
- }
- // 分成明细(type=2 按课次,order_no=500)
- log := &model.UserDivideIntoLog{
- Type: model.DivideTypePerLesson, UserID: "01HAGENT0000000000000001",
- StudentID: student, OrderNo: strPtr("500"), Money: "10.00",
- }
- if err := db.Create(log).Error; err != nil {
- t.Fatalf("seed 分成明细失败: %v", err)
- }
- if err := svc.Divide.DeleteLog(ctx, student, 500); err != nil {
- t.Fatalf("DeleteLog 失败: %v", err)
- }
- var rebound model.UserDivideIntoLog
- if err := db.Where("id = ?", log.ID).First(&rebound).Error; err != nil {
- t.Fatalf("改绑分支不应删除明细,读取失败: %v", err)
- }
- if rebound.OrderNo == nil || *rebound.OrderNo != "900" {
- t.Fatalf("order_no=%v,期望 900(改绑到首次扣费的 record_id)", rebound.OrderNo)
- }
- if got, _ := money.Parse(rebound.Money); got.Cents() != 1000 {
- t.Fatalf("改绑不应改金额:%s", rebound.Money)
- }
- // ② 反向断言:无首扣记录的学生 → 走回滚删除分支(明细被删)
- other := "01HSTUDENT000000000000043"
- orphan := &model.UserDivideIntoLog{
- Type: model.DivideTypePerLesson, UserID: "01HAGENT0000000000000001",
- StudentID: other, OrderNo: strPtr("600"), Money: "20.00",
- }
- if err := db.Create(orphan).Error; err != nil {
- t.Fatalf("seed 无首扣分成明细失败: %v", err)
- }
- if err := svc.Divide.DeleteLog(ctx, other, 600); err != nil {
- t.Fatalf("DeleteLog(无首扣) 失败: %v", err)
- }
- var count int64
- if err := db.Model(&model.UserDivideIntoLog{}).Where("id = ?", orphan.ID).Count(&count).Error; err != nil {
- t.Fatalf("统计失败: %v", err)
- }
- if count != 0 {
- t.Fatalf("无首扣记录时应回滚删除明细,实际仍存在 %d 行", count)
- }
- }
- // strPtr 字符串指针构造(空串 → nil)。
- func strPtr(s string) *string {
- if s == "" {
- return nil
- }
- return &s
- }
|