git.iko.soy/parseable-android
on master help sign in
172 lines 6.9 kB history raw
1package com.parseable.android
3import com.parseable.android.data.model.AboutInfo
4import com.parseable.android.data.model.ApiResult
5import com.parseable.android.data.model.ServerConfig
6import kotlinx.serialization.json.buildJsonObject
7import kotlinx.serialization.json.put
8import com.parseable.android.data.repository.ParseableRepository
9import com.parseable.android.data.repository.SettingsRepository
10import com.parseable.android.ui.screens.settings.SettingsViewModel
11import io.mockk.*
12import kotlinx.coroutines.Dispatchers
13import kotlinx.coroutines.ExperimentalCoroutinesApi
14import kotlinx.coroutines.flow.flowOf
15import kotlinx.coroutines.test.*
16import kotlinx.serialization.json.JsonObject
17import kotlinx.serialization.json.JsonPrimitive
18import org.junit.After
19import org.junit.Assert.*
20import org.junit.Before
21import org.junit.Test
23@OptIn(ExperimentalCoroutinesApi::class)
24class SettingsViewModelTest {
26 private lateinit var settingsRepository: SettingsRepository
27 private lateinit var repository: ParseableRepository
28 private val testDispatcher = UnconfinedTestDispatcher()
30 @Before
31 fun setup() {
32 Dispatchers.setMain(testDispatcher)
33 settingsRepository = mockk(relaxed = true)
34 repository = mockk(relaxed = true)
35 }
37 @After
38 fun tearDown() {
39 Dispatchers.resetMain()
40 }
42 @Test
43 fun `load populates server config from settings`() = runTest {
44 val config = ServerConfig(
45 serverUrl = "https://logs.example.com",
46 username = "admin",
47 password = "secret",
48 useTls = true,
49 )
50 coEvery { settingsRepository.serverConfig } returns flowOf(config)
51 coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo(version = "1.2.0"))
52 coEvery { repository.listUsers() } returns ApiResult.Success(emptyList())
54 val viewModel = SettingsViewModel(settingsRepository, repository)
56 val state = viewModel.state.value
57 assertEquals("https://logs.example.com", state.serverUrl)
58 assertEquals("admin", state.username)
59 assertTrue(state.useTls)
60 assertFalse(state.isLoading)
61 }
63 @Test
64 fun `load sets about info on success`() = runTest {
65 val about = AboutInfo(version = "1.2.0", mode = "standalone", license = buildJsonObject { put("name", "AGPL") })
66 coEvery { settingsRepository.serverConfig } returns flowOf(null)
67 coEvery { repository.getAbout() } returns ApiResult.Success(about)
68 coEvery { repository.listUsers() } returns ApiResult.Success(emptyList())
70 val viewModel = SettingsViewModel(settingsRepository, repository)
72 val state = viewModel.state.value
73 assertEquals("1.2.0", state.aboutInfo?.version)
74 assertEquals("standalone", state.aboutInfo?.mode)
75 assertNull(state.error)
76 }
78 @Test
79 fun `load parses user list from json objects`() = runTest {
80 val users = listOf(
81 JsonObject(mapOf("id" to JsonPrimitive("user1"))),
82 JsonObject(mapOf("username" to JsonPrimitive("user2"))),
83 )
84 coEvery { settingsRepository.serverConfig } returns flowOf(null)
85 coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo())
86 coEvery { repository.listUsers() } returns ApiResult.Success(users)
88 val viewModel = SettingsViewModel(settingsRepository, repository)
90 val state = viewModel.state.value
91 assertEquals(listOf("user1", "user2"), state.users)
92 assertFalse(state.isLoading)
93 }
95 @Test
96 fun `load sets error when about fails`() = runTest {
97 coEvery { settingsRepository.serverConfig } returns flowOf(null)
98 coEvery { repository.getAbout() } returns ApiResult.Error("Server error", 500)
99 coEvery { repository.listUsers() } returns ApiResult.Success(emptyList())
101 val viewModel = SettingsViewModel(settingsRepository, repository)
103 val state = viewModel.state.value
104 assertNotNull(state.error)
105 assertNull(state.aboutInfo)
106 assertFalse(state.isLoading)
107 }
109 @Test
110 fun `load ignores users failure when about succeeds`() = runTest {
111 coEvery { settingsRepository.serverConfig } returns flowOf(null)
112 coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo(version = "1.2.0"))
113 coEvery { repository.listUsers() } returns ApiResult.Error("Forbidden", 403)
115 val viewModel = SettingsViewModel(settingsRepository, repository)
117 // The user list (/user) is secondary and is commonly forbidden for restricted accounts,
118 // so its failure must not surface a blocking error or hide otherwise-valid server info.
119 val state = viewModel.state.value
120 assertNull(state.error)
121 assertEquals("1.2.0", state.aboutInfo?.version)
122 assertTrue(state.users.isEmpty())
123 }
125 @Test
126 fun `load surfaces only the about error, not the users error`() = runTest {
127 coEvery { settingsRepository.serverConfig } returns flowOf(null)
128 coEvery { repository.getAbout() } returns ApiResult.Error("Server error", 500)
129 coEvery { repository.listUsers() } returns ApiResult.Error("Forbidden", 403)
131 val viewModel = SettingsViewModel(settingsRepository, repository)
133 // Only the primary server-info call (/about) is treated as blocking; the secondary
134 // /user failure stays silent.
135 val state = viewModel.state.value
136 assertNotNull(state.error)
137 assertTrue(state.error!!.contains("Server error"))
138 assertFalse(state.error!!.contains("Permission denied"))
139 }
141 @Test
142 fun `load resets isLoading on unexpected exception`() = runTest {
143 coEvery { settingsRepository.serverConfig } returns flowOf(null)
144 coEvery { repository.getAbout() } throws RuntimeException("unexpected")
145 coEvery { repository.listUsers() } returns ApiResult.Success(emptyList())
147 val viewModel = SettingsViewModel(settingsRepository, repository)
149 val state = viewModel.state.value
150 assertFalse(state.isLoading)
151 assertNotNull(state.error)
152 assertTrue(state.error!!.contains("unexpected"))
153 }
155 @Test
156 fun `load can be called again to refresh`() = runTest {
157 coEvery { settingsRepository.serverConfig } returns flowOf(null)
158 coEvery { repository.getAbout() } returns ApiResult.Error("fail", 500)
159 coEvery { repository.listUsers() } returns ApiResult.Success(emptyList())
161 val viewModel = SettingsViewModel(settingsRepository, repository)
162 assertNotNull(viewModel.state.value.error)
164 // Fix the mock and reload
165 coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo(version = "2.0"))
166 viewModel.load()
168 val state = viewModel.state.value
169 assertNull(state.error)
170 assertEquals("2.0", state.aboutInfo?.version)
171 }