package user import ( "testing" "time" modelOrder "github.com/perfect-panel/server/internal/model/order" modelUser "github.com/perfect-panel/server/internal/model/user" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func newFamilyBindingTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ DisableForeignKeyConstraintWhenMigrating: true, Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("open sqlite db: %v", err) } if err = db.Exec(` CREATE TABLE user_subscribe ( id integer primary key, user_id integer not null, order_id integer not null, subscribe_id integer not null, start_time datetime, expire_time datetime, token text, uuid text, status integer, created_at datetime, updated_at datetime )`).Error; err != nil { t.Fatalf("create user_subscribe schema: %v", err) } if err = db.Exec(` CREATE TABLE "order" ( id integer primary key, user_id integer not null, subscription_user_id integer not null, order_no text, subscribe_token text, status integer, subscribe_id integer, created_at datetime, updated_at datetime )`).Error; err != nil { t.Fatalf("create order schema: %v", err) } return db } func insertFamilyBindingTestOrder(t *testing.T, db *gorm.DB, order modelOrder.Order) { t.Helper() if err := db.Exec(` INSERT INTO "order" ( id, user_id, subscription_user_id, order_no, subscribe_token, status, subscribe_id, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, order.Id, order.UserId, order.SubscriptionUserId, order.OrderNo, order.SubscribeToken, order.Status, order.SubscribeId, order.CreatedAt, order.UpdatedAt, ).Error; err != nil { t.Fatalf("insert order %d: %v", order.Id, err) } } func insertFamilyBindingTestSubscribe(t *testing.T, db *gorm.DB, sub modelUser.Subscribe) { t.Helper() if err := db.Exec(` INSERT INTO user_subscribe ( id, user_id, order_id, subscribe_id, start_time, expire_time, token, uuid, status, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, sub.Id, sub.UserId, sub.OrderId, sub.SubscribeId, sub.StartTime, sub.ExpireTime, sub.Token, sub.UUID, sub.Status, sub.CreatedAt, sub.UpdatedAt, ).Error; err != nil { t.Fatalf("insert subscribe %d: %v", sub.Id, err) } } func TestMoveMemberSubscribesToOwnerMovesSubscribeAndCurrentOrder(t *testing.T) { db := newFamilyBindingTestDB(t) memberUserID := int64(1001) ownerUserID := int64(2001) now := time.Now().Add(-time.Hour) expireAt := now.Add(30 * 24 * time.Hour) currentOrder := modelOrder.Order{ Id: 9001, UserId: memberUserID, SubscriptionUserId: memberUserID, OrderNo: "order-current", SubscribeToken: "token-current", Status: 5, SubscribeId: 3001, CreatedAt: now, UpdatedAt: now, } unlinkedOrder := modelOrder.Order{ Id: 9002, UserId: memberUserID, SubscriptionUserId: memberUserID, OrderNo: "order-unlinked", SubscribeToken: "token-unlinked", Status: 5, SubscribeId: 3001, CreatedAt: now, UpdatedAt: now, } sub := modelUser.Subscribe{ Id: 7001, UserId: memberUserID, OrderId: currentOrder.Id, SubscribeId: currentOrder.SubscribeId, StartTime: now, ExpireTime: expireAt, Token: "token-current", UUID: "uuid-current", Status: 1, CreatedAt: now, UpdatedAt: now, } insertFamilyBindingTestOrder(t, db, currentOrder) insertFamilyBindingTestOrder(t, db, unlinkedOrder) insertFamilyBindingTestSubscribe(t, db, sub) var moved []modelUser.Subscribe if err := db.Transaction(func(tx *gorm.DB) error { var err error moved, err = moveMemberSubscribesToOwner(tx, memberUserID, ownerUserID) return err }); err != nil { t.Fatalf("move member subscribes: %v", err) } if len(moved) != 1 { t.Fatalf("moved subscribes length = %d, want 1", len(moved)) } if moved[0].UserId != memberUserID { t.Fatalf("moved cache copy user_id = %d, want original member %d", moved[0].UserId, memberUserID) } var gotSub modelUser.Subscribe if err := db.First(&gotSub, "id = ?", sub.Id).Error; err != nil { t.Fatalf("query moved subscribe: %v", err) } if gotSub.UserId != ownerUserID { t.Fatalf("subscribe user_id = %d, want owner %d", gotSub.UserId, ownerUserID) } if gotSub.Token == "" || gotSub.Token == sub.Token { t.Fatalf("subscribe token = %q, want regenerated from old token %q", gotSub.Token, sub.Token) } if gotSub.UUID == "" || gotSub.UUID == sub.UUID { t.Fatalf("subscribe uuid = %q, want regenerated from old uuid %q", gotSub.UUID, sub.UUID) } var gotOrder modelOrder.Order if err := db.First(&gotOrder, "id = ?", currentOrder.Id).Error; err != nil { t.Fatalf("query updated order: %v", err) } if gotOrder.SubscriptionUserId != ownerUserID { t.Fatalf("current order subscription_user_id = %d, want owner %d", gotOrder.SubscriptionUserId, ownerUserID) } if gotOrder.SubscribeToken != gotSub.Token { t.Fatalf("current order subscribe_token = %q, want regenerated subscribe token %q", gotOrder.SubscribeToken, gotSub.Token) } var gotUnlinkedOrder modelOrder.Order if err := db.First(&gotUnlinkedOrder, "id = ?", unlinkedOrder.Id).Error; err != nil { t.Fatalf("query unlinked order: %v", err) } if gotUnlinkedOrder.SubscriptionUserId != memberUserID { t.Fatalf("unlinked order subscription_user_id = %d, want unchanged member %d", gotUnlinkedOrder.SubscriptionUserId, memberUserID) } if gotUnlinkedOrder.SubscribeToken != unlinkedOrder.SubscribeToken { t.Fatalf("unlinked order subscribe_token = %q, want unchanged token %q", gotUnlinkedOrder.SubscribeToken, unlinkedOrder.SubscribeToken) } } func TestMoveMemberSubscribesToOwnerNoSubscribesIsNoop(t *testing.T) { db := newFamilyBindingTestDB(t) var moved []modelUser.Subscribe if err := db.Transaction(func(tx *gorm.DB) error { var err error moved, err = moveMemberSubscribesToOwner(tx, 1001, 2001) return err }); err != nil { t.Fatalf("move empty member subscribes: %v", err) } if len(moved) != 0 { t.Fatalf("moved subscribes length = %d, want 0", len(moved)) } } func TestTransferMemberSubscribesToOwnerStillDiscardsMemberSubscribes(t *testing.T) { db := newFamilyBindingTestDB(t) memberUserID := int64(1001) ownerUserID := int64(2001) now := time.Now().Add(-time.Hour) order := modelOrder.Order{ Id: 9001, UserId: memberUserID, SubscriptionUserId: memberUserID, OrderNo: "order-discard", SubscribeToken: "token-discard", Status: 5, SubscribeId: 3001, CreatedAt: now, UpdatedAt: now, } sub := modelUser.Subscribe{ Id: 7001, UserId: memberUserID, OrderId: order.Id, SubscribeId: order.SubscribeId, StartTime: now, ExpireTime: now.Add(30 * 24 * time.Hour), Token: "token-discard", UUID: "uuid-discard", Status: 1, CreatedAt: now, UpdatedAt: now, } insertFamilyBindingTestOrder(t, db, order) insertFamilyBindingTestSubscribe(t, db, sub) var removed []modelUser.Subscribe if err := db.Transaction(func(tx *gorm.DB) error { var err error removed, err = transferMemberSubscribesToOwner(tx, memberUserID, ownerUserID) return err }); err != nil { t.Fatalf("discard member subscribes: %v", err) } if len(removed) != 1 { t.Fatalf("removed subscribes length = %d, want 1", len(removed)) } if removed[0].UserId != memberUserID { t.Fatalf("removed cache copy user_id = %d, want original member %d", removed[0].UserId, memberUserID) } var memberCount int64 if err := db.Model(&modelUser.Subscribe{}).Where("user_id = ?", memberUserID).Count(&memberCount).Error; err != nil { t.Fatalf("count member subscribes: %v", err) } if memberCount != 0 { t.Fatalf("member subscribe count = %d, want 0", memberCount) } var ownerCount int64 if err := db.Model(&modelUser.Subscribe{}).Where("user_id = ?", ownerUserID).Count(&ownerCount).Error; err != nil { t.Fatalf("count owner subscribes: %v", err) } if ownerCount != 0 { t.Fatalf("owner subscribe count = %d, want 0", ownerCount) } var gotOrder modelOrder.Order if err := db.First(&gotOrder, "id = ?", order.Id).Error; err != nil { t.Fatalf("query order: %v", err) } if gotOrder.SubscriptionUserId != memberUserID { t.Fatalf("discard path order subscription_user_id = %d, want unchanged member %d", gotOrder.SubscriptionUserId, memberUserID) } }