diff --git a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt index 0020d534..2188d90e 100644 --- a/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt +++ b/feature/mutual/src/main/java/com/rtbishop/look4sat/feature/mutual/MutualViewModel.kt @@ -82,7 +82,10 @@ class MutualViewModel( private val settingsRepo: ISettingsRepo, // Injected so unit tests can run the pass computation on a test dispatcher; // production callers keep the CPU-bound pool default. - private val computeDispatcher: CoroutineDispatcher = Dispatchers.Default + private val computeDispatcher: CoroutineDispatcher = Dispatchers.Default, + // Injected so unit tests can pin the wall clock to the fixture TLE epoch and + // stay deterministic; production callers keep the real clock default. + private val nowMillis: () -> Long = System::currentTimeMillis ) : ViewModel() { private val _uiState = MutableStateFlow(MutualUiState()) @@ -303,7 +306,7 @@ class MutualViewModel( queryGeneration += 1 viewModelScope.launch { - val now = System.currentTimeMillis() + val now = nowMillis() // 转发器类型筛选: 使用硬编码 AMSAT Live 清单 (见 Sources): // FM = {SO-50, ISS ZARYA, AO-123}, Linear = {RS-44, FO-29, // AO-7, AO-73, JO-97}. 不再依赖 AMSAT 网页同步: 任何网络下都 diff --git a/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/MutualViewModelTest.kt b/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/MutualViewModelTest.kt index 8077b90c..d3942037 100644 --- a/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/MutualViewModelTest.kt +++ b/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/MutualViewModelTest.kt @@ -53,7 +53,9 @@ class MutualViewModelTest { ): MutualViewModel = MutualViewModel( satelliteRepo = FakeSatelliteRepo(satellites, passes), settingsRepo = FakeSettingsRepo(position), - computeDispatcher = StandardTestDispatcher(mainDispatcherRule.dispatcher.scheduler) + computeDispatcher = StandardTestDispatcher(mainDispatcherRule.dispatcher.scheduler), + // Pin the clock to the fixture base so results never depend on wall time. + nowMillis = { TestOrbits.FIXTURE_BASE } ) private fun TestScope.queryAndSettle(vm: MutualViewModel) { @@ -219,7 +221,7 @@ class MutualViewModelTest { assertTrue(state.mutualPasses.all { it.catNum == 25544 }) assertTrue(state.mutualPasses.all { it.name == "ISS (ZARYA)" }) // AOS/LOS within the searched horizon - val now = System.currentTimeMillis() + val now = TestOrbits.FIXTURE_BASE assertTrue(state.mutualPasses.all { it.startTime >= now - 120_000L }) assertTrue(state.mutualPasses.all { it.endTime <= now + 12 * 3600_000L + 120_000L }) } diff --git a/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/TestOrbits.kt b/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/TestOrbits.kt index 8acce29a..743bd1f0 100644 --- a/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/TestOrbits.kt +++ b/feature/mutual/src/test/java/com/rtbishop/look4sat/feature/mutual/TestOrbits.kt @@ -48,6 +48,15 @@ object TestOrbits { ) ) + /** + * Fixed wall-clock base for every fixture computation: 2024-03-10T00:00Z, + * ~18 h after [ISS]'s TLE epoch, so SGP4 propagates an accurate, healthy + * orbit. Tests pin the ViewModel clock to this value as well + * (MutualViewModel.nowMillis), which makes pass windows deterministic + * instead of rotting as the fixture ages against System.currentTimeMillis(). + */ + val FIXTURE_BASE: Long = 1710028800000L + /** Guangzhou (default BA7OPF grid OL62/OL63 area). */ val GUANGZHOU = GeoPos(23.13, 113.26) @@ -85,7 +94,7 @@ object TestOrbits { fun findPassWindows( sat: OrbitalObject = ISS, pos: GeoPos = GUANGZHOU, - time: Long = System.currentTimeMillis(), + time: Long = FIXTURE_BASE, hoursAhead: Int = 12 ): List { if (!sat.willBeSeen(pos)) return emptyList()