25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

315 lines
7.6 KiB

  1. package model
  2. import (
  3. "errors"
  4. "testing"
  5. "github.com/QuantumNous/new-api/common"
  6. "github.com/glebarez/sqlite"
  7. "github.com/stretchr/testify/require"
  8. "gorm.io/gorm"
  9. )
  10. func setupRegionMigrationUserDB(t *testing.T) *gorm.DB {
  11. t.Helper()
  12. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  13. require.NoError(t, err)
  14. sqlDB, err := db.DB()
  15. require.NoError(t, err)
  16. sqlDB.SetMaxOpenConns(1)
  17. require.NoError(t, db.AutoMigrate(&User{}, &CustomOAuthProvider{}, &UserOAuthBinding{}))
  18. return db
  19. }
  20. func TestInsertImportedUserWithHashedPassword_PreservesHash(t *testing.T) {
  21. db := setupRegionMigrationUserDB(t)
  22. orig := DB
  23. DB = db
  24. defer func() {
  25. DB = orig
  26. }()
  27. user := &User{
  28. Username: "ov-alice",
  29. Password: "already-hashed-value",
  30. Email: "alice@example.com",
  31. DisplayName: "Alice",
  32. Status: common.UserStatusEnabled,
  33. Role: common.RoleCommonUser,
  34. Group: "default",
  35. Quota: 123,
  36. AffCode: "A001",
  37. CreatedAt: 1710000000,
  38. }
  39. require.NoError(t, InsertImportedUserWithHashedPassword(user))
  40. var saved User
  41. require.NoError(t, db.Where("username = ?", "ov-alice").First(&saved).Error)
  42. require.Equal(t, "already-hashed-value", saved.Password)
  43. require.Greater(t, saved.Id, 0)
  44. }
  45. func TestInsertImportedUserWithHashedPassword_RejectsExplicitId(t *testing.T) {
  46. db := setupRegionMigrationUserDB(t)
  47. orig := DB
  48. DB = db
  49. defer func() {
  50. DB = orig
  51. }()
  52. user := &User{
  53. Id: 10000001,
  54. Username: "bad-import",
  55. Password: "hash",
  56. AffCode: "X001",
  57. }
  58. err := InsertImportedUserWithHashedPassword(user)
  59. require.Error(t, err)
  60. require.Contains(t, err.Error(), "Id must not be set")
  61. }
  62. func TestInsertImportedUserWithHashedPassword_RejectsOverlongFields(t *testing.T) {
  63. db := setupRegionMigrationUserDB(t)
  64. orig := DB
  65. DB = db
  66. defer func() {
  67. DB = orig
  68. }()
  69. user := &User{
  70. Username: "abcdefghijklmnopqrstu",
  71. Password: "already-hashed-value",
  72. Email: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com",
  73. DisplayName: "DDDDDDDDDDDDDDDDDDDDDDDDD",
  74. Status: common.UserStatusEnabled,
  75. Role: common.RoleCommonUser,
  76. Group: "default",
  77. Quota: 123,
  78. AffCode: "A002",
  79. CreatedAt: 1710000000,
  80. }
  81. err := InsertImportedUserWithHashedPassword(user)
  82. require.Error(t, err)
  83. require.Contains(t, err.Error(), "username exceeds max length")
  84. user.Username = "valid-user"
  85. err = InsertImportedUserWithHashedPassword(user)
  86. require.Error(t, err)
  87. require.Contains(t, err.Error(), "display_name exceeds max length")
  88. user.DisplayName = "Valid Name"
  89. err = InsertImportedUserWithHashedPassword(user)
  90. require.Error(t, err)
  91. require.Contains(t, err.Error(), "email exceeds max length")
  92. }
  93. func TestCopyOAuthBindingsIfMissing_Idempotent(t *testing.T) {
  94. db := setupRegionMigrationUserDB(t)
  95. orig := DB
  96. DB = db
  97. defer func() {
  98. DB = orig
  99. }()
  100. require.NoError(t, db.Create(&CustomOAuthProvider{
  101. Id: 10,
  102. Name: "GitHub Enterprise",
  103. Slug: "gh-enterprise",
  104. Enabled: true,
  105. ClientId: "x",
  106. AuthorizationEndpoint: "a",
  107. TokenEndpoint: "t",
  108. UserInfoEndpoint: "u",
  109. }).Error)
  110. require.NoError(t, db.Create(&User{Id: 1, Username: "cn-user", Password: "hash", AffCode: "AFF1"}).Error)
  111. bindings := []*UserOAuthBinding{{
  112. ProviderId: 10,
  113. ProviderUserId: "u-100",
  114. }}
  115. require.NoError(t, CopyOAuthBindingsIfMissing(1, bindings))
  116. require.NoError(t, CopyOAuthBindingsIfMissing(1, bindings))
  117. var count int64
  118. require.NoError(t, db.Model(&UserOAuthBinding{}).Where("user_id = ?", 1).Count(&count).Error)
  119. require.EqualValues(t, 1, count)
  120. }
  121. func TestConvertUserToSynced_Idempotent(t *testing.T) {
  122. db := setupRegionMigrationUserDB(t)
  123. orig := DB
  124. DB = db
  125. defer func() {
  126. DB = orig
  127. }()
  128. require.NoError(t, db.Create(&User{
  129. Id: 10000001,
  130. Username: "ov-user",
  131. Password: "hash",
  132. AffCode: "AFF2",
  133. Quota: 88,
  134. Source: common.UserSourceLocal,
  135. }).Error)
  136. require.NoError(t, ConvertUserToSynced(10000001, 2001, 88))
  137. require.NoError(t, ConvertUserToSynced(10000001, 2001, 88))
  138. var saved User
  139. require.NoError(t, db.First(&saved, 10000001).Error)
  140. require.Equal(t, common.UserSourceSynced, saved.Source)
  141. require.Equal(t, 2001, saved.RemoteUserId)
  142. }
  143. func TestConvertUserToSynced_DifferentRemoteUserIdFails(t *testing.T) {
  144. db := setupRegionMigrationUserDB(t)
  145. orig := DB
  146. DB = db
  147. defer func() {
  148. DB = orig
  149. }()
  150. require.NoError(t, db.Create(&User{
  151. Id: 10000002,
  152. Username: "ov-user2",
  153. Password: "hash",
  154. AffCode: "AFF3",
  155. Source: common.UserSourceLocal,
  156. }).Error)
  157. require.NoError(t, ConvertUserToSynced(10000002, 3001, 100))
  158. err := ConvertUserToSynced(10000002, 9999, 100)
  159. require.Error(t, err)
  160. require.ErrorIs(t, err, ErrSyncedUserRemoteUserIDImmutable)
  161. }
  162. func TestListLocalUsersForMigration_FiltersLocalOnly(t *testing.T) {
  163. db := setupRegionMigrationUserDB(t)
  164. orig := DB
  165. DB = db
  166. defer func() {
  167. DB = orig
  168. }()
  169. require.NoError(t, db.Create(&User{
  170. Id: 10000001,
  171. Username: "local-1",
  172. Password: "h",
  173. AffCode: "L1",
  174. Source: common.UserSourceLocal,
  175. }).Error)
  176. require.NoError(t, db.Create(&User{
  177. Id: 10000002,
  178. Username: "synced-1",
  179. Password: "h",
  180. AffCode: "S1",
  181. Source: common.UserSourceSynced,
  182. RemoteUserId: 1,
  183. }).Error)
  184. require.NoError(t, db.Create(&User{
  185. Id: 10000003,
  186. Username: "root",
  187. Password: "h",
  188. AffCode: "R1",
  189. Source: common.UserSourceLocal,
  190. Role: common.RoleRootUser,
  191. }).Error)
  192. users, total, err := ListLocalUsersForMigration(0, 20)
  193. require.NoError(t, err)
  194. require.EqualValues(t, 1, total)
  195. require.Len(t, users, 1)
  196. require.Equal(t, "local-1", users[0].Username)
  197. }
  198. func TestQueryLocalUsersForMigration_FiltersByKeyword(t *testing.T) {
  199. db := setupRegionMigrationUserDB(t)
  200. orig := DB
  201. DB = db
  202. defer func() {
  203. DB = orig
  204. }()
  205. require.NoError(t, db.Create(&User{
  206. Id: 10000011,
  207. Username: "alice-local",
  208. Password: "h",
  209. Email: "alice@example.com",
  210. AffCode: "AL1",
  211. Source: common.UserSourceLocal,
  212. }).Error)
  213. require.NoError(t, db.Create(&User{
  214. Id: 10000012,
  215. Username: "bob-local",
  216. Password: "h",
  217. Email: "bob@example.com",
  218. AffCode: "BO1",
  219. Source: common.UserSourceLocal,
  220. }).Error)
  221. users, total, err := QueryLocalUsersForMigration(0, 20, "alice")
  222. require.NoError(t, err)
  223. require.EqualValues(t, 1, total)
  224. require.Len(t, users, 1)
  225. require.Equal(t, "alice-local", users[0].Username)
  226. }
  227. func TestConvertUserToSynced_RejectsRootUser(t *testing.T) {
  228. db := setupRegionMigrationUserDB(t)
  229. orig := DB
  230. DB = db
  231. defer func() {
  232. DB = orig
  233. }()
  234. require.NoError(t, db.Create(&User{
  235. Id: 10000010,
  236. Username: "root",
  237. Password: "hash",
  238. AffCode: "ROOT1",
  239. Source: common.UserSourceLocal,
  240. Role: common.RoleRootUser,
  241. }).Error)
  242. err := ConvertUserToSynced(10000010, 2001, 88)
  243. require.Error(t, err)
  244. require.ErrorIs(t, err, ErrConvertRootUserToSynced)
  245. var saved User
  246. require.NoError(t, db.First(&saved, 10000010).Error)
  247. require.Equal(t, common.UserSourceLocal, saved.Source)
  248. require.Equal(t, 0, saved.RemoteUserId)
  249. }
  250. func TestConvertUserToSynced_SentinelErrorsRemainDistinct(t *testing.T) {
  251. db := setupRegionMigrationUserDB(t)
  252. orig := DB
  253. DB = db
  254. defer func() {
  255. DB = orig
  256. }()
  257. require.NoError(t, db.Create(&User{
  258. Id: 10000020,
  259. Username: "already-synced",
  260. Password: "hash",
  261. AffCode: "SYNC20",
  262. Source: common.UserSourceSynced,
  263. RemoteUserId: 3001,
  264. }).Error)
  265. err := ConvertUserToSynced(10000020, 9999, 100)
  266. require.Error(t, err)
  267. require.True(t, errors.Is(err, ErrSyncedUserRemoteUserIDImmutable))
  268. require.False(t, errors.Is(err, ErrConvertRootUserToSynced))
  269. }