test(web): преподавательская ведомость, материалы и аудит
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package ru.ysu.lms.web.admin
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.mock.web.MockServletContext
|
||||
import org.thymeleaf.context.WebContext
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine
|
||||
import org.thymeleaf.templatemode.TemplateMode
|
||||
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
|
||||
import org.thymeleaf.web.servlet.JakartaServletWebApplication
|
||||
import java.util.Locale
|
||||
|
||||
class AdminAuditTemplateTest {
|
||||
private val engine =
|
||||
SpringTemplateEngine().apply {
|
||||
setTemplateResolver(
|
||||
ClassLoaderTemplateResolver().apply {
|
||||
prefix = "templates/"
|
||||
suffix = ".html"
|
||||
characterEncoding = "UTF-8"
|
||||
templateMode = TemplateMode.HTML
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `renders audit events with labels and filter`() {
|
||||
val rows =
|
||||
listOf(
|
||||
AuditRow(
|
||||
occurredMsk = "01.09.2026 00:00",
|
||||
actorLabel = "admin",
|
||||
action = "GRADE_SET",
|
||||
actionLabel = "Выставлена оценка",
|
||||
entityType = "GRADE",
|
||||
entityId = "g1",
|
||||
entityLabel = "Оценка · g1",
|
||||
courseId = "c1",
|
||||
studentId = "s1",
|
||||
details = "Оценка 5",
|
||||
),
|
||||
AuditRow(
|
||||
occurredMsk = "31.08.2026 23:50",
|
||||
actorLabel = "система",
|
||||
action = "STUDENT_TRANSFER",
|
||||
actionLabel = "Перевод студента",
|
||||
entityType = "STUDENT",
|
||||
entityId = null,
|
||||
entityLabel = "Студент",
|
||||
courseId = null,
|
||||
studentId = null,
|
||||
details = "",
|
||||
),
|
||||
)
|
||||
|
||||
val html =
|
||||
render(
|
||||
"admin/audit",
|
||||
mapOf(
|
||||
"events" to rows,
|
||||
"action" to "GRADE_SET",
|
||||
"limit" to 100,
|
||||
"actionOptions" to AuditViews.actionOptions("GRADE_SET"),
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(html).contains("Аудит")
|
||||
assertThat(html).contains("name=\"action\"")
|
||||
assertThat(html).contains("name=\"limit\"")
|
||||
assertThat(html).contains("value=\"GRADE_SET\"")
|
||||
assertThat(html).contains("Все действия")
|
||||
assertThat(html).contains("01.09.2026 00:00")
|
||||
assertThat(html).contains("31.08.2026 23:50")
|
||||
assertThat(html).contains("admin")
|
||||
assertThat(html).contains("система")
|
||||
assertThat(html).contains("Выставлена оценка")
|
||||
assertThat(html).contains("Перевод студента")
|
||||
assertThat(html).contains("Оценка · g1")
|
||||
assertThat(html).contains("c1")
|
||||
assertThat(html).contains("s1")
|
||||
assertThat(html).contains("Оценка 5")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `renders empty audit state`() {
|
||||
val html =
|
||||
render(
|
||||
"admin/audit",
|
||||
mapOf(
|
||||
"events" to emptyList<AuditRow>(),
|
||||
"action" to "",
|
||||
"limit" to 100,
|
||||
"actionOptions" to AuditViews.actionOptions(null),
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(html).contains("Аудит")
|
||||
assertThat(html).contains("Записей аудита нет.")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `keeps unknown action filter selectable`() {
|
||||
val html =
|
||||
render(
|
||||
"admin/audit",
|
||||
mapOf(
|
||||
"events" to emptyList<AuditRow>(),
|
||||
"action" to "CUSTOM_ACTION",
|
||||
"limit" to 100,
|
||||
"actionOptions" to AuditViews.actionOptions("CUSTOM_ACTION"),
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(html).contains("value=\"CUSTOM_ACTION\"")
|
||||
assertThat(html).contains("CUSTOM_ACTION")
|
||||
}
|
||||
|
||||
private fun render(
|
||||
template: String,
|
||||
variables: Map<String, Any?>,
|
||||
): String {
|
||||
val servletContext = MockServletContext()
|
||||
val application = JakartaServletWebApplication.buildApplication(servletContext)
|
||||
val exchange =
|
||||
application.buildExchange(
|
||||
MockHttpServletRequest(servletContext),
|
||||
MockHttpServletResponse(),
|
||||
)
|
||||
val context = WebContext(exchange, Locale.forLanguageTag("ru"))
|
||||
context.setVariable("csrfToken", "csrf")
|
||||
variables.forEach { (name, value) -> context.setVariable(name, value) }
|
||||
return engine.process(template, context)
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,7 @@ class AdminStage4TemplateTest {
|
||||
"attestations" to emptyList<GroupAttestation>(),
|
||||
"publishedLabsCount" to 0,
|
||||
"profileCourseId" to null,
|
||||
"teacherView" to false,
|
||||
)
|
||||
|
||||
val html = render("admin/group", model)
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package ru.ysu.lms.web.admin
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.mock.web.MockServletContext
|
||||
import org.thymeleaf.context.WebContext
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine
|
||||
import org.thymeleaf.templatemode.TemplateMode
|
||||
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
|
||||
import org.thymeleaf.web.servlet.JakartaServletWebApplication
|
||||
import ru.ysu.lms.web.api.GroupDetailDto
|
||||
import ru.ysu.lms.web.api.LabSummaryDto
|
||||
import ru.ysu.lms.web.api.StudentRowDto
|
||||
import java.util.Locale
|
||||
|
||||
class TeacherGradebookTemplateTest {
|
||||
private val engine =
|
||||
SpringTemplateEngine().apply {
|
||||
setTemplateResolver(
|
||||
ClassLoaderTemplateResolver().apply {
|
||||
prefix = "templates/"
|
||||
suffix = ".html"
|
||||
characterEncoding = "UTF-8"
|
||||
templateMode = TemplateMode.HTML
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher gradebook keeps grading and defense forms`() {
|
||||
val html = render()
|
||||
|
||||
assertThat(html).contains("action=\"/admin/groups/g1/grades\"")
|
||||
assertThat(html).contains("action=\"/admin/defenses/save\"")
|
||||
assertThat(html).contains("/admin/groups/g1/publish")
|
||||
assertThat(html).contains("name=\"courseId\"")
|
||||
assertThat(html).contains("value=\"c1\"")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher gradebook has no admin-only controls`() {
|
||||
val html = render()
|
||||
|
||||
assertThat(html).doesNotContain("/credentials")
|
||||
assertThat(html).doesNotContain("Пароли")
|
||||
assertThat(html).doesNotContain("password-dot")
|
||||
assertThat(html).doesNotContain("reset-password")
|
||||
assertThat(html).doesNotContain("academic-leave")
|
||||
assertThat(html).doesNotContain("/students/s1/archive")
|
||||
assertThat(html).doesNotContain("/students/s1/expel")
|
||||
assertThat(html).doesNotContain("/students/s1/unenroll")
|
||||
assertThat(html).doesNotContain("/attestations")
|
||||
assertThat(html).doesNotContain("modal-add-attestation")
|
||||
assertThat(html).doesNotContain("modal-add-student")
|
||||
assertThat(html).doesNotContain("Добавить студента")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher gradebook links profiles to teacher course`() {
|
||||
val html = render()
|
||||
|
||||
assertThat(html).contains("/teacher/courses/c1/students/s1")
|
||||
assertThat(html).doesNotContain("/admin/courses/c1/students/s1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `admin view is used when teacherView attribute is missing`() {
|
||||
val html = render(teacherView = null)
|
||||
|
||||
assertThat(html).contains("/credentials")
|
||||
assertThat(html).contains("/students/s1/reset-password")
|
||||
assertThat(html).contains("/admin/courses/c1/students/s1")
|
||||
assertThat(html).doesNotContain("/teacher/courses/c1/students/s1")
|
||||
}
|
||||
|
||||
private fun render(teacherView: Boolean? = true): String {
|
||||
val group =
|
||||
GroupDetailDto(
|
||||
id = "g1",
|
||||
name = "ПИ-21БО",
|
||||
students =
|
||||
listOf(
|
||||
StudentRowDto(
|
||||
id = "s1",
|
||||
studentNo = "123456",
|
||||
fullName = "Иванов Иван",
|
||||
status = "ACTIVE",
|
||||
mustChangePassword = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
val lab = LabSummaryDto(id = "l1", number = 1, title = "Введение", status = "published")
|
||||
val servletContext = MockServletContext()
|
||||
val application = JakartaServletWebApplication.buildApplication(servletContext)
|
||||
val exchange =
|
||||
application.buildExchange(
|
||||
MockHttpServletRequest(servletContext),
|
||||
MockHttpServletResponse(),
|
||||
)
|
||||
val context = WebContext(exchange, Locale.forLanguageTag("ru"))
|
||||
context.setVariable("csrfToken", "csrf")
|
||||
if (teacherView != null) context.setVariable("teacherView", teacherView)
|
||||
context.setVariable("profileCourseId", "c1")
|
||||
context.setVariable("group", group)
|
||||
context.setVariable("labs", listOf(lab))
|
||||
context.setVariable("candidates", emptyList<Any>())
|
||||
context.setVariable("studentGrades", listOf(studentRow()))
|
||||
context.setVariable("labPublication", mapOf("l1" to GroupLabPublication(allPublished = true, publishedCount = 1, total = 1)))
|
||||
context.setVariable("labSubmissionCounts", mapOf("l1" to 1))
|
||||
context.setVariable("attestations", emptyList<GroupAttestation>())
|
||||
context.setVariable("publishedLabsCount", 1)
|
||||
return engine.process("admin/group", context)
|
||||
}
|
||||
|
||||
private fun studentRow(): GroupStudentRow =
|
||||
GroupStudentRow(
|
||||
studentId = "s1",
|
||||
fullName = "Иванов Иван",
|
||||
studentNo = "123456",
|
||||
basis = "",
|
||||
status = "ACTIVE",
|
||||
mustChangePassword = true,
|
||||
cells =
|
||||
listOf(
|
||||
GroupLabCell(
|
||||
labId = "l1",
|
||||
labNumber = 1,
|
||||
labTitle = "Введение",
|
||||
score = "4",
|
||||
published = true,
|
||||
),
|
||||
),
|
||||
variantsSummary = "",
|
||||
commentsJson = "[]",
|
||||
averageScore = "4",
|
||||
)
|
||||
}
|
||||
65
web/src/test/kotlin/ru/ysu/lms/web/api/AuditDtoTest.kt
Normal file
65
web/src/test/kotlin/ru/ysu/lms/web/api/AuditDtoTest.kt
Normal file
@@ -0,0 +1,65 @@
|
||||
package ru.ysu.lms.web.api
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import tools.jackson.databind.ObjectMapper
|
||||
|
||||
class AuditDtoTest {
|
||||
private val mapper = ObjectMapper()
|
||||
|
||||
@Test
|
||||
fun `deserializes audit event list with nulls`() {
|
||||
val json =
|
||||
"""
|
||||
[
|
||||
{
|
||||
"id": "e1",
|
||||
"occurredAt": "2026-08-31T21:00:00Z",
|
||||
"actorAccountId": "a1",
|
||||
"actorLogin": "admin",
|
||||
"action": "GRADE_SET",
|
||||
"entityType": "GRADE",
|
||||
"entityId": "g1",
|
||||
"courseId": "c1",
|
||||
"studentId": "s1",
|
||||
"details": "Оценка 5",
|
||||
"unknownField": "ignored"
|
||||
},
|
||||
{
|
||||
"id": "e2",
|
||||
"occurredAt": "2026-09-01T10:15:00Z",
|
||||
"actorAccountId": null,
|
||||
"actorLogin": null,
|
||||
"action": "STUDENT_TRANSFER",
|
||||
"entityType": "STUDENT",
|
||||
"entityId": null,
|
||||
"courseId": null,
|
||||
"studentId": null,
|
||||
"details": ""
|
||||
}
|
||||
]
|
||||
""".trimIndent()
|
||||
|
||||
val events = mapper.readValue(json, Array<AuditEventDto>::class.java).toList()
|
||||
|
||||
assertThat(events).hasSize(2)
|
||||
assertThat(events[0].id).isEqualTo("e1")
|
||||
assertThat(events[0].occurredAt).isEqualTo("2026-08-31T21:00:00Z")
|
||||
assertThat(events[0].actorAccountId).isEqualTo("a1")
|
||||
assertThat(events[0].actorLogin).isEqualTo("admin")
|
||||
assertThat(events[0].action).isEqualTo("GRADE_SET")
|
||||
assertThat(events[0].entityType).isEqualTo("GRADE")
|
||||
assertThat(events[0].entityId).isEqualTo("g1")
|
||||
assertThat(events[0].courseId).isEqualTo("c1")
|
||||
assertThat(events[0].studentId).isEqualTo("s1")
|
||||
assertThat(events[0].details).isEqualTo("Оценка 5")
|
||||
|
||||
assertThat(events[1].id).isEqualTo("e2")
|
||||
assertThat(events[1].actorAccountId).isNull()
|
||||
assertThat(events[1].actorLogin).isNull()
|
||||
assertThat(events[1].entityId).isNull()
|
||||
assertThat(events[1].courseId).isNull()
|
||||
assertThat(events[1].studentId).isNull()
|
||||
assertThat(events[1].details).isEmpty()
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@ import org.springframework.mock.web.MockHttpSession
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
|
||||
class AdminInterceptorTest {
|
||||
@Controller
|
||||
@@ -19,8 +21,74 @@ class AdminInterceptorTest {
|
||||
@GetMapping("/admin/labs/{id}")
|
||||
fun labDetail(): String = "admin/lab"
|
||||
|
||||
@GetMapping("/admin/labs/{id}/schema")
|
||||
fun labSchema(): String = "admin/lab"
|
||||
|
||||
@GetMapping("/admin/users")
|
||||
fun users(): String = "admin/users"
|
||||
|
||||
@GetMapping("/admin/groups/{id}")
|
||||
fun group(): String = "admin/group"
|
||||
|
||||
@GetMapping("/admin/groups")
|
||||
fun groups(): String = "admin/groups"
|
||||
|
||||
@GetMapping("/admin/groups/{id}/assign-modal")
|
||||
fun assignModal(): String = "admin/assign-fragment"
|
||||
|
||||
@GetMapping("/admin/groups/{id}/submissions.zip")
|
||||
fun submissions(): String = "admin/group"
|
||||
|
||||
@GetMapping("/admin/submissions/{id}/download")
|
||||
fun downloadSubmission(): String = "admin/group"
|
||||
|
||||
@GetMapping("/admin/defense-watch")
|
||||
fun defenseWatch(): String = "admin/group"
|
||||
|
||||
@GetMapping("/admin/comment-templates")
|
||||
fun commentTemplates(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups")
|
||||
fun createGroup(): String = "admin/groups"
|
||||
|
||||
@PostMapping("/admin/groups/import")
|
||||
fun importGroup(): String = "admin/groups"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/attestations")
|
||||
fun createAttestation(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/comment-templates/delete")
|
||||
fun deleteCommentTemplate(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/grades")
|
||||
fun grades(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/publish")
|
||||
fun publish(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/publish-student")
|
||||
fun publishStudent(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/set-level")
|
||||
fun setLevel(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/pin-variant")
|
||||
fun pinVariant(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/distribute")
|
||||
fun distribute(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/groups/{id}/distribute-all")
|
||||
fun distributeAll(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/defenses/save")
|
||||
fun saveDefense(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/defenses/{id}/issue-code")
|
||||
fun issueCode(): String = "admin/group"
|
||||
|
||||
@PostMapping("/admin/defenses/{id}/reset")
|
||||
fun resetDefense(): String = "admin/group"
|
||||
}
|
||||
|
||||
private val mockMvc: MockMvc =
|
||||
@@ -37,10 +105,83 @@ class AdminInterceptorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher may open lab detail`() {
|
||||
fun `teacher may open lab detail and schema`() {
|
||||
mockMvc
|
||||
.perform(get("/admin/labs/l1").session(sessionFor("TEACHER")))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(get("/admin/labs/l1/schema").session(sessionFor("TEACHER")))
|
||||
.andExpect(status().isOk)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher may open gradebook page and its fragments`() {
|
||||
val session = sessionFor("TEACHER")
|
||||
mockMvc
|
||||
.perform(get("/admin/groups/g1").session(session))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(get("/admin/groups/g1/assign-modal").session(session))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(get("/admin/groups/g1/submissions.zip").session(session))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(get("/admin/submissions/s1/download").session(session))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(get("/admin/defense-watch").session(session))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(get("/admin/comment-templates").session(session))
|
||||
.andExpect(status().isOk)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher may run gradebook actions`() {
|
||||
val session = sessionFor("TEACHER")
|
||||
listOf(
|
||||
"/admin/groups/g1/grades",
|
||||
"/admin/groups/g1/publish",
|
||||
"/admin/groups/g1/publish-student",
|
||||
"/admin/groups/g1/set-level",
|
||||
"/admin/groups/g1/pin-variant",
|
||||
"/admin/groups/g1/distribute",
|
||||
).forEach { url ->
|
||||
mockMvc
|
||||
.perform(post(url).session(session))
|
||||
.andExpect(status().isOk)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher may run defense actions`() {
|
||||
val session = sessionFor("TEACHER")
|
||||
mockMvc
|
||||
.perform(post("/admin/defenses/save").session(session))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(post("/admin/defenses/d1/issue-code").session(session))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(post("/admin/defenses/d1/reset").session(session))
|
||||
.andExpect(status().isOk)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `teacher is redirected from admin-only group operations`() {
|
||||
val session = sessionFor("TEACHER")
|
||||
listOf("/admin/groups", "/admin/groups/import", "/admin/groups/g1/attestations", "/admin/comment-templates/delete")
|
||||
.forEach { url ->
|
||||
mockMvc
|
||||
.perform(post(url).session(session))
|
||||
.andExpect(status().is3xxRedirection)
|
||||
.andExpect(redirectedUrl("/teacher"))
|
||||
}
|
||||
mockMvc
|
||||
.perform(get("/admin/groups").session(session))
|
||||
.andExpect(status().is3xxRedirection)
|
||||
.andExpect(redirectedUrl("/teacher"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,6 +205,12 @@ class AdminInterceptorTest {
|
||||
mockMvc
|
||||
.perform(get("/admin/users").session(sessionFor("ADMIN")))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(post("/admin/groups").session(sessionFor("ADMIN")))
|
||||
.andExpect(status().isOk)
|
||||
mockMvc
|
||||
.perform(get("/admin/groups/g1").session(sessionFor("ADMIN")))
|
||||
.andExpect(status().isOk)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package ru.ysu.lms.web.teacher
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.mock.web.MockServletContext
|
||||
import org.thymeleaf.context.WebContext
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine
|
||||
import org.thymeleaf.templatemode.TemplateMode
|
||||
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
|
||||
import org.thymeleaf.web.servlet.JakartaServletWebApplication
|
||||
import ru.ysu.lms.web.api.ContentArticleDto
|
||||
import ru.ysu.lms.web.api.ContentArticleNodeDto
|
||||
import ru.ysu.lms.web.api.ContentChapterNodeDto
|
||||
import ru.ysu.lms.web.api.ContentSectionNodeDto
|
||||
import ru.ysu.lms.web.api.ContentTreeNodeDto
|
||||
import ru.ysu.lms.web.api.CourseDetailDto
|
||||
import ru.ysu.lms.web.session.SessionUser
|
||||
import java.util.Locale
|
||||
|
||||
class TeacherCourseContentTemplateTest {
|
||||
private val engine =
|
||||
SpringTemplateEngine().apply {
|
||||
setTemplateResolver(
|
||||
ClassLoaderTemplateResolver().apply {
|
||||
prefix = "templates/"
|
||||
suffix = ".html"
|
||||
characterEncoding = "UTF-8"
|
||||
templateMode = TemplateMode.HTML
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private val course = CourseDetailDto(id = "c1", courseNo = 42, title = "Алгоритмы")
|
||||
|
||||
private val tree =
|
||||
ContentTreeNodeDto(
|
||||
chapters =
|
||||
listOf(
|
||||
ContentChapterNodeDto(
|
||||
id = "ch1",
|
||||
title = "Введение",
|
||||
slug = "intro",
|
||||
published = true,
|
||||
sections =
|
||||
listOf(
|
||||
ContentSectionNodeDto(
|
||||
id = "sec1",
|
||||
title = "Основы",
|
||||
slug = "basics",
|
||||
published = false,
|
||||
articles =
|
||||
listOf(
|
||||
ContentArticleNodeDto(
|
||||
id = "art1",
|
||||
title = "Первый шаг",
|
||||
slug = "first-step",
|
||||
published = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `content page renders create forms and full tree`() {
|
||||
val html = render("teacher/content")
|
||||
|
||||
assertThat(html).contains("Материалы курса")
|
||||
assertThat(html).contains("Алгоритмы")
|
||||
assertThat(html).contains("action=\"/teacher/courses/c1/content/chapters\"")
|
||||
assertThat(html).contains("name=\"title\"")
|
||||
assertThat(html).contains("Введение")
|
||||
assertThat(html).contains("Основы")
|
||||
assertThat(html).contains("Первый шаг")
|
||||
assertThat(html).contains("Разделов: 1")
|
||||
assertThat(html).contains("Статей: 1")
|
||||
assertThat(html).contains("name=\"_csrf\"")
|
||||
assertThat(html).contains("/learn/course/42")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `content page links articles to teacher edit page and publishes every level`() {
|
||||
val html = render("teacher/content")
|
||||
|
||||
assertThat(html).contains("/teacher/courses/c1/content/intro/basics/first-step")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/chapters/ch1/published")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/sections/sec1/published")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/articles/art1/published")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/chapters/ch1/rename")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/sections/sec1/rename")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/chapters/ch1/delete")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/sections/sec1/delete")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/articles/art1/delete")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `content page renders article and section creation forms`() {
|
||||
val html = render("teacher/content")
|
||||
|
||||
assertThat(html).contains("action=\"/teacher/courses/c1/content/sections\"")
|
||||
assertThat(html).contains("name=\"chapterId\"")
|
||||
assertThat(html).contains("action=\"/teacher/courses/c1/content/articles\"")
|
||||
assertThat(html).contains("name=\"sectionId\"")
|
||||
assertThat(html).contains("name=\"bodyMd\"")
|
||||
assertThat(html).contains("Добавить статью")
|
||||
assertThat(html).contains("Добавить раздел")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `article page shows editable markdown, publish and delete forms and preview`() {
|
||||
val article =
|
||||
ContentArticleDto(
|
||||
id = "art1",
|
||||
chapterSlug = "intro",
|
||||
chapterTitle = "Введение",
|
||||
sectionSlug = "basics",
|
||||
sectionTitle = "Основы",
|
||||
slug = "first-step",
|
||||
title = "Первый шаг",
|
||||
bodyMd = "# Первый шаг\n\nТекст статьи",
|
||||
published = false,
|
||||
)
|
||||
val html =
|
||||
render(
|
||||
"teacher/content-article",
|
||||
mapOf(
|
||||
"article" to article,
|
||||
"bodyHtml" to "<p>Текст статьи</p>",
|
||||
"back" to "/teacher/courses/c1/content/intro/basics/first-step",
|
||||
),
|
||||
)
|
||||
|
||||
assertThat(html).contains("name=\"bodyMd\"")
|
||||
assertThat(html).contains("rows=\"18\"")
|
||||
assertThat(html).contains("# Первый шаг")
|
||||
assertThat(html).contains("action=\"/teacher/courses/c1/content/articles/art1/save\"")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/articles/art1/delete")
|
||||
assertThat(html).contains("/teacher/courses/c1/content/articles/art1/published")
|
||||
assertThat(html).contains("<p>Текст статьи</p>")
|
||||
assertThat(html).contains("name=\"_csrf\"")
|
||||
assertThat(html).contains("/learn/course/42/intro/basics/first-step")
|
||||
}
|
||||
|
||||
private fun render(
|
||||
template: String,
|
||||
extra: Map<String, Any?> = emptyMap(),
|
||||
): String {
|
||||
val servletContext = MockServletContext()
|
||||
val application = JakartaServletWebApplication.buildApplication(servletContext)
|
||||
val exchange =
|
||||
application.buildExchange(
|
||||
MockHttpServletRequest(servletContext),
|
||||
MockHttpServletResponse(),
|
||||
)
|
||||
val context = WebContext(exchange, Locale.forLanguageTag("ru"))
|
||||
context.setVariable("csrfToken", "csrf")
|
||||
context.setVariable(
|
||||
"currentUser",
|
||||
SessionUser(
|
||||
id = "t1",
|
||||
login = "teacher",
|
||||
role = "TEACHER",
|
||||
name = "Преподаватель",
|
||||
mustChangePassword = false,
|
||||
),
|
||||
)
|
||||
context.setVariable("course", course)
|
||||
context.setVariable("tree", tree)
|
||||
extra.forEach { (name, value) -> context.setVariable(name, value) }
|
||||
return engine.process(template, context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package ru.ysu.lms.web.teacher
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.mock.web.MockServletContext
|
||||
import org.thymeleaf.context.WebContext
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine
|
||||
import org.thymeleaf.templatemode.TemplateMode
|
||||
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
|
||||
import org.thymeleaf.web.servlet.JakartaServletWebApplication
|
||||
import ru.ysu.lms.web.api.CourseDetailDto
|
||||
import ru.ysu.lms.web.api.CourseGroupDto
|
||||
import ru.ysu.lms.web.api.CourseGroupOptionDto
|
||||
import ru.ysu.lms.web.session.SessionUser
|
||||
import java.util.Locale
|
||||
|
||||
class TeacherCourseTemplateTest {
|
||||
private val engine =
|
||||
SpringTemplateEngine().apply {
|
||||
setTemplateResolver(
|
||||
ClassLoaderTemplateResolver().apply {
|
||||
prefix = "templates/"
|
||||
suffix = ".html"
|
||||
characterEncoding = "UTF-8"
|
||||
templateMode = TemplateMode.HTML
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `renders attached group names and gradebook links`() {
|
||||
val html = render()
|
||||
|
||||
assertThat(html).contains("ПИ-21БО")
|
||||
assertThat(html).contains("/admin/groups/g1")
|
||||
assertThat(html).contains("courseId=c1")
|
||||
assertThat(html).contains("Ведомость")
|
||||
assertThat(html).contains("/teacher/courses/c1/groups/detach")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `renders group attach form with available groups`() {
|
||||
val html = render()
|
||||
|
||||
assertThat(html).contains("action=\"/teacher/courses/c1/groups/attach\"")
|
||||
assertThat(html).contains("name=\"groupId\"")
|
||||
assertThat(html).contains("ПИ-22БО")
|
||||
assertThat(html).contains("Привязать")
|
||||
}
|
||||
|
||||
private fun render(): String {
|
||||
val course =
|
||||
CourseDetailDto(
|
||||
id = "c1",
|
||||
courseNo = 1,
|
||||
title = "Программирование",
|
||||
groups = listOf(CourseGroupDto(groupId = "g1", addedAt = "2026-01-01T00:00:00Z")),
|
||||
)
|
||||
val servletContext = MockServletContext()
|
||||
val application = JakartaServletWebApplication.buildApplication(servletContext)
|
||||
val exchange =
|
||||
application.buildExchange(
|
||||
MockHttpServletRequest(servletContext),
|
||||
MockHttpServletResponse(),
|
||||
)
|
||||
val context = WebContext(exchange, Locale.forLanguageTag("ru"))
|
||||
context.setVariable("csrfToken", "csrf")
|
||||
context.setVariable(
|
||||
"currentUser",
|
||||
SessionUser(
|
||||
id = "t1",
|
||||
login = "teacher",
|
||||
role = "TEACHER",
|
||||
name = "Преподаватель",
|
||||
mustChangePassword = false,
|
||||
),
|
||||
)
|
||||
context.setVariable("course", course)
|
||||
context.setVariable("groups", course.groups)
|
||||
context.setVariable("groupNames", mapOf("g1" to "ПИ-21БО"))
|
||||
context.setVariable(
|
||||
"availableGroups",
|
||||
listOf(CourseGroupOptionDto(id = "g2", name = "ПИ-22БО", status = "ACTIVE")),
|
||||
)
|
||||
context.setVariable("labs", emptyList<Any>())
|
||||
return engine.process("teacher/course", context)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user