mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-24 00:55:35 +00:00
235 lines
10 KiB
TypeScript
235 lines
10 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
import { setEnglishLocale } from './helpers/auth-fixtures'
|
|
import { createFreshSession, loginWithCredentials } from './helpers/session'
|
|
import { E2eTestDataBuilder } from './helpers/test-data-builder'
|
|
|
|
function getOptionalEnv(name: string): string | undefined {
|
|
const value = process.env[name]?.trim()
|
|
return value ? value : undefined
|
|
}
|
|
|
|
function adminCredentials() {
|
|
return {
|
|
username: getOptionalEnv('E2E_ADMIN_USERNAME') ?? getOptionalEnv('BOOTSTRAP_ADMIN_USERNAME') ?? 'admin',
|
|
password: getOptionalEnv('E2E_ADMIN_PASSWORD') ?? getOptionalEnv('BOOTSTRAP_ADMIN_PASSWORD') ?? 'ChangeMe!2026',
|
|
}
|
|
}
|
|
|
|
function withoutKnownMetaCspWarning(messages: string[]): string[] {
|
|
return messages.filter((message) => (
|
|
!message.includes("frame-ancestors' is ignored when delivered via a <meta> element")
|
|
))
|
|
}
|
|
|
|
test.describe('Rejected version replacement (Real API)', () => {
|
|
test.describe.configure({ timeout: 150_000 })
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
await setEnglishLocale(page)
|
|
await createFreshSession(page, testInfo)
|
|
})
|
|
|
|
test('re-publishes the same version after rejection', async ({ page, browser }, testInfo) => {
|
|
const consoleErrors: string[] = []
|
|
const pageErrors: string[] = []
|
|
const adminConsoleErrors: string[] = []
|
|
const adminPageErrors: string[] = []
|
|
page.on('console', (message) => {
|
|
if (message.type() === 'error') consoleErrors.push(message.text())
|
|
})
|
|
page.on('pageerror', (error) => pageErrors.push(error.message))
|
|
|
|
const publisherBuilder = new E2eTestDataBuilder(page, testInfo)
|
|
await publisherBuilder.init()
|
|
|
|
const adminContext = await browser.newContext()
|
|
const adminPage = await adminContext.newPage()
|
|
adminPage.on('console', (message) => {
|
|
if (message.type() === 'error') adminConsoleErrors.push(message.text())
|
|
})
|
|
adminPage.on('pageerror', (error) => adminPageErrors.push(error.message))
|
|
const adminBuilder = new E2eTestDataBuilder(adminPage, testInfo)
|
|
await loginWithCredentials(adminPage, adminCredentials(), testInfo)
|
|
await adminBuilder.init()
|
|
|
|
try {
|
|
const namespace = await publisherBuilder.ensureWritableNamespace()
|
|
const skillName = `replace-rejected-${Date.now().toString(36)}`
|
|
const firstPublish = await publisherBuilder.publishSkill(namespace.slug, {
|
|
name: skillName,
|
|
version: '1.0.0',
|
|
})
|
|
const rejectedReviewId = await adminBuilder.waitForPendingReview(
|
|
namespace.slug,
|
|
firstPublish.slug,
|
|
firstPublish.version,
|
|
)
|
|
await publisherBuilder.waitForVersionStatus(
|
|
namespace.slug,
|
|
firstPublish.slug,
|
|
firstPublish.version,
|
|
'PENDING_REVIEW',
|
|
)
|
|
await adminBuilder.rejectReview(rejectedReviewId)
|
|
await publisherBuilder.waitForVersionStatus(
|
|
namespace.slug,
|
|
firstPublish.slug,
|
|
firstPublish.version,
|
|
'REJECTED',
|
|
)
|
|
|
|
await page.goto('/dashboard/review-progress')
|
|
await expect(page.getByRole('heading', { name: 'My Review Progress' })).toBeVisible()
|
|
const rejectedCard = page.locator('article').filter({ hasText: firstPublish.slug })
|
|
await expect(rejectedCard).toContainText('Rejected')
|
|
await expect(rejectedCard).toContainText('1 submission')
|
|
await rejectedCard.getByRole('button', { name: 'Submission history' }).click()
|
|
await expect(rejectedCard).toContainText('Rejected by Playwright E2E')
|
|
await rejectedCard.getByRole('link', { name: 'Edit and resubmit' }).click()
|
|
await expect(page).toHaveURL(/\/dashboard\/publish/)
|
|
await expect(page.getByText(new RegExp(`Resubmit .* v${firstPublish.version}`))).toBeVisible()
|
|
|
|
const replacement = await publisherBuilder.publishSkill(namespace.slug, {
|
|
name: skillName,
|
|
description: 'Replacement after review rejection',
|
|
version: '1.0.0',
|
|
})
|
|
const replacementReviewId = await adminBuilder.waitForPendingReview(
|
|
namespace.slug,
|
|
replacement.slug,
|
|
replacement.version,
|
|
)
|
|
await publisherBuilder.waitForVersionStatus(
|
|
namespace.slug,
|
|
replacement.slug,
|
|
replacement.version,
|
|
'PENDING_REVIEW',
|
|
)
|
|
|
|
expect(replacement.skillId).toBe(firstPublish.skillId)
|
|
expect(replacement.version).toBe(firstPublish.version)
|
|
expect(replacementReviewId).not.toBe(rejectedReviewId)
|
|
|
|
const progressResponse = await page.request.get(
|
|
`/api/web/reviews/my-progress?q=${encodeURIComponent(replacement.slug)}&page=0&size=20`,
|
|
)
|
|
expect(progressResponse.status()).toBe(200)
|
|
const progressBody = await progressResponse.json() as {
|
|
data: {
|
|
items: Array<{ latestStatus: string; attemptCount: number }>
|
|
total: number
|
|
statusCounts: { pending: number; approved: number; rejected: number }
|
|
}
|
|
}
|
|
expect(progressBody.data.total).toBe(1)
|
|
expect(progressBody.data.items).toHaveLength(1)
|
|
expect(progressBody.data.items[0]).toMatchObject({ latestStatus: 'PENDING', attemptCount: 2 })
|
|
expect(progressBody.data.statusCounts).toEqual({ pending: 1, approved: 0, rejected: 0 })
|
|
|
|
const rejectedFilterResponse = await page.request.get(
|
|
`/api/web/reviews/my-progress?q=${encodeURIComponent(replacement.slug)}&status=REJECTED&page=0&size=20`,
|
|
)
|
|
expect(rejectedFilterResponse.status()).toBe(200)
|
|
const rejectedFilterBody = await rejectedFilterResponse.json() as {
|
|
data: { items: unknown[]; total: number; statusCounts: { pending: number } }
|
|
}
|
|
expect(rejectedFilterBody.data.items).toEqual([])
|
|
expect(rejectedFilterBody.data.total).toBe(0)
|
|
expect(rejectedFilterBody.data.statusCounts.pending).toBe(1)
|
|
|
|
const missingSearchResponse = await page.request.get(
|
|
'/api/web/reviews/my-progress?q=definitely-missing-review-progress&page=0&size=20',
|
|
)
|
|
expect(missingSearchResponse.status()).toBe(200)
|
|
const missingSearchBody = await missingSearchResponse.json() as {
|
|
data: { items: unknown[]; total: number; statusCounts: { pending: number; approved: number; rejected: number } }
|
|
}
|
|
expect(missingSearchBody.data.items).toEqual([])
|
|
expect(missingSearchBody.data.total).toBe(0)
|
|
expect(missingSearchBody.data.statusCounts).toEqual({ pending: 0, approved: 0, rejected: 0 })
|
|
|
|
const outOfRangeResponse = await page.request.get(
|
|
`/api/web/reviews/my-progress?q=${encodeURIComponent(replacement.slug)}&page=99&size=1`,
|
|
)
|
|
expect(outOfRangeResponse.status()).toBe(200)
|
|
const outOfRangeBody = await outOfRangeResponse.json() as {
|
|
data: { items: unknown[]; total: number; statusCounts: { pending: number } }
|
|
}
|
|
expect(outOfRangeBody.data.items).toEqual([])
|
|
expect(outOfRangeBody.data.total).toBe(1)
|
|
expect(outOfRangeBody.data.statusCounts.pending).toBe(1)
|
|
|
|
const maximumPageResponse = await page.request.get(
|
|
`/api/web/reviews/my-progress?q=${encodeURIComponent(replacement.slug)}&page=2147483647&size=100`,
|
|
)
|
|
expect(maximumPageResponse.status()).toBe(200)
|
|
const maximumPageBody = await maximumPageResponse.json() as {
|
|
data: { items: unknown[]; total: number; statusCounts: { pending: number } }
|
|
}
|
|
expect(maximumPageBody.data.items).toEqual([])
|
|
expect(maximumPageBody.data.total).toBe(1)
|
|
expect(maximumPageBody.data.statusCounts.pending).toBe(1)
|
|
|
|
const attemptsResponse = await page.request.get(
|
|
`/api/web/reviews/my-progress/${replacementReviewId}/attempts`,
|
|
)
|
|
expect(attemptsResponse.status()).toBe(200)
|
|
const attemptsBody = await attemptsResponse.json() as {
|
|
data: Array<{ id: number; status: string; skillVersionId: number | null }>
|
|
}
|
|
expect(attemptsBody.data.map((attempt) => attempt.id)).toEqual([
|
|
replacementReviewId,
|
|
rejectedReviewId,
|
|
])
|
|
expect(attemptsBody.data.map((attempt) => attempt.status)).toEqual(['PENDING', 'REJECTED'])
|
|
expect(attemptsBody.data[1]?.skillVersionId).toBeNull()
|
|
|
|
const reviewerAttemptsResponse = await adminPage.request.get(
|
|
`/api/web/reviews/${replacementReviewId}/attempts`,
|
|
)
|
|
expect(reviewerAttemptsResponse.status()).toBe(200)
|
|
const reviewerAttemptsBody = await reviewerAttemptsResponse.json() as {
|
|
data: Array<{ id: number; status: string }>
|
|
}
|
|
expect(reviewerAttemptsBody.data.map((attempt) => attempt.id)).toEqual([
|
|
replacementReviewId,
|
|
rejectedReviewId,
|
|
])
|
|
|
|
const replacedReviewResponse = await adminPage.request.get(`/api/web/reviews/${rejectedReviewId}`)
|
|
expect(replacedReviewResponse.status()).toBe(200)
|
|
|
|
await page.goto('/dashboard/review-progress')
|
|
await expect(page.getByRole('heading', { name: 'My Review Progress' })).toBeVisible()
|
|
await expect(page.getByRole('button', { name: /In review/ })).toContainText('1')
|
|
const progressCard = page.locator('article').filter({ hasText: replacement.slug })
|
|
await expect(progressCard).toContainText('In review')
|
|
await expect(progressCard).toContainText('2 submissions')
|
|
await progressCard.getByRole('button', { name: 'Submission history' }).click()
|
|
await expect(progressCard).toContainText('Attempt 2')
|
|
await expect(progressCard).toContainText('Attempt 1')
|
|
await expect(progressCard).toContainText('Rejected by Playwright E2E')
|
|
await expect(progressCard).toContainText('Reviewed by')
|
|
await expect(progressCard.getByRole('link', { name: 'Edit and resubmit' })).toHaveCount(0)
|
|
await page.screenshot({ path: testInfo.outputPath('author-review-progress-desktop.png'), fullPage: true })
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 })
|
|
await expect(progressCard).toBeVisible()
|
|
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true)
|
|
await page.screenshot({ path: testInfo.outputPath('author-review-progress-mobile.png'), fullPage: true })
|
|
|
|
await adminPage.goto(`/dashboard/reviews/${replacementReviewId}`)
|
|
await expect(adminPage.getByRole('heading', { name: 'Submission History' })).toBeVisible()
|
|
await expect(adminPage.getByText('Attempt 2')).toBeVisible()
|
|
await expect(adminPage.getByText('Attempt 1')).toBeVisible()
|
|
expect(withoutKnownMetaCspWarning(consoleErrors)).toEqual([])
|
|
expect(pageErrors).toEqual([])
|
|
expect(withoutKnownMetaCspWarning(adminConsoleErrors)).toEqual([])
|
|
expect(adminPageErrors).toEqual([])
|
|
} finally {
|
|
await adminBuilder.cleanup()
|
|
await adminContext.close()
|
|
await publisherBuilder.cleanup()
|
|
}
|
|
})
|
|
})
|