Implement Recipe, Planning, Shopping, Pantry, and Admin domains
Outside-in TDD for all 5 remaining domains (128 tests total): - Recipe: CRUD, ingredients autocomplete/patch, tags, categories (27 tests) - Planning: week plans, slots, confirm, suggestions, variety score, cooking logs (24 tests) - Shopping: generate from plan, publish, check/add/remove items (15 tests) - Pantry: CRUD with expiry sorting (11 tests) - Admin: user management, password reset, audit logging (13 tests) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
170
backend/src/test/java/com/recipeapp/admin/AdminServiceTest.java
Normal file
170
backend/src/test/java/com/recipeapp/admin/AdminServiceTest.java
Normal file
@@ -0,0 +1,170 @@
|
||||
package com.recipeapp.admin;
|
||||
|
||||
import com.recipeapp.admin.dto.*;
|
||||
import com.recipeapp.admin.entity.AdminAuditLog;
|
||||
import com.recipeapp.auth.UserAccountRepository;
|
||||
import com.recipeapp.auth.entity.UserAccount;
|
||||
import com.recipeapp.common.ConflictException;
|
||||
import com.recipeapp.common.ResourceNotFoundException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AdminServiceTest {
|
||||
|
||||
@Mock private UserAccountRepository userAccountRepository;
|
||||
@Mock private AdminAuditLogRepository auditLogRepository;
|
||||
@Mock private AdminUserQueryRepository adminUserQueryRepository;
|
||||
@Mock private PasswordEncoder passwordEncoder;
|
||||
|
||||
private AdminServiceImpl adminService;
|
||||
|
||||
private final String adminEmail = "admin@example.com";
|
||||
private UserAccount adminUser;
|
||||
private UserAccount targetUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
adminService = new AdminServiceImpl(userAccountRepository, auditLogRepository, adminUserQueryRepository, passwordEncoder);
|
||||
adminUser = new UserAccount("admin@example.com", "Admin", "hashed");
|
||||
setId(adminUser, UserAccount.class, UUID.randomUUID());
|
||||
targetUser = new UserAccount("jane@example.com", "Jane", "hashed");
|
||||
setId(targetUser, UserAccount.class, UUID.randomUUID());
|
||||
}
|
||||
|
||||
private <T> void setId(T entity, Class<T> clazz, UUID id) {
|
||||
try {
|
||||
var field = clazz.getDeclaredField("id");
|
||||
field.setAccessible(true);
|
||||
field.set(entity, id);
|
||||
} catch (Exception e) { throw new RuntimeException(e); }
|
||||
}
|
||||
|
||||
@Test
|
||||
void listUsers_returnsPaginatedResults() {
|
||||
when(adminUserQueryRepository.findUsersFiltered(isNull(), isNull(), any(Pageable.class)))
|
||||
.thenReturn(List.of(targetUser));
|
||||
when(adminUserQueryRepository.countUsersFiltered(isNull(), isNull()))
|
||||
.thenReturn(1L);
|
||||
|
||||
var result = adminService.listUsers(null, null, 50, 0);
|
||||
|
||||
assertEquals(1, result.users().size());
|
||||
assertEquals(1L, result.total());
|
||||
assertEquals("jane@example.com", result.users().getFirst().email());
|
||||
}
|
||||
|
||||
@Test
|
||||
void listUsers_withSearchFilter() {
|
||||
when(adminUserQueryRepository.findUsersFiltered(eq("jane"), isNull(), any(Pageable.class)))
|
||||
.thenReturn(List.of(targetUser));
|
||||
when(adminUserQueryRepository.countUsersFiltered(eq("jane"), isNull()))
|
||||
.thenReturn(1L);
|
||||
|
||||
var result = adminService.listUsers("jane", null, 50, 0);
|
||||
|
||||
assertEquals(1, result.users().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createUser_success() {
|
||||
when(userAccountRepository.existsByEmailIgnoreCase("new@example.com")).thenReturn(false);
|
||||
when(userAccountRepository.findByEmailIgnoreCase(adminEmail)).thenReturn(Optional.of(adminUser));
|
||||
when(passwordEncoder.encode("TempPass1!")).thenReturn("encoded");
|
||||
when(userAccountRepository.save(any(UserAccount.class))).thenAnswer(inv -> {
|
||||
var u = inv.getArgument(0, UserAccount.class);
|
||||
setId(u, UserAccount.class, UUID.randomUUID());
|
||||
return u;
|
||||
});
|
||||
when(auditLogRepository.save(any(AdminAuditLog.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
var result = adminService.createUser(
|
||||
new CreateUserRequest("new@example.com", "New User", "TempPass1!", "user"), adminEmail);
|
||||
|
||||
assertEquals("new@example.com", result.email());
|
||||
assertEquals("New User", result.displayName());
|
||||
verify(auditLogRepository).save(argThat(log -> "create_account".equals(log.getAction())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createUser_duplicateEmail_throwsConflict() {
|
||||
when(userAccountRepository.existsByEmailIgnoreCase("jane@example.com")).thenReturn(true);
|
||||
|
||||
assertThrows(ConflictException.class, () ->
|
||||
adminService.createUser(
|
||||
new CreateUserRequest("jane@example.com", "Jane", "TempPass1!", "user"), adminEmail));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateUser_success() {
|
||||
when(userAccountRepository.findByEmailIgnoreCase(adminEmail)).thenReturn(Optional.of(adminUser));
|
||||
when(userAccountRepository.findById(targetUser.getId())).thenReturn(Optional.of(targetUser));
|
||||
when(userAccountRepository.save(any(UserAccount.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
when(auditLogRepository.save(any(AdminAuditLog.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
var result = adminService.updateUser(targetUser.getId(),
|
||||
new UpdateUserRequest("Updated Jane", null, null, null), adminEmail);
|
||||
|
||||
assertEquals("Updated Jane", result.displayName());
|
||||
verify(auditLogRepository).save(argThat(log -> "update_account".equals(log.getAction())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateUser_deactivate() {
|
||||
when(userAccountRepository.findByEmailIgnoreCase(adminEmail)).thenReturn(Optional.of(adminUser));
|
||||
when(userAccountRepository.findById(targetUser.getId())).thenReturn(Optional.of(targetUser));
|
||||
when(userAccountRepository.save(any(UserAccount.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
when(auditLogRepository.save(any(AdminAuditLog.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
var result = adminService.updateUser(targetUser.getId(),
|
||||
new UpdateUserRequest(null, null, null, false), adminEmail);
|
||||
|
||||
assertFalse(result.isActive());
|
||||
verify(auditLogRepository).save(argThat(log -> "deactivate_account".equals(log.getAction())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetPassword_success() {
|
||||
when(userAccountRepository.findByEmailIgnoreCase(adminEmail)).thenReturn(Optional.of(adminUser));
|
||||
when(userAccountRepository.findById(targetUser.getId())).thenReturn(Optional.of(targetUser));
|
||||
when(passwordEncoder.encode("NewTemp123!")).thenReturn("encoded");
|
||||
when(userAccountRepository.save(any(UserAccount.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
when(auditLogRepository.save(any(AdminAuditLog.class))).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
var result = adminService.resetPassword(targetUser.getId(),
|
||||
new ResetPasswordRequest("NewTemp123!", "Forgot password"), adminEmail);
|
||||
|
||||
assertEquals("Password reset successfully", result.message());
|
||||
assertTrue(result.mustChangePassword());
|
||||
verify(auditLogRepository).save(argThat(log -> "reset_password".equals(log.getAction())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listAuditLog_returnsLogs() {
|
||||
var log = new AdminAuditLog(adminUser.getId(), targetUser.getId(), "create_account", Map.of(), null);
|
||||
setId(log, AdminAuditLog.class, UUID.randomUUID());
|
||||
when(auditLogRepository.findAllByOrderByPerformedAtDesc(any(Pageable.class)))
|
||||
.thenReturn(List.of(log));
|
||||
when(userAccountRepository.findById(adminUser.getId())).thenReturn(Optional.of(adminUser));
|
||||
when(userAccountRepository.findById(targetUser.getId())).thenReturn(Optional.of(targetUser));
|
||||
|
||||
var result = adminService.listAuditLog(null, 50, 0);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("create_account", result.getFirst().action());
|
||||
assertEquals("admin@example.com", result.getFirst().adminEmail());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user