@@ -13,6 +13,9 @@ internal class SessionTests
1313 // Exception thrown by the ThrowingTrack sabotage delegate across four scenarios.
1414 private const string TrackExplodeMessage = "track explode" ;
1515
16+ // Fixed virtual "now" used as the starting point for every Session lifecycle test.
17+ private static readonly DateTime FixedNowUtc = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
18+
1619 private List < ( string name , Dictionary < string , object > props ) > _events ;
1720
1821 [ SetUp ]
@@ -59,7 +62,7 @@ public void Start_GeneratesUniqueSessionId()
5962 [ Test ]
6063 public void End_FiresSessionEnd_WithDuration ( )
6164 {
62- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
65+ var now = FixedNowUtc ;
6366 using var session = new Session ( MockTrack , getUtcNow : ( ) => now ) ;
6467 session . Start ( ) ;
6568 now = now . AddSeconds ( 2 ) ;
@@ -136,7 +139,7 @@ void Track(string name, Dictionary<string, object> props)
136139 [ Test ]
137140 public void Pause_ThenResume_ShortPause_ContinuesSession ( )
138141 {
139- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
142+ var now = FixedNowUtc ;
140143 using var session = new Session ( MockTrack , getUtcNow : ( ) => now ) ;
141144 session . Start ( ) ;
142145 var originalId = session . SessionId ;
@@ -155,7 +158,7 @@ public void Pause_ThenResume_LongPause_StartsNewSession()
155158 {
156159 // Uses the injected clock to jump past the 30-second threshold
157160 // without sleeping.
158- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
161+ var now = FixedNowUtc ;
159162 using var session = new Session ( MockTrack , getUtcNow : ( ) => now ) ;
160163 session . Start ( ) ;
161164 var id1 = session . SessionId ;
@@ -182,7 +185,7 @@ public void Pause_CalledTwice_SecondCallIsNoOp()
182185 // _pausedAt jump to the second Pause and this test reports a
183186 // larger duration (over-crediting engagement by the double-pause
184187 // gap).
185- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
188+ var now = FixedNowUtc ;
186189 DateTime Clock ( ) => now ;
187190
188191 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
@@ -233,7 +236,7 @@ public void Resume_NegativePauseDuration_ClampsAccumulatorToZero()
233236 // artificial engagement credit. Sabotage: removing the clamp
234237 // would let this test report a duration that exceeds the
235238 // wall-clock window, which the assertion below pins.
236- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
239+ var now = FixedNowUtc ;
237240 DateTime Clock ( ) => now ;
238241
239242 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
@@ -270,7 +273,7 @@ public void End_ClockRewindsSinceStart_ClampsDurationToZero()
270273 // because it only fires when _pausedAt was set. Sabotage:
271274 // removing `if (engagedSeconds < 0) return 0;` would let this
272275 // test report -3 instead of 0.
273- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
276+ var now = FixedNowUtc ;
274277 DateTime Clock ( ) => now ;
275278
276279 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
@@ -296,7 +299,7 @@ public void End_ClockRewindsWhilePaused_DoesNotInflateDuration()
296299 // (the final engagedSeconds ≥ 0 clamp only catches negatives,
297300 // not over-credit). Sabotage: removing the livePause clamp lets
298301 // this test report 15s instead of the ≤ 5s wall-clock window.
299- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
302+ var now = FixedNowUtc ;
300303 DateTime Clock ( ) => now ;
301304
302305 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
@@ -318,7 +321,7 @@ public void End_ClockRewindsWhilePaused_DoesNotInflateDuration()
318321 public void End_AfterShortPause_ReportsDurationMinusPause ( )
319322 {
320323 // 10 seconds session, 3 seconds paused inside it → 7 seconds engaged.
321- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
324+ var now = FixedNowUtc ;
322325 DateTime Clock ( ) => now ;
323326
324327 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
@@ -343,7 +346,7 @@ public void End_AfterShortPause_ReportsDurationMinusPause()
343346 public void End_WhilePaused_ExcludesInFlightPauseFromDuration ( )
344347 {
345348 // Session running 5s, then paused for 2s and ended without resuming.
346- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
349+ var now = FixedNowUtc ;
347350 DateTime Clock ( ) => now ;
348351
349352 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
@@ -371,7 +374,7 @@ public void End_AfterExtendedPauseRollover_ReportsPrePauseDuration()
371374 // for the extended-pause rollover path: a naive duration that
372375 // forgot to credit the in-flight pause before End fires would
373376 // ship wall-clock seconds and break engagement dashboards.
374- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
377+ var now = FixedNowUtc ;
375378 DateTime Clock ( ) => now ;
376379
377380 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
@@ -393,7 +396,7 @@ public void End_AfterExtendedPauseRollover_ReportsPrePauseDuration()
393396 public void Heartbeat_AfterShortPause_ReportsPauseAdjustedDuration ( )
394397 {
395398 // Engaged 6s, paused 2s, resumed, then heartbeat → 6 s engaged.
396- var now = new DateTime ( 2026 , 4 , 20 , 12 , 0 , 0 , DateTimeKind . Utc ) ;
399+ var now = FixedNowUtc ;
397400 DateTime Clock ( ) => now ;
398401
399402 using var session = new Session ( MockTrack , getUtcNow : Clock ) ;
0 commit comments