Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

159 řádky
4.6 KiB

  1. import type { APIRequestContext } from '@playwright/test'
  2. import { adminCredentials } from './helpers'
  3. export interface ContentAssetRecord {
  4. id: string
  5. versionId: string
  6. code: string
  7. name: string
  8. type: string
  9. storageUri: string
  10. mimeType: string
  11. sizeBytes: number
  12. sha256: string
  13. metadata: Record<string, unknown>
  14. status: string
  15. sortOrder: number
  16. }
  17. export interface ContentDependencyRecord {
  18. id: string
  19. targetProjectId: string
  20. targetVersionId: string
  21. relationType: string
  22. required: boolean
  23. sortOrder: number
  24. }
  25. export interface ContentDetailRecord {
  26. project: {
  27. id: string
  28. type: string
  29. code: string
  30. name: string
  31. status: string
  32. currentVersionId: string
  33. publishedVersionId: string | null
  34. version: number
  35. }
  36. currentVersion: {
  37. id: string
  38. projectId: string
  39. versionNo: number
  40. versionCode: string
  41. status: string
  42. content: Record<string, unknown>
  43. dependencies: ContentDependencyRecord[]
  44. assets: ContentAssetRecord[]
  45. }
  46. }
  47. interface ApiEnvelope<T> {
  48. data?: T
  49. }
  50. export interface ContentApiSession {
  51. headers: Record<string, string>
  52. }
  53. export type CleanupOutcome = 'removed' | 'not-found' | 'failed' | 'not-created'
  54. export interface CleanupRecord {
  55. kind: '模型工程' | '场景工程'
  56. id: string | null
  57. code: string
  58. outcome: CleanupOutcome
  59. statusCode?: number
  60. message?: string
  61. }
  62. const endpoint = (projectId: string) => `/api/tran/v1/content/projects/${encodeURIComponent(projectId)}`
  63. const readData = async <T>(response: Awaited<ReturnType<APIRequestContext['get']>>, operation: string): Promise<T> => {
  64. if (!response.ok()) throw new Error(`${operation}失败(HTTP ${response.status()})`)
  65. const body = await response.json() as ApiEnvelope<T>
  66. if (body.data === undefined) throw new Error(`${operation}失败:响应未包含 data`)
  67. return body.data
  68. }
  69. export async function openContentApiSession(request: APIRequestContext): Promise<ContentApiSession> {
  70. const credentials = adminCredentials()
  71. const response = await request.post('/api/auth/v1/auth/login', {
  72. data: {
  73. username: credentials.username,
  74. password: credentials.password,
  75. roleCode: 'admin',
  76. rememberMe: false,
  77. },
  78. })
  79. const body = await readData<{ accessToken?: string }>(response, 'E2E API 登录')
  80. if (!body.accessToken) throw new Error('E2E API 登录失败:响应未返回访问令牌')
  81. return { headers: { Authorization: `Bearer ${body.accessToken}` } }
  82. }
  83. export async function closeContentApiSession(
  84. request: APIRequestContext,
  85. session: ContentApiSession | null,
  86. ): Promise<void> {
  87. if (!session) return
  88. await request.post('/api/auth/v1/auth/logout', { headers: session.headers }).catch(() => undefined)
  89. }
  90. export async function readContentDetail(
  91. request: APIRequestContext,
  92. session: ContentApiSession,
  93. projectId: string,
  94. ): Promise<ContentDetailRecord> {
  95. const response = await request.get(endpoint(projectId), { headers: session.headers })
  96. return readData<ContentDetailRecord>(response, '读取内容工程')
  97. }
  98. export async function removeContentProject(
  99. request: APIRequestContext,
  100. session: ContentApiSession,
  101. kind: CleanupRecord['kind'],
  102. projectId: string | null,
  103. code: string,
  104. ): Promise<CleanupRecord> {
  105. if (!projectId) return { kind, id: null, code, outcome: 'not-created' }
  106. try {
  107. const detailResponse = await request.get(endpoint(projectId), { headers: session.headers })
  108. if (detailResponse.status() === 404) return { kind, id: projectId, code, outcome: 'not-found' }
  109. if (!detailResponse.ok()) {
  110. return {
  111. kind,
  112. id: projectId,
  113. code,
  114. outcome: 'failed',
  115. statusCode: detailResponse.status(),
  116. message: '清理前读取工程失败',
  117. }
  118. }
  119. const detailBody = await detailResponse.json() as ApiEnvelope<ContentDetailRecord>
  120. const projectVersion = detailBody.data?.project.version
  121. if (!Number.isInteger(projectVersion)) {
  122. return { kind, id: projectId, code, outcome: 'failed', message: '清理前未取得工程乐观锁版本' }
  123. }
  124. const deleteResponse = await request.delete(endpoint(projectId), {
  125. headers: session.headers,
  126. params: { version: String(projectVersion) },
  127. })
  128. if (deleteResponse.ok()) return { kind, id: projectId, code, outcome: 'removed' }
  129. if (deleteResponse.status() === 404) return { kind, id: projectId, code, outcome: 'not-found' }
  130. return {
  131. kind,
  132. id: projectId,
  133. code,
  134. outcome: 'failed',
  135. statusCode: deleteResponse.status(),
  136. message: '服务端拒绝删除测试工程',
  137. }
  138. } catch {
  139. return { kind, id: projectId, code, outcome: 'failed', message: '清理 API 调用异常' }
  140. }
  141. }