Skip to content

Commit 32c308d

Browse files
Add unit tests
1 parent 041aeb3 commit 32c308d

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords
2+
3+
import android.content.Context
4+
import android.content.SharedPreferences
5+
import kotlinx.coroutines.Dispatchers
6+
import kotlinx.coroutines.joinAll
7+
import kotlinx.coroutines.launch
8+
import kotlinx.coroutines.test.runTest
9+
import org.assertj.core.api.Assertions.assertThat
10+
import org.junit.After
11+
import org.junit.Before
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
import org.mockito.kotlin.mock
15+
import org.robolectric.RobolectricTestRunner
16+
import org.robolectric.RuntimeEnvironment
17+
import org.wordpress.android.fluxc.model.SiteModel
18+
import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.JetpackApplicationPasswordsSupport.Companion.FLAG_REFRESH_DURATION_DAYS
19+
import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.JetpackApplicationPasswordsSupport.Companion.UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES
20+
import org.wordpress.android.fluxc.utils.PreferenceUtils
21+
import kotlin.time.Duration.Companion.days
22+
23+
@RunWith(RobolectricTestRunner::class)
24+
class JetpackApplicationPasswordsSupportTest {
25+
private lateinit var context: Context
26+
private lateinit var sharedPreferences: SharedPreferences
27+
private lateinit var jetpackApplicationPasswordsSupport: JetpackApplicationPasswordsSupport
28+
29+
private val testSiteId = 123L
30+
31+
@Before
32+
fun setup() {
33+
context = RuntimeEnvironment.getApplication().applicationContext
34+
sharedPreferences = PreferenceUtils.getFluxCPreferences(context)
35+
jetpackApplicationPasswordsSupport = JetpackApplicationPasswordsSupport(context = context, appLog = mock())
36+
}
37+
38+
@After
39+
fun tearDown() {
40+
sharedPreferences.edit().remove(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES).apply()
41+
}
42+
43+
@Test
44+
fun `given site with no flag and app passwords supported, when checking support, then return true`() {
45+
// Given
46+
val testSite = createSite(supportsAppPasswords = true)
47+
// SharedPreferences is empty by default
48+
49+
// When
50+
val result = jetpackApplicationPasswordsSupport.supportsAppPasswords(testSite)
51+
52+
// Then
53+
assertThat(result).isTrue()
54+
}
55+
56+
@Test
57+
fun `given site with no flag but app passwords not supported, when checking support, then return false`() {
58+
// Given
59+
val siteWithoutAppPasswordsSupport = createSite(supportsAppPasswords = false)
60+
61+
// When
62+
val result = jetpackApplicationPasswordsSupport.supportsAppPasswords(siteWithoutAppPasswordsSupport)
63+
64+
// Then
65+
assertThat(result).isFalse()
66+
}
67+
68+
@Test
69+
fun `given site flagged as unsupported within refresh duration, when checking support, then return false`() {
70+
// Given
71+
val testSite = createSite(supportsAppPasswords = true)
72+
val currentTime = System.currentTimeMillis()
73+
val flagTime = currentTime - (FLAG_REFRESH_DURATION_DAYS.days - 1.days).inWholeMilliseconds
74+
val unsupportedSites = setOf("$testSiteId:$flagTime")
75+
76+
// Pre-populate SharedPreferences with the flag
77+
sharedPreferences.edit()
78+
.putStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, unsupportedSites)
79+
.apply()
80+
81+
// When
82+
val result = jetpackApplicationPasswordsSupport.supportsAppPasswords(testSite)
83+
84+
// Then
85+
assertThat(result).isFalse()
86+
}
87+
88+
@Test
89+
fun `given site flagged as unsupported beyond refresh duration, when checking support, then clear flag and return site support status`() {
90+
// Given
91+
val testSite = createSite(supportsAppPasswords = true)
92+
val currentTime = System.currentTimeMillis()
93+
val flagTime = currentTime - (FLAG_REFRESH_DURATION_DAYS.days + 1.days).inWholeMilliseconds
94+
val unsupportedSites = setOf("$testSiteId:$flagTime", "456:$currentTime")
95+
96+
// Pre-populate SharedPreferences with the flags
97+
sharedPreferences.edit()
98+
.putStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, unsupportedSites)
99+
.apply()
100+
101+
// When
102+
val result = jetpackApplicationPasswordsSupport.supportsAppPasswords(testSite)
103+
104+
// Then
105+
assertThat(result).isTrue() // Returns site's isApplicationPasswordsSupported value
106+
107+
// Verify that the expired flag was removed but the other flag remains
108+
val remainingFlags = sharedPreferences.getStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, null)
109+
assertThat(remainingFlags).containsExactly("456:$currentTime")
110+
}
111+
112+
@Test
113+
fun `given multiple sites in unsupported list, when checking support for different site, then return site support status`() {
114+
// Given
115+
val differentSiteId = 999L
116+
val differentSite = createSite(siteId = differentSiteId, supportsAppPasswords = true)
117+
val currentTime = System.currentTimeMillis()
118+
val flagTime = currentTime - (FLAG_REFRESH_DURATION_DAYS.days - 1.days).inWholeMilliseconds
119+
val unsupportedSites = setOf("$testSiteId:$flagTime", "456:$currentTime")
120+
121+
// Pre-populate SharedPreferences with the flags
122+
sharedPreferences.edit()
123+
.putStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, unsupportedSites)
124+
.apply()
125+
126+
// When
127+
val result = jetpackApplicationPasswordsSupport.supportsAppPasswords(differentSite)
128+
129+
// Then
130+
assertThat(result).isTrue()
131+
132+
// Verify that the existing flags were not modified
133+
val remainingFlags = sharedPreferences.getStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, null)
134+
assertThat(remainingFlags).containsExactlyInAnyOrder("$testSiteId:$flagTime", "456:$currentTime")
135+
}
136+
137+
@Test
138+
fun `given site not yet flagged, when flagging as unsupported, then add site to unsupported list`() {
139+
// Given
140+
val testSite = createSite()
141+
val existingUnsupportedSites = setOf("456:1234567890")
142+
143+
// Pre-populate SharedPreferences with existing flags
144+
sharedPreferences.edit()
145+
.putStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, existingUnsupportedSites)
146+
.apply()
147+
148+
// When
149+
jetpackApplicationPasswordsSupport.flagAsUnsupported(testSite)
150+
151+
// Then
152+
val updatedFlags = sharedPreferences.getStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, null)
153+
assertThat(updatedFlags).hasSize(2)
154+
assertThat(updatedFlags).contains("456:1234567890")
155+
assertThat(updatedFlags!!.any { it.startsWith("$testSiteId:") }).isTrue()
156+
}
157+
158+
@Test
159+
fun `given site already flagged, when flagging as unsupported again, then do not modify the list`() {
160+
// Given
161+
val testSite = createSite()
162+
val currentTime = System.currentTimeMillis()
163+
val existingUnsupportedSites = setOf("$testSiteId:$currentTime", "456:1234567890")
164+
165+
// Pre-populate SharedPreferences with existing flags
166+
sharedPreferences.edit()
167+
.putStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, existingUnsupportedSites)
168+
.apply()
169+
170+
// When
171+
jetpackApplicationPasswordsSupport.flagAsUnsupported(testSite)
172+
173+
// Then
174+
val updatedFlags = sharedPreferences.getStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, null)
175+
assertThat(updatedFlags).isEqualTo(existingUnsupportedSites)
176+
}
177+
178+
@Test
179+
fun `given malformed flag entry, when checking support, then handle gracefully`() {
180+
// Given
181+
val testSite = createSite(supportsAppPasswords = true)
182+
val unsupportedSites = setOf("$testSiteId:invalid_timestamp", "456:1234567890")
183+
sharedPreferences.edit()
184+
.putStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, unsupportedSites)
185+
.apply()
186+
187+
// When
188+
val result = jetpackApplicationPasswordsSupport.supportsAppPasswords(testSite)
189+
190+
// Then
191+
assertThat(result).isTrue()
192+
val remainingFlags = sharedPreferences.getStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, null)
193+
assertThat(remainingFlags).noneMatch { it.startsWith("$testSiteId:") }
194+
}
195+
196+
@Test
197+
fun `given concurrent access, when flagging and checking support, then maintain data integrity`() = runTest {
198+
// Given
199+
val testSite = createSite()
200+
val currentTime = System.currentTimeMillis()
201+
val flagTime = currentTime - 8.days.inWholeMilliseconds
202+
println(flagTime)
203+
sharedPreferences.edit()
204+
.putStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, setOf("$testSiteId:$flagTime"))
205+
.apply()
206+
207+
// When
208+
val accessJobs = (0..4).map {
209+
launch(Dispatchers.Default) {
210+
jetpackApplicationPasswordsSupport.supportsAppPasswords(testSite)
211+
}
212+
}
213+
val updateJobs = (0..4).map {
214+
launch(Dispatchers.Default) {
215+
jetpackApplicationPasswordsSupport.flagAsUnsupported(testSite)
216+
}
217+
}
218+
joinAll(*accessJobs.toTypedArray(), *updateJobs.toTypedArray())
219+
220+
// Then
221+
val updatedFlags = sharedPreferences.getStringSet(UNSUPPORTED_JETPACK_APP_PASSWORDS_SITES, null)
222+
val siteFlag = updatedFlags?.firstOrNull { it.startsWith("$testSiteId:") }
223+
assertThat(siteFlag).isNotNull
224+
val timestamp = siteFlag!!.substringAfter(":").toLongOrNull()
225+
assertThat(timestamp).isGreaterThanOrEqualTo(currentTime)
226+
}
227+
228+
private fun createSite(siteId: Long = testSiteId, supportsAppPasswords: Boolean = true): SiteModel {
229+
val site = SiteModel()
230+
site.siteId = siteId
231+
site.applicationPasswordsAuthorizeUrl = if (supportsAppPasswords) "https://example.com/authorize" else null
232+
return site
233+
}
234+
}

0 commit comments

Comments
 (0)