Harden WebView nav + add test suite + fix lint for production
Build APK / build (push) Successful in 1m29s
Build APK / build (push) Successful in 1m29s
Security: - LoginActivity WebView now enforces a host allow-list in shouldOverrideUrlLoading: only claude.ai + required SSO/CDN hosts (Google, Apple, Cloudflare, gstatic, recaptcha) can navigate; everything else is blocked. market://intent:// still blocked; about:/data: allowed. Device-verified: claude.ai login + Cloudflare challenge still load. Tests (33, pure-JVM JUnit4, no device needed): - Extracted shouldRecordHistory() pure throttle decision (regression guard for the empty-history-chart bug) + HistoryThrottleTest. - UsageDataTest (mergedWith last-good/partial-union, computed props), PaceCalcTest, PeakHoursTest. - Added junit:junit:4.13.2 as testImplementation only. Build quality: - widget_layout.xml: suppress false-positive UseAppTint lint on the widget refresh button (app:tint doesn't work in RemoteViews; android:tint is correct here) so lintDebug is clean. Verified locally: 33 unit tests pass, lintDebug 0 errors, signed assembleRelease OK (apksigner verified, signer identity unchanged), emulator smoke test launches + renders without crash.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package me.khodak.claudeusage
|
||||
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Pure-JVM tests for PaceCalc.compute. The clock is injected via the `now` parameter, so these are
|
||||
* deterministic and require no Android framework.
|
||||
*/
|
||||
class PaceCalcTest {
|
||||
|
||||
private val window = PaceCalc.SESSION_WINDOW_MS // 5h
|
||||
|
||||
@Test
|
||||
fun computeReturnsPaceForValidFutureReset() {
|
||||
// Place "now" halfway through the window (elapsedFraction = 0.5, within [0.03, 1.0)).
|
||||
val resetEpoch = 10_000_000_000L
|
||||
val now = resetEpoch - window / 2
|
||||
val pace = PaceCalc.compute(usedPct = 25f, resetEpoch = resetEpoch, windowMs = window, now = now)
|
||||
|
||||
assertNotNull(pace)
|
||||
val p = pace!!
|
||||
// markerPct mirrors elapsedFraction*100 → ~50, and must stay in 0..100.
|
||||
assertTrue("markerPct in range", p.markerPct in 0..100)
|
||||
assertEquals50(p.markerPct)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun markerPctAlwaysInRangeNearWindowEnd() {
|
||||
// Near the end of the window (95% elapsed, still < 1.0) marker approaches 100, never exceeds.
|
||||
// Kept off the exact boundary so Float rounding of (window-elapsed)/window can't tip it to 1.0.
|
||||
val resetEpoch = 10_000_000_000L
|
||||
val now = resetEpoch - (window * 5L / 100L) // 95% elapsed
|
||||
val pace = PaceCalc.compute(usedPct = 80f, resetEpoch = resetEpoch, windowMs = window, now = now)
|
||||
|
||||
assertNotNull(pace)
|
||||
assertTrue(pace!!.markerPct in 0..100)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeReturnsNullForPastReset() {
|
||||
// Reset already passed → elapsedFraction >= 1.0 → null.
|
||||
val resetEpoch = 10_000_000_000L
|
||||
val now = resetEpoch + 60_000L
|
||||
assertNull(PaceCalc.compute(usedPct = 50f, resetEpoch = resetEpoch, windowMs = window, now = now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeReturnsNullTooEarlyInWindow() {
|
||||
// Less than 3% elapsed → not enough signal → null.
|
||||
val resetEpoch = 10_000_000_000L
|
||||
val now = resetEpoch - window + (window * 0.01f).toLong() // 1% elapsed
|
||||
assertNull(PaceCalc.compute(usedPct = 5f, resetEpoch = resetEpoch, windowMs = window, now = now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeReturnsNullForNonPositiveReset() {
|
||||
assertNull(PaceCalc.compute(usedPct = 50f, resetEpoch = 0L, windowMs = window))
|
||||
assertNull(PaceCalc.compute(usedPct = 50f, resetEpoch = -1L, windowMs = window))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun computeReturnsNullForNegativeUsedPct() {
|
||||
val resetEpoch = 10_000_000_000L
|
||||
val now = resetEpoch - window / 2
|
||||
assertNull(PaceCalc.compute(usedPct = -1f, resetEpoch = resetEpoch, windowMs = window, now = now))
|
||||
}
|
||||
|
||||
private fun assertEquals50(markerPct: Int) {
|
||||
// elapsedFraction 0.5 → (0.5*100).toInt() == 50.
|
||||
org.junit.Assert.assertEquals(50, markerPct)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user