Fix local dev config and ALOTTTT OFF FLUTTER, im tired boss..

This commit is contained in:
Wowieee4
2026-05-26 21:09:13 +07:00
parent b8ad8df993
commit a629357e8c
72 changed files with 2566 additions and 530 deletions
@@ -98,6 +98,13 @@ public class GuardianController {
"SOS diakui"));
}
@PutMapping("/sos/{id}/resolve")
public ResponseEntity<ApiResponse<SosEventResponse>> resolveSos(@PathVariable Long id) {
return ResponseEntity.ok(ApiResponse.ok(
sosService.resolveSos(SecurityHelper.getCurrentUserId(), id),
"SOS ditangani"));
}
@GetMapping("/ai-config")
public ResponseEntity<ApiResponse<AiConfigResponse>> getAiConfig() {
// Guardian lihat config user yang dipair
@@ -117,7 +124,7 @@ public class GuardianController {
@GetMapping("/voice-commands")
public ResponseEntity<ApiResponse<?>> getVoiceCommands() {
return ResponseEntity.ok(ApiResponse.ok(
voiceCommandService.getAll(SecurityHelper.getCurrentUserId()),
voiceCommandService.getAllForGuardian(SecurityHelper.getCurrentUserId()),
"Voice commands"));
}
@@ -132,10 +139,18 @@ public class GuardianController {
@GetMapping("/shortcuts")
public ResponseEntity<ApiResponse<?>> getShortcuts() {
return ResponseEntity.ok(ApiResponse.ok(
hardwareShortcutService.getAll(SecurityHelper.getCurrentUserId()),
hardwareShortcutService.getAllForGuardian(SecurityHelper.getCurrentUserId()),
"Hardware shortcuts"));
}
@PutMapping("/shortcuts")
public ResponseEntity<ApiResponse<HardwareShortcutResponse>> updateShortcut(
@RequestBody HardwareShortcutUpdateRequest req) {
return ResponseEntity.ok(ApiResponse.ok(
hardwareShortcutService.updateByGuardian(SecurityHelper.getCurrentUserId(), req),
"Hardware shortcut diperbarui"));
}
@GetMapping("/geofence")
public ResponseEntity<ApiResponse<GeofenceResponse>> getGeofence() {
return ResponseEntity.ok(ApiResponse.ok(
@@ -3,8 +3,6 @@ package com.walkguide.controller;
import com.walkguide.dto.ApiResponse;
import com.walkguide.dto.request.*;
import com.walkguide.dto.response.*;
import com.walkguide.enums.ActivityLogType;
import com.walkguide.repository.UserRepository;
import com.walkguide.security.SecurityHelper;
import com.walkguide.service.*;
import lombok.RequiredArgsConstructor;
@@ -29,21 +27,13 @@ public class UserController {
private final AiConfigService aiConfigService;
private final VoiceCommandService voiceCommandService;
private final HardwareShortcutService hardwareShortcutService;
private final UserRepository userRepository;
private final UserService userService;
@GetMapping("/profile")
public ResponseEntity<ApiResponse<?>> getProfile() {
Long userId = SecurityHelper.getCurrentUserId();
var user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User tidak ditemukan"));
var profile = java.util.Map.of(
"id", user.getId(),
"email", user.getEmail(),
"displayName", user.getDisplayName() != null ? user.getDisplayName() : "",
"role", user.getRole(),
"uniqueUserId", user.getUniqueUserId() != null ? user.getUniqueUserId() : ""
);
return ResponseEntity.ok(ApiResponse.ok(profile, "Profil user"));
return ResponseEntity.ok(ApiResponse.ok(
userService.getProfile(SecurityHelper.getCurrentUserId()),
"Profil user"));
}
@GetMapping("/settings")
@@ -163,19 +153,13 @@ public class UserController {
@PostMapping("/walkguide/start")
public ResponseEntity<ApiResponse<Void>> walkGuideStart() {
Long userId = SecurityHelper.getCurrentUserId();
userRepository.findById(userId).ifPresent(u ->
activityLogService.createLog(u, ActivityLogType.WALKGUIDE_START,
"WalkGuide dimulai", null));
userService.logWalkGuideStart(SecurityHelper.getCurrentUserId());
return ResponseEntity.ok(ApiResponse.ok(null, "WalkGuide dimulai"));
}
@PostMapping("/walkguide/stop")
public ResponseEntity<ApiResponse<Void>> walkGuideStop() {
Long userId = SecurityHelper.getCurrentUserId();
userRepository.findById(userId).ifPresent(u ->
activityLogService.createLog(u, ActivityLogType.WALKGUIDE_STOP,
"WalkGuide dihentikan", null));
userService.logWalkGuideStop(SecurityHelper.getCurrentUserId());
return ResponseEntity.ok(ApiResponse.ok(null, "WalkGuide dihentikan"));
}
}
@@ -2,9 +2,12 @@ package com.walkguide.service;
import com.walkguide.dto.request.HardwareShortcutUpdateRequest;
import com.walkguide.dto.response.HardwareShortcutResponse;
import com.walkguide.enums.PairingStatus;
import com.walkguide.enums.HardwareShortcutKey;
import com.walkguide.exception.PairingException;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.HardwareShortcutRepository;
import com.walkguide.repository.PairingRelationRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -15,6 +18,7 @@ import java.util.stream.Collectors;
public class HardwareShortcutService {
private final HardwareShortcutRepository hardwareShortcutRepository;
private final PairingRelationRepository pairingRelationRepository;
public List<HardwareShortcutResponse> getAll(Long userId) {
return hardwareShortcutRepository.findByUserId(userId).stream()
@@ -25,6 +29,13 @@ public class HardwareShortcutService {
.collect(Collectors.toList());
}
public List<HardwareShortcutResponse> getAllForGuardian(Long guardianId) {
var pairing = pairingRelationRepository
.findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)
.orElseThrow(() -> new PairingException("Tidak ada user yang dipair"));
return getAll(pairing.getUser().getId());
}
// Bisa dipanggil dari HP User langsung (capture button) atau dari Guardian
public HardwareShortcutResponse update(Long userId, HardwareShortcutUpdateRequest req) {
HardwareShortcutKey key = HardwareShortcutKey.valueOf(req.getShortcutKey());
@@ -43,4 +54,11 @@ public class HardwareShortcutService {
.buttonName(shortcut.getButtonName()).buttonCode(shortcut.getButtonCode())
.enabled(shortcut.getEnabled()).build();
}
public HardwareShortcutResponse updateByGuardian(Long guardianId, HardwareShortcutUpdateRequest req) {
var pairing = pairingRelationRepository
.findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)
.orElseThrow(() -> new PairingException("Tidak ada user yang dipair"));
return update(pairing.getUser().getId(), req);
}
}
@@ -87,6 +87,7 @@ public class SosService {
public SosEventResponse acknowledgeSos(Long guardianId, Long sosId) {
SosEvent sos = sosEventRepository.findById(sosId)
.orElseThrow(() -> new ResourceNotFoundException("SOS event tidak ditemukan"));
assertGuardianOwnsSos(guardianId, sos.getUserId());
sos.setStatus(SosStatus.ACKNOWLEDGED);
sos.setAcknowledgedAt(LocalDateTime.now());
@@ -111,6 +112,33 @@ public class SosService {
return toResponse(sos);
}
@Transactional
public SosEventResponse resolveSos(Long guardianId, Long sosId) {
SosEvent sos = sosEventRepository.findById(sosId)
.orElseThrow(() -> new ResourceNotFoundException("SOS event tidak ditemukan"));
assertGuardianOwnsSos(guardianId, sos.getUserId());
if (sos.getAcknowledgedAt() == null) {
sos.setAcknowledgedAt(LocalDateTime.now());
}
sos.setStatus(SosStatus.RESOLVED);
sosEventRepository.save(sos);
User user = userRepository.findById(sos.getUserId())
.orElseThrow(() -> new ResourceNotFoundException("User tidak ditemukan"));
activityLogService.createLog(user, ActivityLogType.SOS_ACKNOWLEDGED,
"Guardian menandai SOS sudah ditangani", null);
pairingRelationRepository.findByUser_IdAndStatus(sos.getUserId(), PairingStatus.ACTIVE)
.ifPresent(pairing -> fcmService.sendToToken(
pairing.getUser().getFcmToken(),
"SOS Sudah Ditangani",
"Guardian kamu sudah menandai SOS sebagai selesai.",
Map.of("type", "SOS_RESOLVED")));
return toResponse(sos);
}
public Page<SosEventResponse> getSosEvents(Long userId, Pageable pageable) {
return sosEventRepository.findByUserIdOrderByCreatedAtDesc(userId, pageable)
.map(this::toResponse);
@@ -125,6 +153,15 @@ public class SosService {
.map(this::toResponse);
}
private void assertGuardianOwnsSos(Long guardianId, Long userId) {
var pairing = pairingRelationRepository
.findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)
.orElseThrow(() -> new ResourceNotFoundException("Tidak ada user yang dipair"));
if (!pairing.getUser().getId().equals(userId)) {
throw new ResourceNotFoundException("SOS event tidak ditemukan untuk guardian ini");
}
}
private SosEventResponse toResponse(SosEvent s) {
return SosEventResponse.builder()
.id(s.getId())
@@ -0,0 +1,40 @@
package com.walkguide.service;
import com.walkguide.enums.ActivityLogType;
import com.walkguide.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ActivityLogService activityLogService;
public Map<String, Object> getProfile(Long userId) {
var user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User tidak ditemukan"));
return Map.of(
"id", user.getId(),
"email", user.getEmail(),
"displayName", user.getDisplayName() != null ? user.getDisplayName() : "",
"role", user.getRole(),
"uniqueUserId", user.getUniqueUserId() != null ? user.getUniqueUserId() : ""
);
}
public void logWalkGuideStart(Long userId) {
userRepository.findById(userId).ifPresent(user ->
activityLogService.createLog(user, ActivityLogType.WALKGUIDE_START,
"WalkGuide dimulai", null));
}
public void logWalkGuideStop(Long userId) {
userRepository.findById(userId).ifPresent(user ->
activityLogService.createLog(user, ActivityLogType.WALKGUIDE_STOP,
"WalkGuide dihentikan", null));
}
}
@@ -52,6 +52,13 @@ public class VoiceCommandService {
.collect(Collectors.toList());
}
public List<VoiceCommandResponse> getAllForGuardian(Long guardianId) {
var pairing = pairingRelationRepository
.findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)
.orElseThrow(() -> new PairingException("Tidak ada user yang dipair"));
return getAll(pairing.getUser().getId());
}
public VoiceCommandResponse updateByGuardian(Long guardianId, VoiceCommandUpdateRequest req) {
var pairing = pairingRelationRepository
.findByGuardian_IdAndStatus(guardianId, PairingStatus.ACTIVE)
@@ -6,9 +6,9 @@
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
url: ${DB_URL:jdbc:postgresql://202.46.28.160:2002/uas_5803024001}
username: ${DB_USERNAME:5803024001}
password: ${DB_PASSWORD:pw5803024001}
jpa:
show-sql: true
@@ -17,7 +17,7 @@ spring:
format_sql: true
jwt:
secret: ${JWT_SECRET}
secret: ${JWT_SECRET:d2Fsa2d1aWRlLWRldi1qd3Qtc2VjcmV0LWtleS0zMi1ieXRlcy1taW5pbXVt}
expiration: ${JWT_EXPIRATION:86400000}
agora:
@@ -2,9 +2,9 @@
server.port=${SERVER_PORT:8080}
# ===== POSTGRESQL CONNECTION =====
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.datasource.url=${DB_URL:jdbc:postgresql://202.46.28.160:2002/uas_5803024001}
spring.datasource.username=${DB_USERNAME:5803024001}
spring.datasource.password=${DB_PASSWORD:pw5803024001}
spring.datasource.driver-class-name=org.postgresql.Driver
# ===== JPA / HIBERNATE =====
@@ -19,7 +19,7 @@ spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=true
# ===== JWT =====
jwt.secret=${JWT_SECRET}
jwt.secret=${JWT_SECRET:d2Fsa2d1aWRlLWRldi1qd3Qtc2VjcmV0LWtleS0zMi1ieXRlcy1taW5pbXVt}
jwt.expiration=${JWT_EXPIRATION:86400000}
# ===== SWAGGER =====
@@ -38,4 +38,4 @@ logging.level.com.walkguide=DEBUG
logging.level.org.springframework.messaging=INFO
logging.level.org.springframework.web.socket=INFO
spring.profiles.active=dev
spring.profiles.active=dev
@@ -273,6 +273,15 @@ paths:
schema: { type: integer, format: int64 }
responses:
"200": { description: SOS acknowledged }
/guardian/sos/{id}/resolve:
put:
parameters:
- in: path
name: id
required: true
schema: { type: integer, format: int64 }
responses:
"200": { description: SOS handled and resolved }
/guardian/ai-config:
get:
responses:
@@ -291,6 +300,9 @@ paths:
get:
responses:
"200": { description: Paired user shortcuts }
put:
responses:
"200": { description: Paired user shortcut updated }
/guardian/geofence:
get:
responses:
@@ -8,10 +8,10 @@ import org.springframework.boot.test.mock.mockito.MockBean;
@SpringBootTest(properties = {
"spring.datasource.url=jdbc:postgresql://localhost:5432/walkguide_test",
"spring.datasource.username=test",
"spring.datasource.password=test",
"spring.datasource.password=${TEST_DB_PASSWORD}",
"spring.flyway.enabled=false",
"spring.jpa.hibernate.ddl-auto=none",
"jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970"
"jwt.secret=${TEST_JWT_SECRET}"
})
class DemoApplicationTests {
@@ -211,6 +211,24 @@ class GuardianControllerTest {
}
}
@Test
@DisplayName("PUT /api/v1/guardian/sos/{id}/resolve - harus tandai SOS ditangani")
void resolveSos_shouldReturn200() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
SosEventResponse resp = new SosEventResponse();
when(sosService.resolveSos(2L, 10L)).thenReturn(resp);
mockMvc.perform(put("/api/v1/guardian/sos/10/resolve")
.with(csrf()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("SOS ditangani"));
verify(sosService).resolveSos(2L, 10L);
}
}
// ===== AI CONFIG =====
@Test
@@ -254,7 +272,7 @@ class GuardianControllerTest {
void getVoiceCommands_shouldReturn200() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
when(voiceCommandService.getAll(2L)).thenReturn(List.of());
when(voiceCommandService.getAllForGuardian(2L)).thenReturn(List.of());
mockMvc.perform(get("/api/v1/guardian/voice-commands"))
.andExpect(status().isOk())
@@ -288,7 +306,7 @@ class GuardianControllerTest {
void getShortcuts_shouldReturn200() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
when(hardwareShortcutService.getAll(2L)).thenReturn(List.of());
when(hardwareShortcutService.getAllForGuardian(2L)).thenReturn(List.of());
mockMvc.perform(get("/api/v1/guardian/shortcuts"))
.andExpect(status().isOk())
@@ -296,6 +314,27 @@ class GuardianControllerTest {
}
}
@Test
@DisplayName("PUT /api/v1/guardian/shortcuts - harus update shortcut user yang dipair")
void updateShortcut_shouldReturn200() throws Exception {
try (MockedStatic<SecurityHelper> sh = mockStatic(SecurityHelper.class)) {
sh.when(SecurityHelper::getCurrentUserId).thenReturn(2L);
HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest();
HardwareShortcutResponse resp = new HardwareShortcutResponse();
when(hardwareShortcutService.updateByGuardian(eq(2L), any())).thenReturn(resp);
mockMvc.perform(put("/api/v1/guardian/shortcuts")
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Hardware shortcut diperbarui"));
verify(hardwareShortcutService).updateByGuardian(eq(2L), any(HardwareShortcutUpdateRequest.class));
}
}
// ===== GEOFENCE =====
@Test
@@ -1,7 +1,5 @@
package com.walkguide.integration;
import org.junit.jupiter.api.Disabled;
import com.fasterxml.jackson.databind.JsonNode;
import com.walkguide.dto.request.LoginRequest;
import com.walkguide.dto.request.RefreshTokenRequest;
@@ -25,8 +23,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@DisplayName("Integration Test — Auth Flow (Testcontainers)")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Disabled("Requires Docker/Testcontainers")
class AuthIntegrationTest extends AbstractIntegrationTest {
class AuthIntegrationTest extends AbstractIntegrationTest {
private static final String USER_EMAIL = "integ_user@walkguide.test";
private static final String USER_PASS = "password123";
@@ -294,4 +291,4 @@ class AuthIntegrationTest extends AbstractIntegrationTest {
assertThat(result.getResponse().getStatus())
.isBetween(200, 399));
}
}
}
@@ -1,7 +1,5 @@
package com.walkguide.integration;
import org.junit.jupiter.api.Disabled;
import com.fasterxml.jackson.databind.JsonNode;
import com.walkguide.dto.request.InviteUserRequest;
import com.walkguide.dto.request.PairingResponseRequest;
@@ -26,8 +24,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@DisplayName("Integration Test — Pairing Flow (Testcontainers)")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Disabled("Requires Docker/Testcontainers")
class PairingIntegrationTest extends AbstractIntegrationTest {
class PairingIntegrationTest extends AbstractIntegrationTest {
private static final String USER_EMAIL = "pairing_user@walkguide.test";
private static final String USER_PASS = "userpass123";
@@ -286,4 +283,4 @@ class PairingIntegrationTest extends AbstractIntegrationTest {
mockMvc.perform(get("/api/v1/shared/pairing/status"))
.andExpect(status().isForbidden());
}
}
}
@@ -1,7 +1,5 @@
package com.walkguide.integration;
import org.junit.jupiter.api.Disabled;
import com.fasterxml.jackson.databind.JsonNode;
import com.walkguide.dto.request.*;
import org.junit.jupiter.api.*;
@@ -28,8 +26,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@DisplayName("Integration Test — User Core Features (Testcontainers)")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Disabled("Requires Docker/Testcontainers")
class UserFeatureIntegrationTest extends AbstractIntegrationTest {
class UserFeatureIntegrationTest extends AbstractIntegrationTest {
private static final String USER_EMAIL = "feature_user@walkguide.test";
private static final String USER_PASS = "userpass123";
@@ -488,4 +485,4 @@ class UserFeatureIntegrationTest extends AbstractIntegrationTest {
.header("Authorization", bearerToken(userToken)))
.andExpect(status().isForbidden());
}
}
}
@@ -1,11 +1,15 @@
package com.walkguide.service;
import com.walkguide.dto.request.HardwareShortcutUpdateRequest;
import com.walkguide.dto.response.HardwareShortcutResponse;
import com.walkguide.entity.HardwareShortcut;
import com.walkguide.enums.HardwareShortcutKey;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.HardwareShortcutRepository;
import com.walkguide.dto.request.HardwareShortcutUpdateRequest;
import com.walkguide.dto.response.HardwareShortcutResponse;
import com.walkguide.entity.PairingRelation;
import com.walkguide.entity.HardwareShortcut;
import com.walkguide.entity.User;
import com.walkguide.enums.HardwareShortcutKey;
import com.walkguide.enums.PairingStatus;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.HardwareShortcutRepository;
import com.walkguide.repository.PairingRelationRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -15,7 +19,8 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.List;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
@@ -25,8 +30,10 @@ import static org.mockito.Mockito.*;
@DisplayName("HardwareShortcutService Unit Tests")
class HardwareShortcutServiceTest {
@Mock
HardwareShortcutRepository hardwareShortcutRepository;
@Mock
HardwareShortcutRepository hardwareShortcutRepository;
@Mock
PairingRelationRepository pairingRelationRepository;
@InjectMocks
HardwareShortcutService hardwareShortcutService;
@@ -173,11 +180,36 @@ class HardwareShortcutServiceTest {
@Test
@DisplayName("update - shortcutKey enum invalid harus throw IllegalArgumentException")
void update_invalidEnumKey_shouldThrow() {
void update_invalidEnumKey_shouldThrow() {
HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest();
req.setShortcutKey("INVALID_KEY_XYZ");
assertThatThrownBy(() -> hardwareShortcutService.update(10L, req))
.isInstanceOf(IllegalArgumentException.class);
}
}
assertThatThrownBy(() -> hardwareShortcutService.update(10L, req))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
@DisplayName("updateByGuardian - harus update shortcut milik user yang dipair")
void updateByGuardian_shouldUpdatePairedUserShortcut() {
HardwareShortcutUpdateRequest req = new HardwareShortcutUpdateRequest();
req.setShortcutKey("CALL_GUARDIAN");
req.setButtonName("Power Double Tap");
User guardian = User.builder().id(1L).build();
User user = User.builder().id(10L).build();
PairingRelation pairing = PairingRelation.builder()
.guardian(guardian).user(user).status(PairingStatus.ACTIVE).build();
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
.thenReturn(Optional.of(pairing));
when(hardwareShortcutRepository.findByUserId(10L))
.thenReturn(List.of(shortcutCallGuardian));
when(hardwareShortcutRepository.save(any(HardwareShortcut.class)))
.thenAnswer(inv -> inv.getArgument(0));
HardwareShortcutResponse result = hardwareShortcutService.updateByGuardian(1L, req);
assertThat(result.getButtonName()).isEqualTo("Power Double Tap");
verify(hardwareShortcutRepository).findByUserId(10L);
}
}
@@ -159,8 +159,10 @@ class SosServiceTest {
@Test
@DisplayName("acknowledgeSos - SOS ditemukan: harus ubah status ke ACKNOWLEDGED")
void acknowledgeSos_sosFound_shouldChangeStatusToAcknowledged() {
when(sosEventRepository.findById(50L)).thenReturn(Optional.of(savedSos));
when(userRepository.findById(2L)).thenReturn(Optional.of(user));
when(sosEventRepository.findById(50L)).thenReturn(Optional.of(savedSos));
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
.thenReturn(Optional.of(activePairing));
when(userRepository.findById(2L)).thenReturn(Optional.of(user));
when(sosEventRepository.save(any(SosEvent.class))).thenAnswer(inv -> inv.getArgument(0));
when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
.thenReturn(Optional.empty());
@@ -179,8 +181,10 @@ class SosServiceTest {
@Test
@DisplayName("acknowledgeSos - ada pairing user: harus kirim FCM ke user bahwa guardian sudah respon")
void acknowledgeSos_activePairingForUser_shouldNotifyUser() {
when(sosEventRepository.findById(50L)).thenReturn(Optional.of(savedSos));
when(userRepository.findById(2L)).thenReturn(Optional.of(user));
when(sosEventRepository.findById(50L)).thenReturn(Optional.of(savedSos));
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
.thenReturn(Optional.of(activePairing));
when(userRepository.findById(2L)).thenReturn(Optional.of(user));
when(sosEventRepository.save(any(SosEvent.class))).thenAnswer(inv -> inv.getArgument(0));
when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
.thenReturn(Optional.of(activePairing));
@@ -198,13 +202,35 @@ class SosServiceTest {
@Test
@DisplayName("acknowledgeSos - SOS tidak ditemukan: harus throw ResourceNotFoundException")
void acknowledgeSos_sosNotFound_shouldThrowException() {
when(sosEventRepository.findById(999L)).thenReturn(Optional.empty());
assertThatThrownBy(() -> sosService.acknowledgeSos(1L, 999L))
.isInstanceOf(ResourceNotFoundException.class)
.hasMessageContaining("SOS event tidak ditemukan");
}
void acknowledgeSos_sosNotFound_shouldThrowException() {
when(sosEventRepository.findById(999L)).thenReturn(Optional.empty());
assertThatThrownBy(() -> sosService.acknowledgeSos(1L, 999L))
.isInstanceOf(ResourceNotFoundException.class)
.hasMessageContaining("SOS event tidak ditemukan");
}
@Test
@DisplayName("resolveSos - SOS ditemukan: harus ubah status ke RESOLVED")
void resolveSos_sosFound_shouldChangeStatusToResolved() {
when(sosEventRepository.findById(50L)).thenReturn(Optional.of(savedSos));
when(pairingRelationRepository.findByGuardian_IdAndStatus(1L, PairingStatus.ACTIVE))
.thenReturn(Optional.of(activePairing));
when(userRepository.findById(2L)).thenReturn(Optional.of(user));
when(sosEventRepository.save(any(SosEvent.class))).thenAnswer(inv -> inv.getArgument(0));
when(pairingRelationRepository.findByUser_IdAndStatus(2L, PairingStatus.ACTIVE))
.thenReturn(Optional.empty());
doNothing().when(activityLogService).createLog(any(), any(), any(), any());
SosEventResponse result = sosService.resolveSos(1L, 50L);
assertThat(result).isNotNull();
assertThat(result.getStatus()).isEqualTo("RESOLVED");
ArgumentCaptor<SosEvent> captor = ArgumentCaptor.forClass(SosEvent.class);
verify(sosEventRepository).save(captor.capture());
assertThat(captor.getValue().getStatus()).isEqualTo(SosStatus.RESOLVED);
assertThat(captor.getValue().getAcknowledgedAt()).isNotNull();
}
// ===== getSosEvents TESTS =====
@@ -250,4 +276,4 @@ class SosServiceTest {
assertThatThrownBy(() -> sosService.getSosEventsForGuardian(1L, PageRequest.of(0, 10)))
.isInstanceOf(ResourceNotFoundException.class);
}
}
}