Skip to content

Commit b4ffda0

Browse files
committed
Test application-password credential writers against a real DB
updateApplicationPasswordCredentials and its URL-keyed variant were verified only against a mocked SiteSqlUtils, so their ContentValues mapping never ran. A .first/.second swap, a wrong column, or a dropped IV would ship uncaught — and the new blank-IV decrypt guard would mask it as 'no credentials' rather than surfacing it. EncryptionUtils can't run under Robolectric (AndroidKeyStore), so add a stubbed-EncryptionUtils SiteSqlUtils and assert the column mapping via the raw storedSite() read, plus a decrypt round-trip and the ORIGIN_WPAPI URL-scoping (writes the matching row, leaves a same-url non-WPAPI row untouched).
1 parent c184569 commit b4ffda0

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

libs/fluxc/src/test/java/org/wordpress/android/fluxc/persistence/SiteSqlUtilsTest.kt

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import org.assertj.core.api.Assertions.assertThat
55
import org.junit.Before
66
import org.junit.Test
77
import org.junit.runner.RunWith
8+
import org.mockito.kotlin.mock
9+
import org.mockito.kotlin.whenever
810
import org.robolectric.RobolectricTestRunner
911
import org.robolectric.RuntimeEnvironment
1012
import org.wordpress.android.fluxc.encryption.EncryptionUtils
@@ -14,6 +16,13 @@ import org.wordpress.android.fluxc.model.SiteModel
1416
class SiteSqlUtilsTest {
1517
private val siteSqlUtils = SiteSqlUtils(EncryptionUtils())
1618

19+
// EncryptionUtils uses the AndroidKeyStore provider, which Robolectric can't load, so the production
20+
// util's encrypt/decrypt throw here. A stubbed util lets us exercise the encrypting writers' own
21+
// ContentValues mapping (which half of each encrypt() Pair lands in which column) against a real DB —
22+
// the SiteStore-level tests can't, since they verify a mocked SiteSqlUtils and never run the body.
23+
private val fakeEncryptionUtils = mock<EncryptionUtils>()
24+
private val encryptingSiteSqlUtils = SiteSqlUtils(fakeEncryptionUtils)
25+
1726
@Before
1827
fun setUp() {
1928
val appContext = RuntimeEnvironment.getApplication().applicationContext
@@ -210,6 +219,85 @@ class SiteSqlUtilsTest {
210219
assertThat(stored.apiRestPasswordPlain).isNullOrEmpty()
211220
}
212221

222+
@Test
223+
fun `updateApplicationPasswordCredentials writes each encrypted value into its matching column`() {
224+
// ciphertext (encrypt().first) -> *_ENCRYPTED column, IV (.second) -> *_IV column, and username
225+
// must not be cross-wired with password. A .first/.second swap, a wrong column, or a dropped
226+
// cv.put would all read back wrong here — the failure modes the SiteStore mock can't catch.
227+
whenever(fakeEncryptionUtils.encrypt("user")).thenReturn("enc_user" to "iv_user")
228+
whenever(fakeEncryptionUtils.encrypt("pass")).thenReturn("enc_pass" to "iv_pass")
229+
WellSql.insert(SiteModel().apply { url = "https://example.test" }).execute()
230+
val localId = storedSite().id
231+
232+
val rows = encryptingSiteSqlUtils.updateApplicationPasswordCredentials(localId, "user", "pass")
233+
234+
assertThat(rows).isEqualTo(1)
235+
val stored = storedSite()
236+
assertThat(stored.apiRestUsernameEncrypted).isEqualTo("enc_user")
237+
assertThat(stored.apiRestUsernameIV).isEqualTo("iv_user")
238+
assertThat(stored.apiRestPasswordEncrypted).isEqualTo("enc_pass")
239+
assertThat(stored.apiRestPasswordIV).isEqualTo("iv_pass")
240+
}
241+
242+
@Test
243+
fun `updateApplicationPasswordCredentials persists credentials that decrypt back to the originals`() {
244+
whenever(fakeEncryptionUtils.encrypt("user")).thenReturn("enc_user" to "iv_user")
245+
whenever(fakeEncryptionUtils.encrypt("pass")).thenReturn("enc_pass" to "iv_pass")
246+
whenever(fakeEncryptionUtils.decrypt("enc_user", "iv_user")).thenReturn("user")
247+
whenever(fakeEncryptionUtils.decrypt("enc_pass", "iv_pass")).thenReturn("pass")
248+
WellSql.insert(SiteModel().apply { url = "https://example.test" }).execute()
249+
val localId = storedSite().id
250+
251+
encryptingSiteSqlUtils.updateApplicationPasswordCredentials(localId, "user", "pass")
252+
253+
// Read back through the decrypting path: the stored ciphertext/IV must round-trip to the inputs.
254+
val stored = encryptingSiteSqlUtils.getSites().single()
255+
assertThat(stored.apiRestUsernamePlain).isEqualTo("user")
256+
assertThat(stored.apiRestPasswordPlain).isEqualTo("pass")
257+
}
258+
259+
@Test
260+
fun `updateApplicationPasswordCredentialsForWPAPISite writes the matching WPAPI site by url`() {
261+
whenever(fakeEncryptionUtils.encrypt("user")).thenReturn("enc_user" to "iv_user")
262+
whenever(fakeEncryptionUtils.encrypt("pass")).thenReturn("enc_pass" to "iv_pass")
263+
WellSql.insert(SiteModel().apply {
264+
url = "https://selfhosted.test"
265+
origin = SiteModel.ORIGIN_WPAPI
266+
}).execute()
267+
268+
val rows = encryptingSiteSqlUtils.updateApplicationPasswordCredentialsForWPAPISite(
269+
siteUrl = "https://selfhosted.test",
270+
usernamePlain = "user",
271+
passwordPlain = "pass"
272+
)
273+
274+
assertThat(rows).isEqualTo(1)
275+
val stored = storedSite()
276+
assertThat(stored.apiRestUsernameEncrypted).isEqualTo("enc_user")
277+
assertThat(stored.apiRestUsernameIV).isEqualTo("iv_user")
278+
assertThat(stored.apiRestPasswordEncrypted).isEqualTo("enc_pass")
279+
assertThat(stored.apiRestPasswordIV).isEqualTo("iv_pass")
280+
}
281+
282+
@Test
283+
fun `updateApplicationPasswordCredentialsForWPAPISite leaves a non-WPAPI site with the same url untouched`() {
284+
WellSql.insert(SiteModel().apply {
285+
url = "https://shared.test"
286+
origin = SiteModel.ORIGIN_WPCOM_REST
287+
}).execute()
288+
289+
val rows = encryptingSiteSqlUtils.updateApplicationPasswordCredentialsForWPAPISite(
290+
siteUrl = "https://shared.test",
291+
usernamePlain = "user",
292+
passwordPlain = "pass"
293+
)
294+
295+
assertThat(rows).isEqualTo(0)
296+
val stored = storedSite()
297+
assertThat(stored.apiRestUsernameEncrypted).isNullOrEmpty()
298+
assertThat(stored.apiRestPasswordEncrypted).isNullOrEmpty()
299+
}
300+
213301
@Test
214302
fun `updateXmlRpcUrl writes the column and leaves other fields alone`() {
215303
WellSql.insert(SiteModel().apply {

0 commit comments

Comments
 (0)