From 61216a3c43a97175980c2268ba0a0e7d98bb2136 Mon Sep 17 00:00:00 2001 From: Hossain Khan Date: Sat, 31 Jan 2026 13:35:33 -0500 Subject: [PATCH] test: Add comprehensive test coverage for device ID and battery reporting - Add tests for getDeviceIdFromApi() method (mocked response) - Add tests for reportBatteryStatus() success and failure scenarios - Add tests for null deviceId and userApiToken validation - Add tests for fake data mode behavior - Add tests for saveDeviceId/getDeviceId in TrmnlDeviceConfigDataStore - Add tests for deviceId persistence in dual-storage (JSON + legacy) - Add tests for deviceId removal when set to null Test coverage includes: - 6 new tests in TrmnlDisplayRepositoryTest - 5 new tests in TrmnlDeviceConfigDataStoreTest - All edge cases and error conditions validated --- .../data/TrmnlDeviceConfigDataStoreTest.kt | 96 ++++++++++++ .../data/TrmnlDisplayRepositoryTest.kt | 146 ++++++++++++++++++ 2 files changed, 242 insertions(+) diff --git a/app/src/test/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStoreTest.kt b/app/src/test/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStoreTest.kt index 672c99f..093bd59 100644 --- a/app/src/test/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStoreTest.kt +++ b/app/src/test/java/ink/trmnl/android/data/TrmnlDeviceConfigDataStoreTest.kt @@ -626,4 +626,100 @@ class TrmnlDeviceConfigDataStoreTest { val preferences = deviceConfigDataStore.deviceModelPreferencesFlow.first() assertThat(preferences).isEmpty() } + + @Test + fun `saveDeviceId and getDeviceId work correctly`() = + runTest { + // Arrange + val expectedDeviceId = 12345 + + // Act + deviceConfigDataStore.saveDeviceId(expectedDeviceId) + val retrievedDeviceId = deviceConfigDataStore.getDeviceId() + + // Assert + assertThat(retrievedDeviceId).isEqualTo(expectedDeviceId) + } + + @Test + fun `getDeviceId returns null when not saved`() = + runTest { + // Act + val deviceId = deviceConfigDataStore.getDeviceId() + + // Assert + assertThat(deviceId).isNull() + } + + @Test + fun `saveDeviceConfig persists deviceId to both JSON and legacy storage`() = + runTest { + // Arrange + val configWithDeviceId = + TrmnlDeviceConfig( + type = TrmnlDeviceType.BYOD, + apiAccessToken = "test-token", + apiBaseUrl = "https://usetrmnl.com", + userApiToken = "user_test_token", + deviceId = 999, + ) + + // Act + deviceConfigDataStore.saveDeviceConfig(configWithDeviceId) + + // Assert - Verify device ID is persisted via getDeviceId (legacy storage) + val deviceId = deviceConfigDataStore.getDeviceId() + assertThat(deviceId).isEqualTo(999) + + // Assert - Verify device ID is persisted via deviceConfigFlow (JSON storage) + val loadedConfig = deviceConfigDataStore.deviceConfigFlow.first() + assertThat(loadedConfig).isNotNull() + assertThat(loadedConfig?.deviceId).isEqualTo(999) + } + + @Test + fun `deviceConfigFlow loads deviceId from legacy storage when JSON not present`() = + runTest { + // Arrange - Save individual fields (legacy approach) without JSON + deviceConfigDataStore.saveDeviceType(TrmnlDeviceType.BYOD) + deviceConfigDataStore.saveAccessToken("test-token") + deviceConfigDataStore.saveServerUrl("https://usetrmnl.com") + deviceConfigDataStore.saveDeviceId(777) + + // Act + val loadedConfig = deviceConfigDataStore.deviceConfigFlow.first() + + // Assert + assertThat(loadedConfig).isNotNull() + assertThat(loadedConfig?.deviceId).isEqualTo(777) + assertThat(loadedConfig?.type).isEqualTo(TrmnlDeviceType.BYOD) + } + + @Test + fun `saveDeviceConfig removes deviceId from storage when null`() = + runTest { + // Arrange - First save config with deviceId + val configWithDeviceId = + TrmnlDeviceConfig( + type = TrmnlDeviceType.BYOD, + apiAccessToken = "test-token", + apiBaseUrl = "https://usetrmnl.com", + deviceId = 123, + ) + deviceConfigDataStore.saveDeviceConfig(configWithDeviceId) + + // Verify it was saved + assertThat(deviceConfigDataStore.getDeviceId()).isEqualTo(123) + + // Act - Now save config without deviceId + val configWithoutDeviceId = configWithDeviceId.copy(deviceId = null) + deviceConfigDataStore.saveDeviceConfig(configWithoutDeviceId) + + // Assert - DeviceId should be removed + val deviceId = deviceConfigDataStore.getDeviceId() + assertThat(deviceId).isNull() + + val loadedConfig = deviceConfigDataStore.deviceConfigFlow.first() + assertThat(loadedConfig?.deviceId).isNull() + } } diff --git a/app/src/test/java/ink/trmnl/android/data/TrmnlDisplayRepositoryTest.kt b/app/src/test/java/ink/trmnl/android/data/TrmnlDisplayRepositoryTest.kt index 5bb73df..e949f46 100644 --- a/app/src/test/java/ink/trmnl/android/data/TrmnlDisplayRepositoryTest.kt +++ b/app/src/test/java/ink/trmnl/android/data/TrmnlDisplayRepositoryTest.kt @@ -571,4 +571,150 @@ class TrmnlDisplayRepositoryTest { assertThat(result.httpResponseMetadata?.statusCode).isEqualTo(500) assertThat(result.httpResponseMetadata?.contentLength).isEqualTo(123L) } + + @Test + fun `getDeviceIdFromApi should return mocked device ID`() = + runTest { + // Act + val result = repository.getDeviceIdFromApi(byodDeviceConfig) + + // Assert + assertThat(result.isSuccess).isTrue() + assertThat(result.getOrNull()).isEqualTo(1) + + // Verify API was NOT called since we're using mocked response + coVerify(exactly = 0) { apiService.getDeviceMe(any(), any()) } + } + + @Test + fun `reportBatteryStatus should succeed with valid BYOD config`() = + runTest { + // Arrange + val byodConfigWithDeviceId = + byodDeviceConfig.copy( + deviceId = 123, + userApiToken = "user_test_token", + ) + + val expectedApiUrl = "https://server.example.com/api/devices/123" + + coEvery { + userApiService.updateDevice( + fullApiUrl = expectedApiUrl, + accessToken = "Bearer user_test_token", + updateRequest = any(), + ) + } returns ApiResult.success(mockk(relaxed = true)) + + // Act + val result = repository.reportBatteryStatus(byodConfigWithDeviceId, 85) + + // Assert + assertThat(result.isSuccess).isTrue() + + coVerify { + userApiService.updateDevice( + fullApiUrl = expectedApiUrl, + accessToken = "Bearer user_test_token", + updateRequest = match { it.percentCharged == 85.0 }, + ) + } + } + + @Test + fun `reportBatteryStatus should fail when deviceId is null`() = + runTest { + // Arrange + val configWithoutDeviceId = + byodDeviceConfig.copy( + deviceId = null, + userApiToken = "user_test_token", + ) + + // Act + val result = repository.reportBatteryStatus(configWithoutDeviceId, 85) + + // Assert + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java) + assertThat(result.exceptionOrNull()?.message).contains("Device ID is required") + + // Verify API was NOT called + coVerify(exactly = 0) { userApiService.updateDevice(any(), any(), any()) } + } + + @Test + fun `reportBatteryStatus should fail when userApiToken is null`() = + runTest { + // Arrange + val configWithoutUserToken = + byodDeviceConfig.copy( + deviceId = 123, + userApiToken = null, + ) + + // Act + val result = repository.reportBatteryStatus(configWithoutUserToken, 85) + + // Assert + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java) + assertThat(result.exceptionOrNull()?.message).contains("User API token is required") + + // Verify API was NOT called + coVerify(exactly = 0) { userApiService.updateDevice(any(), any(), any()) } + } + + @Test + fun `reportBatteryStatus should handle API failure`() = + runTest { + // Arrange + val byodConfigWithDeviceId = + byodDeviceConfig.copy( + deviceId = 123, + userApiToken = "user_test_token", + ) + + val expectedApiUrl = "https://server.example.com/api/devices/123" + val apiException = java.io.IOException("Network error") + val httpFailure: ApiResult.Failure = + ApiResult.networkFailure(apiException) + + coEvery { + userApiService.updateDevice( + fullApiUrl = expectedApiUrl, + accessToken = "Bearer user_test_token", + updateRequest = any(), + ) + } returns httpFailure + + // Act + val result = repository.reportBatteryStatus(byodConfigWithDeviceId, 85) + + // Assert + assertThat(result.isFailure).isTrue() + assertThat(result.exceptionOrNull()).isEqualTo(apiException) + } + + @Test + fun `reportBatteryStatus should skip API call in fake data mode`() = + runTest { + // Arrange + every { repositoryConfigProvider.shouldUseFakeData } returns true + + val byodConfigWithDeviceId = + byodDeviceConfig.copy( + deviceId = 123, + userApiToken = "user_test_token", + ) + + // Act + val result = repository.reportBatteryStatus(byodConfigWithDeviceId, 85) + + // Assert + assertThat(result.isSuccess).isTrue() + + // Verify API was NOT called + coVerify(exactly = 0) { userApiService.updateDevice(any(), any(), any()) } + } }