first runnable version

This commit is contained in:
yoyuzh
2026-03-14 12:28:46 +08:00
parent 8db2fa2aab
commit 6cff15f8dc
35 changed files with 2118 additions and 256 deletions

View File

@@ -4,6 +4,7 @@ import com.yoyuzh.auth.dto.AuthResponse;
import com.yoyuzh.auth.dto.LoginRequest;
import com.yoyuzh.auth.dto.RegisterRequest;
import com.yoyuzh.common.BusinessException;
import com.yoyuzh.files.FileService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
@@ -38,6 +39,9 @@ class AuthServiceTest {
@Mock
private JwtTokenProvider jwtTokenProvider;
@Mock
private FileService fileService;
@InjectMocks
private AuthService authService;
@@ -60,6 +64,7 @@ class AuthServiceTest {
assertThat(response.token()).isEqualTo("jwt-token");
assertThat(response.user().username()).isEqualTo("alice");
verify(passwordEncoder).encode("plain-password");
verify(fileService).ensureDefaultDirectories(any(User.class));
}
@Test
@@ -90,6 +95,7 @@ class AuthServiceTest {
new UsernamePasswordAuthenticationToken("alice", "plain-password"));
assertThat(response.token()).isEqualTo("jwt-token");
assertThat(response.user().email()).isEqualTo("alice@example.com");
verify(fileService).ensureDefaultDirectories(user);
}
@Test
@@ -102,4 +108,22 @@ class AuthServiceTest {
.isInstanceOf(BusinessException.class)
.hasMessageContaining("用户名或密码错误");
}
@Test
void shouldCreateDefaultDirectoriesForDevLoginUser() {
when(userRepository.findByUsername("demo")).thenReturn(Optional.empty());
when(passwordEncoder.encode("1")).thenReturn("encoded-password");
when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(9L);
user.setCreatedAt(LocalDateTime.now());
return user;
});
when(jwtTokenProvider.generateToken(9L, "demo")).thenReturn("jwt-token");
AuthResponse response = authService.devLogin("demo");
assertThat(response.user().username()).isEqualTo("demo");
verify(fileService).ensureDefaultDirectories(any(User.class));
}
}

View File

@@ -0,0 +1,79 @@
package com.yoyuzh.auth;
import com.yoyuzh.config.FileStorageProperties;
import com.yoyuzh.files.FileService;
import com.yoyuzh.files.StoredFileRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DevBootstrapDataInitializerTest {
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@Mock
private FileService fileService;
@Mock
private StoredFileRepository storedFileRepository;
@Mock
private FileStorageProperties fileStorageProperties;
@InjectMocks
private DevBootstrapDataInitializer initializer;
@TempDir
Path tempDir;
@Test
void shouldCreateInitialDevUsersWhenMissing() throws Exception {
when(userRepository.findByUsername("portal-demo")).thenReturn(Optional.empty());
when(userRepository.findByUsername("portal-study")).thenReturn(Optional.empty());
when(userRepository.findByUsername("portal-design")).thenReturn(Optional.empty());
when(passwordEncoder.encode("portal123456")).thenReturn("encoded-demo-password");
when(passwordEncoder.encode("study123456")).thenReturn("encoded-study-password");
when(passwordEncoder.encode("design123456")).thenReturn("encoded-design-password");
when(storedFileRepository.existsByUserIdAndPathAndFilename(anyLong(), anyString(), anyString())).thenReturn(false);
when(fileStorageProperties.getRootDir()).thenReturn(tempDir.toString());
List<User> savedUsers = new ArrayList<>();
when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId((long) (savedUsers.size() + 1));
user.setCreatedAt(LocalDateTime.now());
savedUsers.add(user);
return user;
});
initializer.run();
verify(userRepository, times(3)).save(any(User.class));
verify(fileService, times(3)).ensureDefaultDirectories(any(User.class));
org.assertj.core.api.Assertions.assertThat(savedUsers)
.extracting(User::getUsername)
.containsExactly("portal-demo", "portal-study", "portal-design");
verify(storedFileRepository, times(9)).save(any());
}
}

View File

@@ -0,0 +1,78 @@
package com.yoyuzh.cqu;
import com.yoyuzh.PortalBackendApplication;
import com.yoyuzh.auth.User;
import com.yoyuzh.auth.UserRepository;
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.mock.mockito.MockBean;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@SpringBootTest(
classes = PortalBackendApplication.class,
properties = {
"spring.datasource.url=jdbc:h2:mem:cqu_tx_test;MODE=MySQL;DB_CLOSE_DELAY=-1",
"spring.datasource.driver-class-name=org.h2.Driver",
"spring.datasource.username=sa",
"spring.datasource.password=",
"spring.jpa.hibernate.ddl-auto=create-drop",
"app.cqu.require-login=true",
"app.cqu.mock-enabled=false"
}
)
class CquDataServiceTransactionTest {
@Autowired
private CquDataService cquDataService;
@Autowired
private UserRepository userRepository;
@Autowired
private GradeRepository gradeRepository;
@MockBean
private CquApiClient cquApiClient;
@Test
void shouldPersistGradesInsideTransactionForLoggedInUser() {
User user = new User();
user.setUsername("portal-demo");
user.setEmail("portal-demo@example.com");
user.setPasswordHash("encoded");
user = userRepository.save(user);
Grade existing = new Grade();
existing.setUser(user);
existing.setCourseName("Old Java");
existing.setGrade(60D);
existing.setSemester("2025-spring");
existing.setStudentId("2023123456");
gradeRepository.save(existing);
when(cquApiClient.fetchGrades("2025-spring", "2023123456")).thenReturn(List.of(
Map.of(
"courseName", "Java",
"grade", 95,
"semester", "2025-spring"
)
));
List<GradeResponse> response = cquDataService.getGrades(user, "2025-spring", "2023123456");
assertThat(response).hasSize(1);
assertThat(response.get(0).courseName()).isEqualTo("Java");
assertThat(response.get(0).grade()).isEqualTo(95D);
assertThat(gradeRepository.findByUserIdAndStudentIdOrderBySemesterAscGradeDesc(user.getId(), "2023123456"))
.hasSize(1)
.first()
.extracting(Grade::getCourseName)
.isEqualTo("Java");
}
}

View File

@@ -26,4 +26,17 @@ class CquMockDataFactoryTest {
assertThat(result.get(0)).containsEntry("studentId", "20230001");
assertThat(result.get(0)).containsKey("grade");
}
@Test
void shouldReturnDifferentMockDataForDifferentStudents() {
List<Map<String, Object>> firstSchedule = CquMockDataFactory.createSchedule("2025-2026-1", "2023123456");
List<Map<String, Object>> secondSchedule = CquMockDataFactory.createSchedule("2025-2026-1", "2022456789");
List<Map<String, Object>> firstGrades = CquMockDataFactory.createGrades("2025-2026-1", "2023123456");
List<Map<String, Object>> secondGrades = CquMockDataFactory.createGrades("2025-2026-1", "2022456789");
assertThat(firstSchedule).extracting(item -> item.get("courseName"))
.isNotEqualTo(secondSchedule.stream().map(item -> item.get("courseName")).toList());
assertThat(firstGrades).extracting(item -> item.get("grade"))
.isNotEqualTo(secondGrades.stream().map(item -> item.get("grade")).toList());
}
}

View File

@@ -7,7 +7,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.PageImpl;
@@ -22,6 +21,8 @@ import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@@ -89,6 +90,25 @@ class FileServiceTest {
assertThat(result.items().get(0).filename()).isEqualTo("notes.txt");
}
@Test
void shouldCreateDefaultDirectoriesForUserWorkspace() {
User user = createUser(7L);
when(storedFileRepository.existsByUserIdAndPathAndFilename(7L, "/", "下载")).thenReturn(false);
when(storedFileRepository.existsByUserIdAndPathAndFilename(7L, "/", "文档")).thenReturn(false);
when(storedFileRepository.existsByUserIdAndPathAndFilename(7L, "/", "图片")).thenReturn(false);
when(storedFileRepository.save(any(StoredFile.class))).thenAnswer(invocation -> invocation.getArgument(0));
fileService.ensureDefaultDirectories(user);
assertThat(tempDir.resolve("7/下载")).exists();
assertThat(tempDir.resolve("7/文档")).exists();
assertThat(tempDir.resolve("7/图片")).exists();
verify(storedFileRepository).existsByUserIdAndPathAndFilename(7L, "/", "下载");
verify(storedFileRepository).existsByUserIdAndPathAndFilename(7L, "/", "文档");
verify(storedFileRepository).existsByUserIdAndPathAndFilename(7L, "/", "图片");
verify(storedFileRepository, times(3)).save(any(StoredFile.class));
}
private User createUser(Long id) {
User user = new User();
user.setId(id);