@@ -64,7 +64,7 @@ class LoginViewModelTest {
65 fun `onLogin succeeds with valid credentials`() = runTest {
65 fun `onLogin succeeds with valid credentials`() = runTest {
66 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
66 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
67− coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo(version = "1.0"))
67+ coEvery { repository.verifyServer() } returns ApiResult.Success(AboutInfo(version = "1.0"))
69 viewModel.onServerUrlChange("https://test.parseable.com")
69 viewModel.onServerUrlChange("https://test.parseable.com")
70 viewModel.onUsernameChange("admin")
70 viewModel.onUsernameChange("admin")
@@ -95,7 +95,7 @@ class LoginViewModelTest {
96 fun `onLogin fails when about check fails`() = runTest {
96 fun `onLogin fails when about check fails`() = runTest {
97 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
97 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
98− coEvery { repository.getAbout() } returns ApiResult.Error("not parseable", 404)
98+ coEvery { repository.verifyServer() } returns ApiResult.Error("not parseable", 404)
100 viewModel.onServerUrlChange("https://test.com")
100 viewModel.onServerUrlChange("https://test.com")
101 viewModel.onUsernameChange("admin")
101 viewModel.onUsernameChange("admin")
@@ -108,6 +108,23 @@ class LoginViewModelTest {
108 assertTrue(state.error!!.contains("doesn't appear to be Parseable"))
108 assertTrue(state.error!!.contains("doesn't appear to be Parseable"))
112+ fun `onLogin shows invalid credentials on 401`() = runTest {
113+ coEvery { repository.testConnection() } returns ApiResult.Success("ok")
114+ coEvery { repository.verifyServer() } returns ApiResult.Error("Unauthorized", 401)
116+ viewModel.onServerUrlChange("https://test.parseable.com")
117+ viewModel.onUsernameChange("admin")
118+ viewModel.onPasswordChange("wrongpass")
121+ val state = viewModel.state.value
122+ assertFalse(state.loginSuccess)
123+ assertNotNull(state.error)
124+ assertTrue(state.error!!.contains("Invalid credentials"))
125+ assertFalse(state.error!!.contains("doesn't appear to be Parseable"))
112 fun `onServerUrlChange clears error`() {
129 fun `onServerUrlChange clears error`() {
113 viewModel.onLogin() // triggers error
130 viewModel.onLogin() // triggers error
@@ -147,7 +164,7 @@ class LoginViewModelTest {
148 fun `onLogin prepends https when not insecure`() = runTest {
165 fun `onLogin prepends https when not insecure`() = runTest {
149 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
166 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
150− coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo())
167+ coEvery { repository.verifyServer() } returns ApiResult.Success(AboutInfo())
152 viewModel.onServerUrlChange("test.parseable.com")
169 viewModel.onServerUrlChange("test.parseable.com")
153 viewModel.onUsernameChange("admin")
170 viewModel.onUsernameChange("admin")
@@ -162,7 +179,7 @@ class LoginViewModelTest {
163 fun `onLogin prepends http when insecure`() = runTest {
180 fun `onLogin prepends http when insecure`() = runTest {
164 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
181 coEvery { repository.testConnection() } returns ApiResult.Success("ok")
165− coEvery { repository.getAbout() } returns ApiResult.Success(AboutInfo())
182+ coEvery { repository.verifyServer() } returns ApiResult.Success(AboutInfo())
167 viewModel.onServerUrlChange("test.parseable.com")
184 viewModel.onServerUrlChange("test.parseable.com")
168 viewModel.onAllowInsecureChange(true)
185 viewModel.onAllowInsecureChange(true)
@@ -174,4 +191,39 @@ class LoginViewModelTest {
174 repository.configure(match { it.serverUrl == "http://test.parseable.com" })
191 repository.configure(match { it.serverUrl == "http://test.parseable.com" })
196+ fun `onLogin handles uppercase URL scheme`() = runTest {
197+ coEvery { repository.testConnection() } returns ApiResult.Success("ok")
198+ coEvery { repository.verifyServer() } returns ApiResult.Success(AboutInfo())
200+ viewModel.onServerUrlChange("HTTPS://test.parseable.com")
201+ viewModel.onUsernameChange("admin")
202+ viewModel.onPasswordChange("pass")
206+ repository.configure(match {
207+ it.serverUrl == "HTTPS://test.parseable.com" && it.useTls
213+ fun `onLogin handles mixed case URL scheme`() = runTest {
214+ coEvery { repository.testConnection() } returns ApiResult.Success("ok")
215+ coEvery { repository.verifyServer() } returns ApiResult.Success(AboutInfo())
217+ viewModel.onServerUrlChange("Http://test.parseable.com")
218+ viewModel.onAllowInsecureChange(true)
219+ viewModel.onUsernameChange("admin")
220+ viewModel.onPasswordChange("pass")
224+ repository.configure(match {
225+ it.serverUrl == "Http://test.parseable.com" && !it.useTls