diff --git a/walkguide-backend/demo/src/test/java/com/walkguide/service/MockDataServiceTest.java b/walkguide-backend/demo/src/test/java/com/walkguide/service/MockDataServiceTest.java new file mode 100644 index 0000000..cd77fb5 --- /dev/null +++ b/walkguide-backend/demo/src/test/java/com/walkguide/service/MockDataServiceTest.java @@ -0,0 +1,270 @@ +package com.walkguide.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.*; + +@DisplayName("MockDataService Unit Tests") +class MockDataServiceTest { + + private MockDataService mockDataService; + + @BeforeEach + void setUp() { + mockDataService = new MockDataService(); + } + + // ===== getUserStatus TESTS ===== + + @Test + @DisplayName("getUserStatus - harus return data status user yang sudah diinisialisasi") + void getUserStatus_shouldReturnInitializedUserStatus() { + Map status = mockDataService.getUserStatus(); + + assertThat(status).isNotNull(); + assertThat(status).containsKey("status"); + assertThat(status).containsKey("location"); + assertThat(status).containsKey("batteryLevel"); + assertThat(status).containsKey("lastSeen"); + } + + @Test + @DisplayName("getUserStatus - nilai default status harus 'Sedang Berjalan'") + void getUserStatus_defaultStatus_shouldBeSedangBerjalan() { + Map status = mockDataService.getUserStatus(); + + assertThat(status.get("status")).isEqualTo("Sedang Berjalan"); + } + + @Test + @DisplayName("getUserStatus - nilai default location harus 'Jl. Kenangan, SBY'") + void getUserStatus_defaultLocation_shouldBeJlKenangan() { + Map status = mockDataService.getUserStatus(); + + assertThat(status.get("location")).isEqualTo("Jl. Kenangan, SBY"); + } + + @Test + @DisplayName("getUserStatus - nilai default batteryLevel harus 85") + void getUserStatus_defaultBatteryLevel_shouldBe85() { + Map status = mockDataService.getUserStatus(); + + assertThat(status.get("batteryLevel")).isEqualTo(85); + } + + @Test + @DisplayName("getUserStatus - nilai default lastSeen harus berupa timestamp ISO 8601") + void getUserStatus_defaultLastSeen_shouldBeIsoTimestamp() { + Map status = mockDataService.getUserStatus(); + + assertThat(status.get("lastSeen")).isEqualTo("2026-04-23T08:40:00Z"); + } + + @Test + @DisplayName("getUserStatus - harus return referensi map yang sama (stateful)") + void getUserStatus_shouldReturnSameMapReference() { + Map first = mockDataService.getUserStatus(); + Map second = mockDataService.getUserStatus(); + + assertThat(first).isSameAs(second); + } + + // ===== updateShortcuts TESTS ===== + + @Test + @DisplayName("updateShortcuts - harus menambahkan entry baru ke hardwareShortcuts") + void updateShortcuts_newEntry_shouldBeAdded() { + Map newShortcuts = new HashMap<>(); + newShortcuts.put("volumeUpAction", "start_navigation"); + newShortcuts.put("powerAction", "send_sos"); + + Map result = mockDataService.updateShortcuts(newShortcuts); + + assertThat(result.get("volumeUpAction")).isEqualTo("start_navigation"); + assertThat(result.get("powerAction")).isEqualTo("send_sos"); + } + + @Test + @DisplayName("updateShortcuts - nilai lama harus ter-override dengan nilai baru") + void updateShortcuts_existingKey_shouldBeOverridden() { + Map update = new HashMap<>(); + update.put("volumeUpAction", "call_guardian"); // override dari "accept_call" + + Map result = mockDataService.updateShortcuts(update); + + assertThat(result.get("volumeUpAction")).isEqualTo("call_guardian"); + } + + @Test + @DisplayName("updateShortcuts - key yang tidak diupdate harus tetap ada") + void updateShortcuts_unrelatedKeys_shouldBePreserved() { + Map update = new HashMap<>(); + update.put("volumeUpAction", "call_guardian"); + // volumeDownAction tidak diubah + + Map result = mockDataService.updateShortcuts(update); + + assertThat(result.get("volumeDownAction")).isEqualTo("emergency_ping"); + } + + @Test + @DisplayName("updateShortcuts - harus return map yang sama (bukan salinan baru)") + void updateShortcuts_shouldReturnSameMapInstance() { + Map before = mockDataService.updateShortcuts(new HashMap<>()); + Map after = mockDataService.updateShortcuts(new HashMap<>()); + + assertThat(before).isSameAs(after); + } + + @Test + @DisplayName("updateShortcuts - default awal volumeUpAction harus 'accept_call'") + void updateShortcuts_defaultVolumeUpAction_shouldBeAcceptCall() { + Map result = mockDataService.updateShortcuts(new HashMap<>()); + + assertThat(result.get("volumeUpAction")).isEqualTo("accept_call"); + } + + @Test + @DisplayName("updateShortcuts - default awal volumeDownAction harus 'emergency_ping'") + void updateShortcuts_defaultVolumeDownAction_shouldBeEmergencyPing() { + Map result = mockDataService.updateShortcuts(new HashMap<>()); + + assertThat(result.get("volumeDownAction")).isEqualTo("emergency_ping"); + } + + @Test + @DisplayName("updateShortcuts - update dengan map kosong tidak boleh mengubah nilai yang ada") + void updateShortcuts_emptyMap_shouldNotModifyExistingValues() { + Map result = mockDataService.updateShortcuts(new HashMap<>()); + + assertThat(result.get("volumeUpAction")).isEqualTo("accept_call"); + assertThat(result.get("volumeDownAction")).isEqualTo("emergency_ping"); + } + + @Test + @DisplayName("updateShortcuts - update berulang harus accumulate semua perubahan") + void updateShortcuts_multipleUpdates_shouldAccumulateChanges() { + mockDataService.updateShortcuts(Map.of("cameraAction", "take_photo")); + mockDataService.updateShortcuts(Map.of("homeAction", "open_walkguide")); + + Map result = mockDataService.updateShortcuts(new HashMap<>()); + + assertThat(result.get("cameraAction")).isEqualTo("take_photo"); + assertThat(result.get("homeAction")).isEqualTo("open_walkguide"); + } + + // ===== updateAiSettings TESTS ===== + + @Test + @DisplayName("updateAiSettings - harus menambahkan entry baru ke aiSettings") + void updateAiSettings_newEntry_shouldBeAdded() { + Map newSettings = new HashMap<>(); + newSettings.put("speechRate", 1.5); + newSettings.put("language", "id-ID"); + + Map result = mockDataService.updateAiSettings(newSettings); + + assertThat(result.get("speechRate")).isEqualTo(1.5); + assertThat(result.get("language")).isEqualTo("id-ID"); + } + + @Test + @DisplayName("updateAiSettings - nilai lama harus ter-override dengan nilai baru") + void updateAiSettings_existingKey_shouldBeOverridden() { + Map update = new HashMap<>(); + update.put("alertDistanceMeters", 5.0); // override dari 2.5 + + Map result = mockDataService.updateAiSettings(update); + + assertThat(result.get("alertDistanceMeters")).isEqualTo(5.0); + } + + @Test + @DisplayName("updateAiSettings - key yang tidak diupdate harus tetap ada") + void updateAiSettings_unrelatedKeys_shouldBePreserved() { + Map update = new HashMap<>(); + update.put("alertDistanceMeters", 3.0); + // hapticFeedback tidak diubah + + Map result = mockDataService.updateAiSettings(update); + + assertThat(result.get("hapticFeedback")).isEqualTo(true); + } + + @Test + @DisplayName("updateAiSettings - default alertDistanceMeters harus 2.5") + void updateAiSettings_defaultAlertDistance_shouldBe2point5() { + Map result = mockDataService.updateAiSettings(new HashMap<>()); + + assertThat(result.get("alertDistanceMeters")).isEqualTo(2.5); + } + + @Test + @DisplayName("updateAiSettings - default hapticFeedback harus true") + void updateAiSettings_defaultHapticFeedback_shouldBeTrue() { + Map result = mockDataService.updateAiSettings(new HashMap<>()); + + assertThat(result.get("hapticFeedback")).isEqualTo(true); + } + + @Test + @DisplayName("updateAiSettings - harus return map yang sama (bukan salinan baru)") + void updateAiSettings_shouldReturnSameMapInstance() { + Map before = mockDataService.updateAiSettings(new HashMap<>()); + Map after = mockDataService.updateAiSettings(new HashMap<>()); + + assertThat(before).isSameAs(after); + } + + @Test + @DisplayName("updateAiSettings - update dengan map kosong tidak boleh mengubah nilai yang ada") + void updateAiSettings_emptyMap_shouldNotModifyExistingValues() { + Map result = mockDataService.updateAiSettings(new HashMap<>()); + + assertThat(result.get("alertDistanceMeters")).isEqualTo(2.5); + assertThat(result.get("hapticFeedback")).isEqualTo(true); + } + + @Test + @DisplayName("updateAiSettings - update boolean ke false harus tersimpan dengan benar") + void updateAiSettings_disableHapticFeedback_shouldBeFalse() { + Map update = new HashMap<>(); + update.put("hapticFeedback", false); + + Map result = mockDataService.updateAiSettings(update); + + assertThat(result.get("hapticFeedback")).isEqualTo(false); + } + + @Test + @DisplayName("updateAiSettings - update berulang harus accumulate semua perubahan") + void updateAiSettings_multipleUpdates_shouldAccumulateChanges() { + mockDataService.updateAiSettings(Map.of("confidenceThreshold", 0.85)); + mockDataService.updateAiSettings(Map.of("maxObstacleRange", 10)); + + Map result = mockDataService.updateAiSettings(new HashMap<>()); + + assertThat(result.get("confidenceThreshold")).isEqualTo(0.85); + assertThat(result.get("maxObstacleRange")).isEqualTo(10); + } + + // ===== Isolation Tests ===== + + @Test + @DisplayName("instance baru harus selalu punya data default yang segar") + void newInstance_shouldAlwaysHaveFreshDefaultData() { + MockDataService instance1 = new MockDataService(); + MockDataService instance2 = new MockDataService(); + + instance1.updateShortcuts(Map.of("volumeUpAction", "changed_action")); + + // instance2 tidak terpengaruh perubahan instance1 + assertThat(instance2.updateShortcuts(new HashMap<>()).get("volumeUpAction")) + .isEqualTo("accept_call"); + } +}