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
This commit is contained in:
Hossain Khan
2026-01-31 13:35:33 -05:00
parent a8d4066af2
commit 61216a3c43
2 changed files with 242 additions and 0 deletions
@@ -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()
}
}
@@ -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<Unit> =
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()) }
}
}