package orderLogic import ( "context" "testing" "time" modelOrder "github.com/perfect-panel/server/internal/model/order" "github.com/perfect-panel/server/internal/model/subscribe" "github.com/perfect-panel/server/internal/model/user" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) func setupActivationEligibilityDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err) sqls := []string{ `CREATE TABLE IF NOT EXISTS "user" ( id INTEGER PRIMARY KEY AUTOINCREMENT, password VARCHAR(100) NOT NULL DEFAULT '', created_at DATETIME, updated_at DATETIME, deleted_at DATETIME DEFAULT NULL )`, `CREATE TABLE IF NOT EXISTS "user_device" ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL DEFAULT 0, identifier VARCHAR(255) NOT NULL DEFAULT '' UNIQUE, created_at DATETIME, updated_at DATETIME )`, `CREATE TABLE IF NOT EXISTS "user_family" ( id INTEGER PRIMARY KEY AUTOINCREMENT, owner_user_id INTEGER NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1, deleted_at DATETIME DEFAULT NULL )`, `CREATE TABLE IF NOT EXISTS "user_family_member" ( id INTEGER PRIMARY KEY AUTOINCREMENT, family_id INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL DEFAULT 0, role TINYINT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 0, join_source VARCHAR(32) NOT NULL DEFAULT '', deleted_at DATETIME DEFAULT NULL )`, `CREATE TABLE IF NOT EXISTS "order" ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL DEFAULT 0, order_no VARCHAR(255) NOT NULL DEFAULT '' UNIQUE, type TINYINT NOT NULL DEFAULT 1, status TINYINT NOT NULL DEFAULT 1, subscribe_id INTEGER NOT NULL DEFAULT 0, quantity INTEGER NOT NULL DEFAULT 1, created_at DATETIME, updated_at DATETIME )`, } for _, sql := range sqls { require.NoError(t, db.Exec(sql).Error) } return db } func insertActivationUser(t *testing.T, db *gorm.DB, userID int64, createdAt time.Time) { t.Helper() require.NoError(t, db.Exec( `INSERT INTO "user" (id, created_at, updated_at) VALUES (?, ?, datetime('now'))`, userID, createdAt.UTC().Format("2006-01-02 15:04:05"), ).Error) } func insertActivationDevice(t *testing.T, db *gorm.DB, userID int64, identifier string, createdAt time.Time) { t.Helper() require.NoError(t, db.Exec( `INSERT INTO "user_device" (user_id, identifier, created_at, updated_at) VALUES (?, ?, ?, datetime('now'))`, userID, identifier, createdAt.UTC().Format("2006-01-02 15:04:05"), ).Error) } func insertActivationFamily(t *testing.T, db *gorm.DB, familyID, ownerUserID int64) { t.Helper() require.NoError(t, db.Exec( `INSERT INTO "user_family" (id, owner_user_id, status) VALUES (?, ?, 1)`, familyID, ownerUserID, ).Error) } func insertActivationFamilyMember(t *testing.T, db *gorm.DB, familyID, userID int64, role, status uint8, joinSource string) { t.Helper() require.NoError(t, db.Exec( `INSERT INTO "user_family_member" (family_id, user_id, role, status, join_source) VALUES (?, ?, ?, ?, ?)`, familyID, userID, role, status, joinSource, ).Error) } func insertActivationOrder(t *testing.T, db *gorm.DB, orderNo string, userID, subscribeID int64, status uint8) { t.Helper() require.NoError(t, db.Exec( `INSERT INTO "order" (user_id, order_no, type, status, subscribe_id, quantity, created_at, updated_at) VALUES (?, ?, 1, ?, ?, 1, datetime('now'), datetime('now'))`, userID, orderNo, status, subscribeID, ).Error) } func TestValidateNewUserOnlyEligibilityAtActivation_UsesEarliestBoundDeviceTime(t *testing.T) { db := setupActivationEligibilityDB(t) const ( ownerUserID = int64(1) memberUserID = int64(2) familyID = int64(10) subscribeID = int64(100) ) insertActivationUser(t, db, ownerUserID, time.Now().Add(-1*time.Hour)) insertActivationUser(t, db, memberUserID, time.Now().Add(-72*time.Hour)) insertActivationDevice(t, db, memberUserID, "activation-old-device", time.Now().Add(-72*time.Hour)) insertActivationFamily(t, db, familyID, ownerUserID) insertActivationFamilyMember(t, db, familyID, ownerUserID, user.FamilyRoleOwner, user.FamilyMemberActive, "owner_init") insertActivationFamilyMember(t, db, familyID, memberUserID, user.FamilyRoleMember, user.FamilyMemberActive, "bind_email_with_verification") err := validateNewUserOnlyEligibilityAtActivation( context.Background(), db, &modelOrder.Order{ UserId: ownerUserID, OrderNo: "activation-check-old-device", Type: OrderTypeSubscribe, Quantity: 1, SubscribeId: subscribeID, }, &subscribe.Subscribe{ Id: subscribeID, Discount: `[{"quantity":1,"discount":90,"new_user_only":true}]`, }, ) require.Error(t, err) require.Contains(t, err.Error(), "is not a new user") } func TestValidateNewUserOnlyEligibilityAtActivation_SharesHistoryAcrossBoundScope(t *testing.T) { db := setupActivationEligibilityDB(t) const ( ownerUserID = int64(11) memberUserID = int64(12) familyID = int64(20) subscribeID = int64(200) ) insertActivationUser(t, db, ownerUserID, time.Now().Add(-1*time.Hour)) insertActivationUser(t, db, memberUserID, time.Now().Add(-2*time.Hour)) insertActivationDevice(t, db, memberUserID, "activation-shared-device", time.Now().Add(-2*time.Hour)) insertActivationFamily(t, db, familyID, ownerUserID) insertActivationFamilyMember(t, db, familyID, ownerUserID, user.FamilyRoleOwner, user.FamilyMemberActive, "owner_init") insertActivationFamilyMember(t, db, familyID, memberUserID, user.FamilyRoleMember, user.FamilyMemberActive, "bind_email_with_verification") insertActivationOrder(t, db, "previous-finished-order", memberUserID, subscribeID, OrderStatusFinished) err := validateNewUserOnlyEligibilityAtActivation( context.Background(), db, &modelOrder.Order{ UserId: ownerUserID, OrderNo: "current-paid-order", Type: OrderTypeSubscribe, Quantity: 1, SubscribeId: subscribeID, }, &subscribe.Subscribe{ Id: subscribeID, Discount: `[{"quantity":1,"discount":90,"new_user_only":true}]`, }, ) require.Error(t, err) require.Contains(t, err.Error(), "already activated") }