package model import ( "errors" "testing" "github.com/QuantumNous/new-api/common" "github.com/glebarez/sqlite" "github.com/stretchr/testify/require" "gorm.io/gorm" ) func setupRegionMigrationUserDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) sqlDB, err := db.DB() require.NoError(t, err) sqlDB.SetMaxOpenConns(1) require.NoError(t, db.AutoMigrate(&User{}, &CustomOAuthProvider{}, &UserOAuthBinding{})) return db } func TestInsertImportedUserWithHashedPassword_PreservesHash(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() user := &User{ Username: "ov-alice", Password: "already-hashed-value", Email: "alice@example.com", DisplayName: "Alice", Status: common.UserStatusEnabled, Role: common.RoleCommonUser, Group: "default", Quota: 123, AffCode: "A001", CreatedAt: 1710000000, } require.NoError(t, InsertImportedUserWithHashedPassword(user)) var saved User require.NoError(t, db.Where("username = ?", "ov-alice").First(&saved).Error) require.Equal(t, "already-hashed-value", saved.Password) require.Greater(t, saved.Id, 0) } func TestInsertImportedUserWithHashedPassword_RejectsExplicitId(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() user := &User{ Id: 10000001, Username: "bad-import", Password: "hash", AffCode: "X001", } err := InsertImportedUserWithHashedPassword(user) require.Error(t, err) require.Contains(t, err.Error(), "Id must not be set") } func TestCopyOAuthBindingsIfMissing_Idempotent(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() require.NoError(t, db.Create(&CustomOAuthProvider{ Id: 10, Name: "GitHub Enterprise", Slug: "gh-enterprise", Enabled: true, ClientId: "x", AuthorizationEndpoint: "a", TokenEndpoint: "t", UserInfoEndpoint: "u", }).Error) require.NoError(t, db.Create(&User{Id: 1, Username: "cn-user", Password: "hash", AffCode: "AFF1"}).Error) bindings := []*UserOAuthBinding{{ ProviderId: 10, ProviderUserId: "u-100", }} require.NoError(t, CopyOAuthBindingsIfMissing(1, bindings)) require.NoError(t, CopyOAuthBindingsIfMissing(1, bindings)) var count int64 require.NoError(t, db.Model(&UserOAuthBinding{}).Where("user_id = ?", 1).Count(&count).Error) require.EqualValues(t, 1, count) } func TestConvertUserToSynced_Idempotent(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() require.NoError(t, db.Create(&User{ Id: 10000001, Username: "ov-user", Password: "hash", AffCode: "AFF2", Quota: 88, Source: common.UserSourceLocal, }).Error) require.NoError(t, ConvertUserToSynced(10000001, 2001, 88)) require.NoError(t, ConvertUserToSynced(10000001, 2001, 88)) var saved User require.NoError(t, db.First(&saved, 10000001).Error) require.Equal(t, common.UserSourceSynced, saved.Source) require.Equal(t, 2001, saved.RemoteUserId) } func TestConvertUserToSynced_DifferentRemoteUserIdFails(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() require.NoError(t, db.Create(&User{ Id: 10000002, Username: "ov-user2", Password: "hash", AffCode: "AFF3", Source: common.UserSourceLocal, }).Error) require.NoError(t, ConvertUserToSynced(10000002, 3001, 100)) err := ConvertUserToSynced(10000002, 9999, 100) require.Error(t, err) require.ErrorIs(t, err, ErrSyncedUserRemoteUserIDImmutable) } func TestListLocalUsersForMigration_FiltersLocalOnly(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() require.NoError(t, db.Create(&User{ Id: 10000001, Username: "local-1", Password: "h", AffCode: "L1", Source: common.UserSourceLocal, }).Error) require.NoError(t, db.Create(&User{ Id: 10000002, Username: "synced-1", Password: "h", AffCode: "S1", Source: common.UserSourceSynced, RemoteUserId: 1, }).Error) require.NoError(t, db.Create(&User{ Id: 10000003, Username: "root", Password: "h", AffCode: "R1", Source: common.UserSourceLocal, Role: common.RoleRootUser, }).Error) users, total, err := ListLocalUsersForMigration(0, 20) require.NoError(t, err) require.EqualValues(t, 1, total) require.Len(t, users, 1) require.Equal(t, "local-1", users[0].Username) } func TestConvertUserToSynced_RejectsRootUser(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() require.NoError(t, db.Create(&User{ Id: 10000010, Username: "root", Password: "hash", AffCode: "ROOT1", Source: common.UserSourceLocal, Role: common.RoleRootUser, }).Error) err := ConvertUserToSynced(10000010, 2001, 88) require.Error(t, err) require.ErrorIs(t, err, ErrConvertRootUserToSynced) var saved User require.NoError(t, db.First(&saved, 10000010).Error) require.Equal(t, common.UserSourceLocal, saved.Source) require.Equal(t, 0, saved.RemoteUserId) } func TestConvertUserToSynced_SentinelErrorsRemainDistinct(t *testing.T) { db := setupRegionMigrationUserDB(t) orig := DB DB = db defer func() { DB = orig }() require.NoError(t, db.Create(&User{ Id: 10000020, Username: "already-synced", Password: "hash", AffCode: "SYNC20", Source: common.UserSourceSynced, RemoteUserId: 3001, }).Error) err := ConvertUserToSynced(10000020, 9999, 100) require.Error(t, err) require.True(t, errors.Is(err, ErrSyncedUserRemoteUserIDImmutable)) require.False(t, errors.Is(err, ErrConvertRootUserToSynced)) }