feat(perf): пагинация аудита и bulk-группы курса
This commit is contained in:
@@ -17,5 +17,6 @@ class AuditController(
|
||||
fun list(
|
||||
@RequestParam(defaultValue = "100") limit: Int,
|
||||
@RequestParam(required = false) action: String?,
|
||||
): List<AuditEventDto> = auditService.list(limit, action)
|
||||
@RequestParam(defaultValue = "0") page: Int,
|
||||
): List<AuditEventDto> = auditService.list(limit, action, page)
|
||||
}
|
||||
|
||||
@@ -38,15 +38,17 @@ class AuditService(
|
||||
fun list(
|
||||
limit: Int = DEFAULT_LIMIT,
|
||||
action: String? = null,
|
||||
page: Int = 0,
|
||||
): List<AuditEventDto> {
|
||||
val size = limit.coerceIn(1, MAX_LIMIT)
|
||||
val page = PageRequest.of(0, size)
|
||||
val pageIndex = page.coerceAtLeast(0)
|
||||
val pageable = PageRequest.of(pageIndex, size)
|
||||
val filter = action?.trim()?.takeIf { it.isNotEmpty() }
|
||||
val found =
|
||||
if (filter == null) {
|
||||
events.findAllByOrderByOccurredAtDescIdDesc(page)
|
||||
events.findAllByOrderByOccurredAtDescIdDesc(pageable)
|
||||
} else {
|
||||
events.findAllByActionOrderByOccurredAtDescIdDesc(filter, page)
|
||||
events.findAllByActionOrderByOccurredAtDescIdDesc(filter, pageable)
|
||||
}
|
||||
return found.map(::toDto)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import ru.ysu.lms.identity.repo.AccountRepository
|
||||
import ru.ysu.lms.identity.roster.CreateGroupRequest
|
||||
import ru.ysu.lms.identity.roster.CreateStudentRequest
|
||||
import ru.ysu.lms.identity.roster.GroupService
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.UUID
|
||||
|
||||
@SpringBootTest(properties = ["lms.promotion.scheduler-enabled=false"])
|
||||
@@ -198,6 +199,33 @@ class AuditIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pagination returns disjoint pages newest first`() {
|
||||
repeat(5) { index ->
|
||||
write(AuditEventRequest(action = "AUDIT_PAGE", entityType = "STUDENT", details = "page-${index + 1}"))
|
||||
}
|
||||
|
||||
val first = detailsOfPage(limit = 2, page = 0)
|
||||
val second = detailsOfPage(limit = 2, page = 1)
|
||||
val third = detailsOfPage(limit = 2, page = 2)
|
||||
|
||||
assertEquals(listOf("page-5", "page-4"), first)
|
||||
assertEquals(listOf("page-3", "page-2"), second)
|
||||
assertEquals(listOf("page-1"), third)
|
||||
assertTrue(first.intersect(second.toSet()).isEmpty())
|
||||
assertTrue((first + second).intersect(third.toSet()).isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `negative page is clamped to the first page and page past the end is empty`() {
|
||||
repeat(3) { index ->
|
||||
write(AuditEventRequest(action = "AUDIT_PAGE_CLAMP", entityType = "STUDENT", details = "page-${index + 1}"))
|
||||
}
|
||||
|
||||
assertEquals(listOf("page-3", "page-2"), detailsOfPage(limit = 2, page = -5))
|
||||
assertEquals(emptyList<String>(), detailsOfPage(limit = 2, page = 1000))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `blank action or entity type is rejected`() {
|
||||
writeExpectingBadRequest(AuditEventRequest(action = " ", entityType = "STUDENT"))
|
||||
@@ -291,6 +319,24 @@ class AuditIntegrationTest {
|
||||
assertTrue(auditEvents.findById(deleteEvent.id).isPresent)
|
||||
}
|
||||
|
||||
private fun detailsOfPage(
|
||||
limit: Int,
|
||||
page: Int,
|
||||
): List<String> {
|
||||
val body =
|
||||
mockMvc
|
||||
.get("/api/v1/audit") {
|
||||
with(admin())
|
||||
param("limit", limit.toString())
|
||||
param("page", page.toString())
|
||||
}.andExpect {
|
||||
status { isOk() }
|
||||
}.andReturn()
|
||||
.response
|
||||
.getContentAsString(StandardCharsets.UTF_8)
|
||||
return objectMapper.readTree(body).map { it.path("details").asText() }
|
||||
}
|
||||
|
||||
private fun write(request: AuditEventRequest) {
|
||||
mockMvc
|
||||
.post("/api/v1/internal/audit") {
|
||||
|
||||
Reference in New Issue
Block a user