Merge commit '8e786ba37ba7fcff09cf88d5951acb21fbb36ea8' into work/reports-ui

This commit is contained in:
sechmachine
2026-08-15 00:47:39 +07:00
7 changed files with 397 additions and 3 deletions
@@ -0,0 +1,173 @@
package com.lab.labtimesheet.feature.account.controller;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import java.util.ArrayList;
import java.util.List;
import com.lab.labtimesheet.config.TestcontainersConfiguration;
import com.lab.labtimesheet.feature.account.model.AccountStatus;
import com.lab.labtimesheet.feature.account.model.GlobalRole;
import com.lab.labtimesheet.feature.account.service.AccountService;
import com.lab.labtimesheet.feature.account.service.BootstrapService;
import com.lab.labtimesheet.feature.integration.model.SecurityMode;
import com.lab.labtimesheet.feature.integration.model.dto.SmtpConnection;
import com.lab.labtimesheet.feature.integration.model.dto.SmtpDraft;
import com.lab.labtimesheet.feature.integration.service.SmtpConfigurationService;
import com.lab.labtimesheet.feature.integration.service.SmtpProbe;
import jakarta.servlet.http.HttpSession;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
@Import({TestcontainersConfiguration.class, AccountWebIntegrationTest.MailProbeConfiguration.class})
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
class AccountWebIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private BootstrapService bootstrap;
@Autowired
private AccountService accounts;
@Autowired
private SmtpConfigurationService smtp;
@Autowired
private RecordingSmtpProbe mail;
@BeforeEach
void initializeAdminAndSmtp() {
bootstrap.bootstrap("admin@example.com", "Admin", "correct horse battery staple");
long adminId = accounts.requireActiveAdminId("admin@example.com");
long draftId = smtp.saveDraft(adminId, new SmtpDraft(
"mailpit", 1025, SecurityMode.NONE, null, null, "admin@example.com", "Lab Timesheet"));
smtp.testDraft(draftId, adminId, "admin@example.com");
smtp.activate(draftId, adminId);
mail.messages.clear();
}
@Test
void adminCreatesMentorAndInternThenMentorActivatesAuthenticatesAndLogsOut() throws Exception {
mockMvc.perform(get("/admin/accounts/new").with(user("admin@example.com").roles("ADMIN")))
.andExpect(status().isOk())
.andExpect(view().name("accounts/new"))
.andExpect(content().string(org.hamcrest.Matchers.containsString("Internship start")));
mockMvc.perform(post("/admin/accounts")
.with(user("admin@example.com").roles("ADMIN"))
.with(csrf())
.param("email", " MENTOR@EXAMPLE.COM ")
.param("displayName", "Mentor One")
.param("role", "MENTOR")
.param("studentCode", "")
.param("internshipStart", "")
.param("internshipEnd", ""))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/admin/accounts/new?created"));
mockMvc.perform(post("/admin/accounts")
.with(user("admin@example.com").roles("ADMIN"))
.with(csrf())
.param("email", "intern@example.com")
.param("displayName", "Intern One")
.param("role", "INTERN")
.param("studentCode", "STU-001")
.param("internshipStart", "2026-08-01")
.param("internshipEnd", "2026-12-31"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/admin/accounts/new?created"));
var pendingMentor = accounts.requireIdentityByEmail("mentor@example.com");
assertThat(pendingMentor.role()).isEqualTo(GlobalRole.MENTOR);
assertThat(pendingMentor.status()).isEqualTo(AccountStatus.PENDING_ACTIVATION);
assertThat(accounts.requireIdentityByEmail("intern@example.com").role()).isEqualTo(GlobalRole.INTERN);
String rawToken = mail.activationTokenFor("mentor@example.com");
mockMvc.perform(get("/activate").param("token", rawToken))
.andExpect(status().isOk())
.andExpect(view().name("accounts/activate"));
mockMvc.perform(post("/activate")
.with(csrf())
.param("token", rawToken)
.param("password", "new secure mentor password")
.param("confirmPassword", "new secure mentor password"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/login?activated"));
var login = mockMvc.perform(post("/login")
.with(csrf())
.param("username", " MENTOR@EXAMPLE.COM ")
.param("password", "new secure mentor password"))
.andExpect(status().is3xxRedirection())
.andExpect(authenticated().withUsername("mentor@example.com"))
.andReturn();
HttpSession session = login.getRequest().getSession(false);
assertThat(session).isNotNull();
mockMvc.perform(get("/admin/accounts/new").session((org.springframework.mock.web.MockHttpSession) session))
.andExpect(status().isForbidden());
mockMvc.perform(post("/logout")
.session((org.springframework.mock.web.MockHttpSession) session)
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/login?logout"))
.andExpect(unauthenticated());
}
@TestConfiguration(proxyBeanMethods = false)
static class MailProbeConfiguration {
@Bean
@Primary
RecordingSmtpProbe recordingSmtpProbe() {
return new RecordingSmtpProbe();
}
}
static final class RecordingSmtpProbe implements SmtpProbe {
private final List<Message> messages = new ArrayList<>();
@Override
public void send(SmtpConnection connection, String recipient, String subject, String body) {
messages.add(new Message(recipient, body));
}
String activationTokenFor(String recipient) {
String body = messages.stream()
.filter(message -> message.recipient().equals(recipient))
.findFirst()
.orElseThrow()
.body();
int tokenStart = body.indexOf("token=");
assertThat(tokenStart).isGreaterThanOrEqualTo(0);
return body.substring(tokenStart + "token=".length()).trim();
}
}
record Message(String recipient, String body) {
}
}