You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

684 lines
23 KiB

  1. package e2e
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "time"
  8. "github.com/QuantumNous/new-api/common"
  9. "github.com/QuantumNous/new-api/model"
  10. helpers "github.com/QuantumNous/new-api/test/e2e/helpers"
  11. "github.com/stretchr/testify/require"
  12. )
  13. var (
  14. repoRoot string
  15. artifactRoot string
  16. binaryPath string
  17. )
  18. type simpleResponse struct {
  19. Success bool `json:"success"`
  20. Message string `json:"message,omitempty"`
  21. Error string `json:"error,omitempty"`
  22. }
  23. type createBatchResponse struct {
  24. Success bool `json:"success"`
  25. Data model.UserMigrationBatch `json:"data"`
  26. }
  27. type adminUserResponse struct {
  28. Success bool `json:"success"`
  29. Data model.User `json:"data"`
  30. }
  31. type batchPageResponse struct {
  32. Success bool `json:"success"`
  33. Data model.UserMigrationBatch `json:"data"`
  34. Items []model.UserMigrationItem `json:"items"`
  35. Total int64 `json:"total"`
  36. }
  37. type preparedBatch struct {
  38. BatchID int
  39. Detail *helpers.BatchDetail
  40. Items map[int]*model.UserMigrationItem
  41. }
  42. type executedBatch struct {
  43. Prepared *preparedBatch
  44. Detail *helpers.BatchDetail
  45. CreatedUser *model.User
  46. SoftAffUser *model.User
  47. VerifyResult map[int]helpers.VerifyEntry
  48. }
  49. func TestMain(m *testing.M) {
  50. var err error
  51. wd, err := os.Getwd()
  52. if err != nil {
  53. fmt.Fprintln(os.Stderr, err)
  54. os.Exit(1)
  55. }
  56. repoRoot = filepath.Clean(filepath.Join(wd, "..", ".."))
  57. artifactRoot = filepath.Join(repoRoot, "test-artifacts", "user-migration-e2e")
  58. if err := os.MkdirAll(artifactRoot, 0o755); err != nil {
  59. fmt.Fprintln(os.Stderr, err)
  60. os.Exit(1)
  61. }
  62. binaryPath, err = helpers.BuildBinary(repoRoot, artifactRoot)
  63. if err != nil {
  64. fmt.Fprintln(os.Stderr, err)
  65. os.Exit(1)
  66. }
  67. os.Exit(m.Run())
  68. }
  69. func TestUserMigrationE2E_FullFlow_GrayScenarios(t *testing.T) {
  70. env := newEnvironment(t)
  71. result := runFullFlow(t, env)
  72. require.Equal(t, model.UserMigrationBatchStatusCompleted, result.Detail.Batch.Status)
  73. require.Equal(t, 3, result.Detail.Batch.MigratedUsers)
  74. require.Equal(t, 0, result.Detail.Batch.FailedUsers)
  75. require.Len(t, result.VerifyResult, 3)
  76. }
  77. func TestUserMigrationE2E_IdempotentRetry(t *testing.T) {
  78. env := newEnvironment(t)
  79. result := runFullFlow(t, env)
  80. forceRetryableState(t, env.CNDBPath, result.Prepared.BatchID, []int{
  81. result.Prepared.Items[helpers.OVCreateUserID].Id,
  82. result.Prepared.Items[helpers.OVMergeUserID].Id,
  83. result.Prepared.Items[helpers.OVSoftAffUserID].Id,
  84. })
  85. var retryResp simpleResponse
  86. require.NoError(t, env.CNClient.PostJSON(
  87. fmt.Sprintf("/api/user-migrations/batches/%d/retry", result.Prepared.BatchID),
  88. nil,
  89. 202,
  90. &retryResp,
  91. ))
  92. require.True(t, retryResp.Success)
  93. retried, err := helpers.WaitBatchStatus(env.CNClient, result.Prepared.BatchID, model.UserMigrationBatchStatusCompleted, 25*time.Second)
  94. require.NoError(t, err)
  95. retriedItems := indexItemsBySourceUserID(retried.Items)
  96. require.Equal(t, model.UserMigrationItemStatusMigrated, retriedItems[helpers.OVCreateUserID].Status)
  97. require.Equal(t, model.UserMigrationItemStatusMigrated, retriedItems[helpers.OVMergeUserID].Status)
  98. require.Equal(t, model.UserMigrationItemStatusMigrated, retriedItems[helpers.OVSoftAffUserID].Status)
  99. require.EqualValues(t, 1, helpers.CountUsersByUsername(t, env.CNDBPath, helpers.UsernameOVCreate))
  100. require.EqualValues(t, 1, helpers.CountUsersByUsername(t, env.CNDBPath, helpers.UsernameOVSoftAff))
  101. helpers.AssertQuota(t, env.CNDBPath, helpers.CNMergeTargetUserID, 700)
  102. helpers.AssertNoDuplicateQuotaGrant(t, env.CNDBPath, result.Prepared.Items[helpers.OVMergeUserID].MigrationToken)
  103. verifyEntries, err := helpers.VerifyBatch(env.CNClient, result.Prepared.BatchID)
  104. require.NoError(t, err)
  105. require.Len(t, verifyEntries, 3)
  106. for _, entry := range verifyEntries {
  107. require.Empty(t, entry.Error)
  108. require.NotNil(t, entry.Result)
  109. require.True(t, entry.Result.TargetUserExists)
  110. require.True(t, entry.Result.RemoteConverted)
  111. require.True(t, entry.Result.QuotaMatched)
  112. }
  113. }
  114. func TestUserMigrationE2E_ExecutionTimeDriftFails(t *testing.T) {
  115. env := newEnvironment(t)
  116. prepared := prepareGrayScenarioBatch(t, env)
  117. renameOVUser(t, env, helpers.OVCreateUserID, helpers.UsernameCNMergeTarget)
  118. var executeResp simpleResponse
  119. require.NoError(t, env.CNClient.PostJSON(
  120. fmt.Sprintf("/api/user-migrations/batches/%d/execute", prepared.BatchID),
  121. nil,
  122. 202,
  123. &executeResp,
  124. ))
  125. require.True(t, executeResp.Success)
  126. failedDetail, err := helpers.WaitBatchStatus(env.CNClient, prepared.BatchID, model.UserMigrationBatchStatusFailed, 25*time.Second)
  127. require.NoError(t, err)
  128. items := indexItemsBySourceUserID(failedDetail.Items)
  129. require.Equal(t, model.UserMigrationItemStatusFailed, items[helpers.OVCreateUserID].Status)
  130. require.Contains(t, items[helpers.OVCreateUserID].ErrorMessage, "drift")
  131. require.Equal(t, model.UserMigrationItemStatusMigrated, items[helpers.OVMergeUserID].Status)
  132. require.Equal(t, model.UserMigrationItemStatusMigrated, items[helpers.OVSoftAffUserID].Status)
  133. require.Equal(t, model.UserMigrationItemStatusSkipped, items[helpers.OVHardSyncedUserID].Status)
  134. require.Equal(t, model.UserMigrationItemStatusSkipped, items[helpers.OVUsernameConflictUserID].Status)
  135. require.EqualValues(t, 0, helpers.CountUsersByEmail(t, env.CNDBPath, helpers.EmailOVCreate))
  136. driftedRemote, err := helpers.GetRemoteUserSnapshot(env.Cluster.OV.BaseURL, helpers.OVCreateUserID)
  137. require.NoError(t, err)
  138. require.NotEqual(t, common.UserSourceSynced, driftedRemote.Source)
  139. }
  140. func TestUserMigrationE2E_RescanPreservesHumanDecision(t *testing.T) {
  141. env := newEnvironment(t)
  142. prepared := prepareGrayScenarioBatch(t, env)
  143. var scanResp simpleResponse
  144. require.NoError(t, env.CNClient.PostJSON(
  145. fmt.Sprintf("/api/user-migrations/batches/%d/scan", prepared.BatchID),
  146. nil,
  147. 202,
  148. &scanResp,
  149. ))
  150. require.True(t, scanResp.Success)
  151. rescannedDetail, err := helpers.WaitBatchStatus(env.CNClient, prepared.BatchID, model.UserMigrationBatchStatusScanned, 20*time.Second)
  152. require.NoError(t, err)
  153. items := indexItemsBySourceUserID(rescannedDetail.Items)
  154. require.Equal(t, model.UserMigrationStrategySkip, items[helpers.OVHardSyncedUserID].ResolutionStrategy)
  155. require.Equal(t, model.UserMigrationItemStatusConflict, items[helpers.OVHardSyncedUserID].Status)
  156. require.Equal(t, model.UserMigrationStrategySkip, items[helpers.OVUsernameConflictUserID].ResolutionStrategy)
  157. require.Equal(t, model.UserMigrationItemStatusConflict, items[helpers.OVUsernameConflictUserID].Status)
  158. require.Equal(t, model.UserMigrationStrategyMergeExisting, items[helpers.OVMergeUserID].ResolutionStrategy)
  159. require.Equal(t, helpers.CNMergeTargetUserID, items[helpers.OVMergeUserID].TargetUserId)
  160. }
  161. func TestUserMigrationE2E_ScanPagination(t *testing.T) {
  162. env := newEnvironment(t)
  163. createManagedUsers(t, env.OVClient, "ov-page", 97, common.RoleCommonUser)
  164. batchID := createBatch(t, env, "pagination")
  165. var scanResp simpleResponse
  166. require.NoError(t, env.CNClient.PostJSON(
  167. fmt.Sprintf("/api/user-migrations/batches/%d/scan", batchID),
  168. nil,
  169. 202,
  170. &scanResp,
  171. ))
  172. require.True(t, scanResp.Success)
  173. detail, err := helpers.WaitBatchStatus(env.CNClient, batchID, model.UserMigrationBatchStatusScanned, 60*time.Second)
  174. require.NoError(t, err)
  175. require.EqualValues(t, 102, detail.Total)
  176. require.Equal(t, 102, detail.Batch.TotalUsers)
  177. page0 := getBatchPage(t, env.CNClient, batchID, 0, 100)
  178. require.Len(t, page0.Items, 100)
  179. page1 := getBatchPage(t, env.CNClient, batchID, 1, 100)
  180. require.Len(t, page1.Items, 2)
  181. require.EqualValues(t, 102, page1.Total)
  182. }
  183. func TestUserMigrationE2E_RootPermission(t *testing.T) {
  184. env := newEnvironment(t)
  185. adminClient := createAndLoginManagedUser(t, env.Cluster.CN.BaseURL, env.CNClient, "migration-admin", helpers.DefaultRootPassword, common.RoleAdminUser)
  186. var listResp simpleResponse
  187. require.NoError(t, adminClient.GetJSON("/api/user-migrations/batches", 200, &listResp))
  188. require.False(t, listResp.Success)
  189. require.NotEmpty(t, listResp.Message)
  190. var createResp simpleResponse
  191. require.NoError(t, adminClient.PostJSON("/api/user-migrations/batches", map[string]any{
  192. "name": "no-access",
  193. "source_region": "ov",
  194. "target_region": "cn",
  195. }, 200, &createResp))
  196. require.False(t, createResp.Success)
  197. require.NotEmpty(t, createResp.Message)
  198. }
  199. func TestUserMigrationE2E_ResolveInvalidPayload(t *testing.T) {
  200. env := newEnvironment(t)
  201. scanned := scanGrayScenarioBatch(t, env, "resolve-invalid")
  202. conflictItem := scanned.Items[helpers.OVHardSyncedUserID]
  203. var invalidStrategyResp simpleResponse
  204. require.NoError(t, env.CNClient.PostJSON(
  205. fmt.Sprintf("/api/user-migrations/items/%d/resolve", conflictItem.Id),
  206. map[string]any{
  207. "resolution_strategy": "invalid",
  208. },
  209. 400,
  210. &invalidStrategyResp,
  211. ))
  212. require.False(t, invalidStrategyResp.Success)
  213. require.Contains(t, invalidStrategyResp.Error, "invalid resolution_strategy")
  214. var mergeWithoutTargetResp simpleResponse
  215. require.NoError(t, env.CNClient.PostJSON(
  216. fmt.Sprintf("/api/user-migrations/items/%d/resolve", conflictItem.Id),
  217. map[string]any{
  218. "resolution_strategy": model.UserMigrationStrategyMergeExisting,
  219. },
  220. 400,
  221. &mergeWithoutTargetResp,
  222. ))
  223. require.False(t, mergeWithoutTargetResp.Success)
  224. require.Contains(t, mergeWithoutTargetResp.Error, "target_user_id")
  225. }
  226. func TestUserMigrationE2E_ScanConflict_WhenAlreadyScanning(t *testing.T) {
  227. env := newEnvironment(t)
  228. batchID := createBatch(t, env, "scan-conflict")
  229. setBatchStatus(t, env.CNDBPath, batchID, model.UserMigrationBatchStatusScanning)
  230. var resp simpleResponse
  231. require.NoError(t, env.CNClient.PostJSON(
  232. fmt.Sprintf("/api/user-migrations/batches/%d/scan", batchID),
  233. nil,
  234. 409,
  235. &resp,
  236. ))
  237. require.False(t, resp.Success)
  238. require.Contains(t, resp.Error, "already being scanned")
  239. }
  240. func TestUserMigrationE2E_ExecuteConflict_WhenAlreadyRunning(t *testing.T) {
  241. env := newEnvironment(t)
  242. prepared := prepareGrayScenarioBatch(t, env)
  243. setBatchStatus(t, env.CNDBPath, prepared.BatchID, model.UserMigrationBatchStatusRunning)
  244. var resp simpleResponse
  245. require.NoError(t, env.CNClient.PostJSON(
  246. fmt.Sprintf("/api/user-migrations/batches/%d/execute", prepared.BatchID),
  247. nil,
  248. 409,
  249. &resp,
  250. ))
  251. require.False(t, resp.Success)
  252. require.Contains(t, resp.Error, "already running")
  253. }
  254. func TestUserMigrationE2E_VerifyQuotaMismatchDetected(t *testing.T) {
  255. env := newEnvironment(t)
  256. result := runFullFlow(t, env)
  257. convertRemoteUserToSynced(t, env.Cluster.OV.BaseURL, helpers.OVMergeUserID, helpers.CNMergeTargetUserID, 199)
  258. verifyEntries, err := helpers.VerifyBatch(env.CNClient, result.Prepared.BatchID)
  259. require.NoError(t, err)
  260. verifyBySource := make(map[int]helpers.VerifyEntry, len(verifyEntries))
  261. for _, entry := range verifyEntries {
  262. verifyBySource[entry.SourceUserID] = entry
  263. }
  264. require.NotNil(t, verifyBySource[helpers.OVMergeUserID].Result)
  265. require.False(t, verifyBySource[helpers.OVMergeUserID].Result.QuotaMatched)
  266. require.True(t, verifyBySource[helpers.OVCreateUserID].Result.QuotaMatched)
  267. require.True(t, verifyBySource[helpers.OVSoftAffUserID].Result.QuotaMatched)
  268. }
  269. func TestUserMigrationE2E_SkipNeverConvertsRemote(t *testing.T) {
  270. env := newEnvironment(t)
  271. prepared := prepareGrayScenarioBatch(t, env)
  272. resolveItem(t, env, prepared.Items[helpers.OVCreateUserID].Id, model.UserMigrationStrategySkip, 0)
  273. readyDetail, err := helpers.WaitBatchStatus(env.CNClient, prepared.BatchID, model.UserMigrationBatchStatusReady, 10*time.Second)
  274. require.NoError(t, err)
  275. var executeResp simpleResponse
  276. require.NoError(t, env.CNClient.PostJSON(
  277. fmt.Sprintf("/api/user-migrations/batches/%d/execute", prepared.BatchID),
  278. nil,
  279. 202,
  280. &executeResp,
  281. ))
  282. require.True(t, executeResp.Success)
  283. completedDetail, err := helpers.WaitBatchStatus(env.CNClient, prepared.BatchID, model.UserMigrationBatchStatusCompleted, 25*time.Second)
  284. require.NoError(t, err)
  285. items := indexItemsBySourceUserID(completedDetail.Items)
  286. require.Equal(t, model.UserMigrationItemStatusSkipped, items[helpers.OVCreateUserID].Status)
  287. require.EqualValues(t, 0, helpers.CountUsersByEmail(t, env.CNDBPath, helpers.EmailOVCreate))
  288. remoteUser, err := helpers.GetRemoteUserSnapshot(env.Cluster.OV.BaseURL, helpers.OVCreateUserID)
  289. require.NoError(t, err)
  290. require.NotEqual(t, common.UserSourceSynced, remoteUser.Source)
  291. require.Equal(t, readyDetail.Items[0].BatchId, items[helpers.OVCreateUserID].BatchId)
  292. }
  293. func newEnvironment(t *testing.T) *helpers.Environment {
  294. t.Helper()
  295. env, err := helpers.PrepareEnvironment(repoRoot, artifactRoot, binaryPath, t.Name())
  296. require.NoError(t, err)
  297. t.Cleanup(func() {
  298. require.NoError(t, env.Close())
  299. })
  300. return env
  301. }
  302. func runFullFlow(t *testing.T, env *helpers.Environment) *executedBatch {
  303. t.Helper()
  304. prepared := prepareGrayScenarioBatch(t, env)
  305. var executeResp simpleResponse
  306. require.NoError(t, env.CNClient.PostJSON(
  307. fmt.Sprintf("/api/user-migrations/batches/%d/execute", prepared.BatchID),
  308. nil,
  309. 202,
  310. &executeResp,
  311. ))
  312. require.True(t, executeResp.Success)
  313. completedDetail, err := helpers.WaitBatchStatus(env.CNClient, prepared.BatchID, model.UserMigrationBatchStatusCompleted, 25*time.Second)
  314. require.NoError(t, err)
  315. require.EqualValues(t, 5, completedDetail.Total)
  316. items := indexItemsBySourceUserID(completedDetail.Items)
  317. require.Equal(t, model.UserMigrationItemStatusMigrated, items[helpers.OVCreateUserID].Status)
  318. require.Equal(t, model.UserMigrationItemStatusMigrated, items[helpers.OVMergeUserID].Status)
  319. require.Equal(t, model.UserMigrationItemStatusMigrated, items[helpers.OVSoftAffUserID].Status)
  320. require.Equal(t, model.UserMigrationItemStatusSkipped, items[helpers.OVHardSyncedUserID].Status)
  321. require.Equal(t, model.UserMigrationItemStatusSkipped, items[helpers.OVUsernameConflictUserID].Status)
  322. createdUser := helpers.AssertCNUserExists(t, env.CNDBPath, helpers.UsernameOVCreate)
  323. require.Equal(t, 100, createdUser.Quota)
  324. helpers.AssertQuota(t, env.CNDBPath, helpers.CNMergeTargetUserID, 700)
  325. softAffUser := helpers.AssertCNUserExists(t, env.CNDBPath, helpers.UsernameOVSoftAff)
  326. require.Equal(t, 150, softAffUser.Quota)
  327. require.NotEqual(t, helpers.AffCodeCNSoftAffOwner, softAffUser.AffCode)
  328. helpers.AssertOVUserSynced(t, env.Cluster.OV.BaseURL, helpers.OVCreateUserID, createdUser.Id, 100)
  329. helpers.AssertOVUserSynced(t, env.Cluster.OV.BaseURL, helpers.OVMergeUserID, helpers.CNMergeTargetUserID, 700)
  330. helpers.AssertOVUserSynced(t, env.Cluster.OV.BaseURL, helpers.OVSoftAffUserID, softAffUser.Id, 150)
  331. hardSnapshot, err := helpers.GetRemoteUserSnapshot(env.Cluster.OV.BaseURL, helpers.OVHardSyncedUserID)
  332. require.NoError(t, err)
  333. require.NotEqual(t, common.UserSourceSynced, hardSnapshot.Source)
  334. usernameSnapshot, err := helpers.GetRemoteUserSnapshot(env.Cluster.OV.BaseURL, helpers.OVUsernameConflictUserID)
  335. require.NoError(t, err)
  336. require.NotEqual(t, common.UserSourceSynced, usernameSnapshot.Source)
  337. verifyEntries, err := helpers.VerifyBatch(env.CNClient, prepared.BatchID)
  338. require.NoError(t, err)
  339. require.Len(t, verifyEntries, 3)
  340. verifyBySource := make(map[int]helpers.VerifyEntry, len(verifyEntries))
  341. for _, entry := range verifyEntries {
  342. require.Empty(t, entry.Error)
  343. require.NotNil(t, entry.Result)
  344. require.True(t, entry.Result.TargetUserExists)
  345. require.True(t, entry.Result.RemoteConverted)
  346. require.True(t, entry.Result.QuotaMatched)
  347. verifyBySource[entry.SourceUserID] = entry
  348. }
  349. return &executedBatch{
  350. Prepared: prepared,
  351. Detail: completedDetail,
  352. CreatedUser: createdUser,
  353. SoftAffUser: softAffUser,
  354. VerifyResult: verifyBySource,
  355. }
  356. }
  357. func prepareGrayScenarioBatch(t *testing.T, env *helpers.Environment) *preparedBatch {
  358. t.Helper()
  359. scanned := scanGrayScenarioBatch(t, env, "gray-scenarios")
  360. scannedItems := scanned.Items
  361. require.Equal(t, model.UserMigrationStrategyCreateNew, scannedItems[helpers.OVCreateUserID].ResolutionStrategy)
  362. require.Equal(t, model.UserMigrationItemStatusReady, scannedItems[helpers.OVCreateUserID].Status)
  363. require.Equal(t, model.UserMigrationStrategyMergeExisting, scannedItems[helpers.OVMergeUserID].ResolutionStrategy)
  364. require.Equal(t, model.UserMigrationItemStatusReady, scannedItems[helpers.OVMergeUserID].Status)
  365. require.Equal(t, helpers.CNMergeTargetUserID, scannedItems[helpers.OVMergeUserID].TargetUserId)
  366. require.Equal(t, model.UserMigrationItemStatusConflict, scannedItems[helpers.OVHardSyncedUserID].Status)
  367. require.Contains(t, decodeFlags(t, scannedItems[helpers.OVHardSyncedUserID].ConflictFlags), "cn_already_synced_to_ov")
  368. require.Equal(t, model.UserMigrationItemStatusConflict, scannedItems[helpers.OVUsernameConflictUserID].Status)
  369. require.Contains(t, decodeFlags(t, scannedItems[helpers.OVUsernameConflictUserID].ConflictFlags), "username")
  370. require.Equal(t, model.UserMigrationStrategyCreateNew, scannedItems[helpers.OVSoftAffUserID].ResolutionStrategy)
  371. require.Equal(t, model.UserMigrationItemStatusReady, scannedItems[helpers.OVSoftAffUserID].Status)
  372. require.Contains(t, decodeFlags(t, scannedItems[helpers.OVSoftAffUserID].ConflictFlags), "aff_code")
  373. resolveItem(t, env, scannedItems[helpers.OVHardSyncedUserID].Id, model.UserMigrationStrategySkip, 0)
  374. resolveItem(t, env, scannedItems[helpers.OVUsernameConflictUserID].Id, model.UserMigrationStrategySkip, 0)
  375. readyDetail, err := helpers.WaitBatchStatus(env.CNClient, scanned.BatchID, model.UserMigrationBatchStatusReady, 10*time.Second)
  376. require.NoError(t, err)
  377. readyItems := indexItemsBySourceUserID(readyDetail.Items)
  378. require.Equal(t, model.UserMigrationStrategySkip, readyItems[helpers.OVHardSyncedUserID].ResolutionStrategy)
  379. require.Equal(t, model.UserMigrationItemStatusReady, readyItems[helpers.OVHardSyncedUserID].Status)
  380. require.Equal(t, model.UserMigrationStrategySkip, readyItems[helpers.OVUsernameConflictUserID].ResolutionStrategy)
  381. require.Equal(t, model.UserMigrationItemStatusReady, readyItems[helpers.OVUsernameConflictUserID].Status)
  382. return &preparedBatch{
  383. BatchID: scanned.BatchID,
  384. Detail: readyDetail,
  385. Items: readyItems,
  386. }
  387. }
  388. func scanGrayScenarioBatch(t *testing.T, env *helpers.Environment, name string) *preparedBatch {
  389. t.Helper()
  390. batchID := createBatch(t, env, name)
  391. var scanResp simpleResponse
  392. require.NoError(t, env.CNClient.PostJSON(
  393. fmt.Sprintf("/api/user-migrations/batches/%d/scan", batchID),
  394. nil,
  395. 202,
  396. &scanResp,
  397. ))
  398. require.True(t, scanResp.Success)
  399. scannedDetail, err := helpers.WaitBatchStatus(env.CNClient, batchID, model.UserMigrationBatchStatusScanned, 20*time.Second)
  400. require.NoError(t, err)
  401. require.EqualValues(t, 5, scannedDetail.Total)
  402. scannedItems := indexItemsBySourceUserID(scannedDetail.Items)
  403. require.Len(t, scannedItems, 5)
  404. return &preparedBatch{
  405. BatchID: batchID,
  406. Detail: scannedDetail,
  407. Items: scannedItems,
  408. }
  409. }
  410. func createBatch(t *testing.T, env *helpers.Environment, name string) int {
  411. t.Helper()
  412. var resp createBatchResponse
  413. require.NoError(t, env.CNClient.PostJSON("/api/user-migrations/batches", map[string]any{
  414. "name": name,
  415. "source_region": "ov",
  416. "target_region": "cn",
  417. }, 200, &resp))
  418. require.True(t, resp.Success)
  419. require.NotZero(t, resp.Data.Id)
  420. return resp.Data.Id
  421. }
  422. func resolveItem(t *testing.T, env *helpers.Environment, itemID int, strategy string, targetUserID int) {
  423. t.Helper()
  424. var resp simpleResponse
  425. require.NoError(t, env.CNClient.PostJSON(
  426. fmt.Sprintf("/api/user-migrations/items/%d/resolve", itemID),
  427. map[string]any{
  428. "resolution_strategy": strategy,
  429. "target_user_id": targetUserID,
  430. },
  431. 200,
  432. &resp,
  433. ))
  434. require.True(t, resp.Success)
  435. }
  436. func renameOVUser(t *testing.T, env *helpers.Environment, userID int, newUsername string) {
  437. t.Helper()
  438. var getResp adminUserResponse
  439. require.NoError(t, env.OVClient.GetJSON(fmt.Sprintf("/api/user/%d", userID), 200, &getResp))
  440. require.True(t, getResp.Success)
  441. var updateResp simpleResponse
  442. require.NoError(t, env.OVClient.PutJSON("/api/user", map[string]any{
  443. "id": getResp.Data.Id,
  444. "username": newUsername,
  445. "display_name": getResp.Data.DisplayName,
  446. "group": getResp.Data.Group,
  447. "quota": getResp.Data.Quota,
  448. "remark": getResp.Data.Remark,
  449. "capture_relay": getResp.Data.CaptureRelay,
  450. }, 200, &updateResp))
  451. require.True(t, updateResp.Success)
  452. }
  453. func createAndLoginManagedUser(t *testing.T, baseURL string, rootClient *helpers.APIClient, username, password string, role int) *helpers.APIClient {
  454. t.Helper()
  455. var resp simpleResponse
  456. require.NoError(t, rootClient.PostJSON("/api/user/", map[string]any{
  457. "username": username,
  458. "password": password,
  459. "display_name": username,
  460. "role": role,
  461. }, 200, &resp))
  462. require.True(t, resp.Success)
  463. client, err := helpers.NewAPIClient(baseURL)
  464. require.NoError(t, err)
  465. require.NoError(t, helpers.Login(client, username, password))
  466. return client
  467. }
  468. func createManagedUsers(t *testing.T, client *helpers.APIClient, prefix string, count int, role int) {
  469. t.Helper()
  470. for i := 0; i < count; i++ {
  471. username := prefix
  472. email := fmt.Sprintf("%s@example.com", prefix)
  473. displayName := prefix
  474. if count > 1 {
  475. username = fmt.Sprintf("%s-%03d", prefix, i)
  476. email = fmt.Sprintf("%s-%03d@example.com", prefix, i)
  477. displayName = fmt.Sprintf("%s %03d", prefix, i)
  478. }
  479. var resp simpleResponse
  480. require.NoError(t, client.PostJSON("/api/user/", map[string]any{
  481. "username": username,
  482. "password": helpers.DefaultRootPassword,
  483. "display_name": displayName,
  484. "email": email,
  485. "role": role,
  486. }, 200, &resp))
  487. require.True(t, resp.Success)
  488. }
  489. }
  490. func forceRetryableState(t *testing.T, dbPath string, batchID int, itemIDs []int) {
  491. t.Helper()
  492. db, err := helpers.OpenSQLite(dbPath)
  493. require.NoError(t, err)
  494. defer func() {
  495. require.NoError(t, helpers.CloseSQLite(db))
  496. }()
  497. require.NoError(t, db.Model(&model.UserMigrationBatch{}).Where("id = ?", batchID).
  498. Update("status", model.UserMigrationBatchStatusFailed).Error)
  499. require.NoError(t, db.Model(&model.UserMigrationItem{}).Where("id IN ?", itemIDs).
  500. Updates(map[string]any{
  501. "status": model.UserMigrationItemStatusFailed,
  502. "error_message": "forced retry for e2e",
  503. }).Error)
  504. }
  505. func setBatchStatus(t *testing.T, dbPath string, batchID int, status string) {
  506. t.Helper()
  507. db, err := helpers.OpenSQLite(dbPath)
  508. require.NoError(t, err)
  509. defer func() {
  510. require.NoError(t, helpers.CloseSQLite(db))
  511. }()
  512. require.NoError(t, db.Model(&model.UserMigrationBatch{}).Where("id = ?", batchID).Update("status", status).Error)
  513. }
  514. func getBatchPage(t *testing.T, client *helpers.APIClient, batchID, page, pageSize int) *batchPageResponse {
  515. t.Helper()
  516. var resp batchPageResponse
  517. require.NoError(t, client.GetJSON(
  518. fmt.Sprintf("/api/user-migrations/batches/%d?page=%d&page_size=%d", batchID, page, pageSize),
  519. 200,
  520. &resp,
  521. ))
  522. require.True(t, resp.Success)
  523. return &resp
  524. }
  525. func convertRemoteUserToSynced(t *testing.T, ovBaseURL string, sourceUserID, remoteUserID, syncedQuota int) {
  526. t.Helper()
  527. client, err := helpers.NewAPIClient(ovBaseURL)
  528. require.NoError(t, err)
  529. var resp simpleResponse
  530. require.NoError(t, client.DoJSONWithHeaders(
  531. "POST",
  532. fmt.Sprintf("/api/internal/migration/users/%d/convert-to-synced", sourceUserID),
  533. map[string]any{
  534. "remote_user_id": remoteUserID,
  535. "synced_quota": syncedQuota,
  536. },
  537. 200,
  538. map[string]string{
  539. "X-Sync-API-Key": helpers.DefaultSyncAPIKey,
  540. "X-Sync-Node": "e2e-test",
  541. },
  542. &resp,
  543. ))
  544. require.True(t, resp.Success)
  545. }
  546. func indexItemsBySourceUserID(items []model.UserMigrationItem) map[int]*model.UserMigrationItem {
  547. indexed := make(map[int]*model.UserMigrationItem, len(items))
  548. for i := range items {
  549. indexed[items[i].SourceUserId] = &items[i]
  550. }
  551. return indexed
  552. }
  553. func decodeFlags(t *testing.T, flagsJSON string) []string {
  554. t.Helper()
  555. var flags []string
  556. require.NoError(t, common.UnmarshalJsonStr(flagsJSON, &flags))
  557. return flags
  558. }