mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-06 08:15:57 +00:00
test(review): strengthen exact-sha coverage
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
parent
918ef9d265
commit
39861bc6d8
5 changed files with 173 additions and 10 deletions
|
|
@ -326,10 +326,15 @@ class ReviewPortalControllerTest {
|
|||
ReviewTask previous = createReviewTask(8L, 20L, "author-1", ReviewTaskStatus.REJECTED);
|
||||
setField(previous, "skillId", 30L);
|
||||
setField(previous, "skillVersion", "1.0.0");
|
||||
ReviewTask otherAuthor = createReviewTask(7L, 20L, "author-2", ReviewTaskStatus.REJECTED);
|
||||
setField(otherAuthor, "skillId", 30L);
|
||||
setField(otherAuthor, "skillVersion", "1.0.0");
|
||||
given(reviewTaskRepository.findById(12L)).willReturn(Optional.of(latest));
|
||||
given(reviewTaskRepository.findBySubmittedByAndSkillIdAndSkillVersionOrderBySubmittedAtDescIdDesc(
|
||||
"author-1", 30L, "1.0.0"))
|
||||
.willReturn(List.of(latest, previous));
|
||||
given(reviewTaskRepository.findBySkillIdAndSkillVersionOrderBySubmittedAtDescIdDesc(30L, "1.0.0"))
|
||||
.willReturn(List.of(latest, previous, otherAuthor));
|
||||
given(governanceQueryRepository.getReviewTaskResponses(List.of(latest, previous)))
|
||||
.willReturn(List.of(toReviewResponse(latest), toReviewResponse(previous)));
|
||||
|
||||
|
|
@ -337,7 +342,12 @@ class ReviewPortalControllerTest {
|
|||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(2))
|
||||
.andExpect(jsonPath("$.data[0].id").value(12))
|
||||
.andExpect(jsonPath("$.data[1].id").value(8));
|
||||
.andExpect(jsonPath("$.data[1].id").value(8))
|
||||
.andExpect(jsonPath("$.data[0].submittedBy").value("author-1"))
|
||||
.andExpect(jsonPath("$.data[1].submittedBy").value("author-1"));
|
||||
|
||||
verify(reviewTaskRepository, never())
|
||||
.findBySkillIdAndSkillVersionOrderBySubmittedAtDescIdDesc(30L, "1.0.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -345,7 +355,7 @@ class ReviewPortalControllerTest {
|
|||
ReviewTask latest = createReviewTask(12L, 20L, "author-1", ReviewTaskStatus.PENDING);
|
||||
setField(latest, "skillId", 30L);
|
||||
setField(latest, "skillVersion", "1.0.0");
|
||||
ReviewTask previous = createReviewTask(8L, 20L, "author-1", ReviewTaskStatus.REJECTED);
|
||||
ReviewTask previous = createReviewTask(8L, 20L, "author-2", ReviewTaskStatus.REJECTED);
|
||||
setField(previous, "skillId", 30L);
|
||||
setField(previous, "skillVersion", "1.0.0");
|
||||
Namespace namespace = createNamespace(20L, "team-a");
|
||||
|
|
@ -368,7 +378,41 @@ class ReviewPortalControllerTest {
|
|||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(2))
|
||||
.andExpect(jsonPath("$.data[0].id").value(12))
|
||||
.andExpect(jsonPath("$.data[1].id").value(8));
|
||||
.andExpect(jsonPath("$.data[1].id").value(8))
|
||||
.andExpect(jsonPath("$.data[0].submittedBy").value("author-1"))
|
||||
.andExpect(jsonPath("$.data[1].submittedBy").value("author-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listReviewAttempts_allowsNamespaceAdminToReadCrossAuthorHistory() throws Exception {
|
||||
ReviewTask latest = createReviewTask(12L, 20L, "author-1", ReviewTaskStatus.PENDING);
|
||||
setField(latest, "skillId", 30L);
|
||||
setField(latest, "skillVersion", "1.0.0");
|
||||
ReviewTask previous = createReviewTask(8L, 20L, "author-2", ReviewTaskStatus.REJECTED);
|
||||
setField(previous, "skillId", 30L);
|
||||
setField(previous, "skillVersion", "1.0.0");
|
||||
Namespace namespace = createNamespace(20L, "team-a");
|
||||
stubNamespaceRoles("namespace-admin", List.of(new NamespaceMember(
|
||||
20L, "namespace-admin", NamespaceRole.ADMIN)));
|
||||
given(rbacService.getUserRoleCodes("namespace-admin")).willReturn(Set.of());
|
||||
given(reviewTaskRepository.findById(12L)).willReturn(Optional.of(latest));
|
||||
given(namespaceRepository.findById(20L)).willReturn(Optional.of(namespace));
|
||||
given(reviewService.canReviewNamespace(
|
||||
latest,
|
||||
"namespace-admin",
|
||||
namespace.getType(),
|
||||
Map.of(20L, NamespaceRole.ADMIN),
|
||||
Set.of())).willReturn(true);
|
||||
given(reviewTaskRepository.findBySkillIdAndSkillVersionOrderBySubmittedAtDescIdDesc(30L, "1.0.0"))
|
||||
.willReturn(List.of(latest, previous));
|
||||
given(governanceQueryRepository.getReviewTaskResponses(List.of(latest, previous)))
|
||||
.willReturn(List.of(toReviewResponse(latest), toReviewResponse(previous)));
|
||||
|
||||
mockMvc.perform(get("/api/v1/reviews/12/attempts").with(auth("namespace-admin")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.data.length()").value(2))
|
||||
.andExpect(jsonPath("$.data[0].submittedBy").value("author-1"))
|
||||
.andExpect(jsonPath("$.data[1].submittedBy").value("author-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ class JpaReviewProgressQueryRepositoryTest {
|
|||
new Skill(namespace.getId(), "alpha-skill", "author-1", SkillVisibility.PUBLIC));
|
||||
Skill beta = entityManager.persistFlushFind(
|
||||
new Skill(namespace.getId(), "beta-skill", "author-1", SkillVisibility.PUBLIC));
|
||||
Skill gamma = entityManager.persistFlushFind(
|
||||
new Skill(namespace.getId(), "gamma-skill", "author-1", SkillVisibility.PUBLIC));
|
||||
|
||||
persistAttempt(
|
||||
alpha,
|
||||
|
|
@ -56,6 +58,13 @@ class JpaReviewProgressQueryRepositoryTest {
|
|||
"2.0.0",
|
||||
ReviewTaskStatus.APPROVED,
|
||||
Instant.parse("2026-08-29T10:00:00Z"));
|
||||
persistAttempt(
|
||||
gamma,
|
||||
namespace,
|
||||
"author-1",
|
||||
"3.0.0",
|
||||
ReviewTaskStatus.REJECTED,
|
||||
Instant.parse("2026-08-28T10:00:00Z"));
|
||||
persistAttempt(
|
||||
beta,
|
||||
namespace,
|
||||
|
|
@ -68,7 +77,7 @@ class JpaReviewProgressQueryRepositoryTest {
|
|||
|
||||
var firstPage = repository.findMyProgress("author-1", null, "", 0, 1);
|
||||
|
||||
assertThat(firstPage.total()).isEqualTo(2);
|
||||
assertThat(firstPage.total()).isEqualTo(3);
|
||||
assertThat(firstPage.items()).singleElement().satisfies(item -> {
|
||||
assertThat(item.skillSlug()).isEqualTo("alpha-skill");
|
||||
assertThat(item.latestStatus()).isEqualTo("PENDING");
|
||||
|
|
@ -76,11 +85,11 @@ class JpaReviewProgressQueryRepositoryTest {
|
|||
});
|
||||
assertThat(firstPage.statusCounts().pending()).isEqualTo(1);
|
||||
assertThat(firstPage.statusCounts().approved()).isEqualTo(1);
|
||||
assertThat(firstPage.statusCounts().rejected()).isZero();
|
||||
assertThat(firstPage.statusCounts().rejected()).isEqualTo(1);
|
||||
|
||||
var emptyPage = repository.findMyProgress("author-1", null, "", 8, 1);
|
||||
assertThat(emptyPage.items()).isEmpty();
|
||||
assertThat(emptyPage.total()).isEqualTo(2);
|
||||
assertThat(emptyPage.total()).isEqualTo(3);
|
||||
|
||||
var searchedAndFiltered = repository.findMyProgress(
|
||||
"author-1", ReviewTaskStatus.APPROVED, "BETA", 0, 20);
|
||||
|
|
@ -88,6 +97,13 @@ class JpaReviewProgressQueryRepositoryTest {
|
|||
.satisfies(item -> assertThat(item.skillSlug()).isEqualTo("beta-skill"));
|
||||
assertThat(searchedAndFiltered.total()).isEqualTo(1);
|
||||
assertThat(searchedAndFiltered.statusCounts().approved()).isEqualTo(1);
|
||||
|
||||
var searchMiss = repository.findMyProgress("author-1", null, "missing", 0, 20);
|
||||
assertThat(searchMiss.items()).isEmpty();
|
||||
assertThat(searchMiss.total()).isZero();
|
||||
assertThat(searchMiss.statusCounts().pending()).isZero();
|
||||
assertThat(searchMiss.statusCounts().approved()).isZero();
|
||||
assertThat(searchMiss.statusCounts().rejected()).isZero();
|
||||
}
|
||||
|
||||
private void persistAttempt(
|
||||
|
|
|
|||
|
|
@ -93,6 +93,55 @@ test.describe('Rejected version replacement (Real API)', () => {
|
|||
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 attemptsResponse = await page.request.get(
|
||||
`/api/web/reviews/my-progress/${replacementReviewId}/attempts`,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,15 @@ test.describe('Light and dark theme', () => {
|
|||
await setEnglishLocale(page)
|
||||
await page.context().setExtraHTTPHeaders({ 'X-Mock-User-Id': 'local-user' })
|
||||
await page.addInitScript(() => {
|
||||
const observedWindow = window as Window & { __themeAtFirstReactContent?: boolean }
|
||||
const observer = new MutationObserver(() => {
|
||||
const root = document.querySelector('#root')
|
||||
if (root?.childElementCount) {
|
||||
observedWindow.__themeAtFirstReactContent = document.documentElement.classList.contains('dark')
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
observer.observe(document, { childList: true, subtree: true })
|
||||
if (!window.sessionStorage.getItem('theme-test-initialized')) {
|
||||
window.localStorage.removeItem('skillhub-theme')
|
||||
window.sessionStorage.setItem('theme-test-initialized', 'true')
|
||||
|
|
@ -21,6 +30,33 @@ test.describe('Light and dark theme', () => {
|
|||
})
|
||||
page.on('pageerror', (error) => pageErrors.push(error.stack ?? error.message))
|
||||
|
||||
await page.route('**/api/web/notifications?*', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
code: 0,
|
||||
msg: 'success',
|
||||
data: {
|
||||
items: [{
|
||||
id: 9001,
|
||||
category: 'REVIEW',
|
||||
eventType: 'REVIEW_SUBMITTED',
|
||||
title: 'Theme notification fixture',
|
||||
bodyJson: JSON.stringify({ skillName: 'Theme preview', version: '1.0.0' }),
|
||||
status: 'UNREAD',
|
||||
createdAt: '2026-09-01T00:00:00Z',
|
||||
}],
|
||||
total: 1,
|
||||
page: 0,
|
||||
size: 5,
|
||||
},
|
||||
timestamp: '2026-09-01T00:00:00Z',
|
||||
requestId: 'theme-notification-fixture',
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await expect(page.locator('html')).not.toHaveClass(/dark/)
|
||||
|
||||
|
|
@ -32,15 +68,24 @@ test.describe('Light and dark theme', () => {
|
|||
await expect(page.locator('html')).toHaveClass(/dark/)
|
||||
await expect(page.getByRole('button', { name: 'Switch to light theme' })).toBeVisible()
|
||||
await expect(page.getByRole('heading', { name: 'SkillHub', exact: true })).toBeVisible()
|
||||
await expect.poll(() => page.evaluate(() => (
|
||||
window as Window & { __themeAtFirstReactContent?: boolean }
|
||||
).__themeAtFirstReactContent)).toBe(true)
|
||||
|
||||
await page.getByRole('link', { name: 'Search', exact: true }).first().click()
|
||||
await expect(page).toHaveURL(/\/search$/)
|
||||
await expect(page.locator('html')).toHaveClass(/dark/)
|
||||
await page.screenshot({ path: testInfo.outputPath('dark-desktop.png'), fullPage: true })
|
||||
|
||||
const notificationButton = page.getByRole('button', { name: 'Notifications' })
|
||||
await notificationButton.click()
|
||||
await expect(page.getByText('Notifications', { exact: true })).toBeVisible()
|
||||
const firstNotification = notificationButton.locator('..').locator('a').first()
|
||||
if (await firstNotification.count()) {
|
||||
await firstNotification.hover()
|
||||
}
|
||||
const firstNotification = page.getByRole('link').filter({ hasText: 'Review submitted' })
|
||||
await expect(firstNotification).toBeVisible()
|
||||
const backgroundBeforeHover = await firstNotification.evaluate((element) => getComputedStyle(element).backgroundColor)
|
||||
await firstNotification.hover()
|
||||
await expect.poll(() => firstNotification.evaluate((element) => getComputedStyle(element).backgroundColor))
|
||||
.not.toBe(backgroundBeforeHover)
|
||||
await page.screenshot({ path: testInfo.outputPath('dark-notifications.png'), fullPage: true })
|
||||
await notificationButton.click()
|
||||
|
||||
|
|
|
|||
|
|
@ -50,4 +50,13 @@ describe('theme preference', () => {
|
|||
expect(document.documentElement.classList.contains('dark')).toBe(true)
|
||||
expect(window.localStorage.getItem(THEME_STORAGE_KEY)).toBe('dark')
|
||||
})
|
||||
|
||||
it('ignores storage write failures', () => {
|
||||
expect(() => saveTheme('dark', {
|
||||
getItem: () => null,
|
||||
setItem: () => {
|
||||
throw new Error('blocked')
|
||||
},
|
||||
})).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue