@@ -226,12 +226,12 @@ static string CreateTempDir()
226226 }
227227
228228 [ Fact ]
229- public void Remote_stale_2week_re_download_refreshes_mtime_immediate_followup_does_not ( )
229+ public void Remote_ref_always_revalidates_via_etag_root_dir_touched_source_files_not ( )
230230 {
231231 var scratch = @"C:\Users\kzu\AppData\Local\Temp\grok-goal-4376de8fe197\implementer" ;
232232 Directory . CreateDirectory ( scratch ) ;
233233 var remoteRef = "gist.github.com/kzu/0ac826dc7de666546aaedd38e5965381" ;
234- var logPath = Path . Combine ( scratch , "stale-download .log" ) ;
234+ var logPath = Path . Combine ( scratch , "remote-etag .log" ) ;
235235
236236 try
237237 {
@@ -243,7 +243,7 @@ public void Remote_stale_2week_re_download_refreshes_mtime_immediate_followup_do
243243 Assert . Equal ( 0 , warm . ExitCode ) ;
244244 Assert . Contains ( "run.cs" , warm . Output ) ;
245245
246- // Locate the downloaded source cs for this ref (robust search, not brittle FirstOrDefault + loose contains)
246+ // Locate the downloaded source cs and its bundle root dir for this ref
247247 var goRoot = GetTempRoot ( ) ;
248248 var gistCandidates = Directory . GetDirectories ( goRoot , "*0ac826dc7de666546aaedd38e5965381*" , SearchOption . AllDirectories )
249249 . OrderByDescending ( d => d . Length )
@@ -256,36 +256,54 @@ public void Remote_stale_2week_re_download_refreshes_mtime_immediate_followup_do
256256 Assert . False ( string . IsNullOrEmpty ( sourceCs ) , "expected source .cs under download" ) ;
257257 var srcInfo = new FileInfo ( sourceCs ) ;
258258
259- // Artificially age it >14 days
260- var staleTime = DateTime . UtcNow . AddDays ( - 20 ) ;
261- srcInfo . LastWriteTimeUtc = staleTime ;
262- Assert . True ( ( DateTime . UtcNow - srcInfo . LastWriteTimeUtc ) . TotalDays > 14 ) ;
259+ // The bundle root (unzipped ref dir) is the gistRoot we found (contains the sources for this gist download).
260+ var bundleRoot = gistRoot ;
261+ var rootInfo = new DirectoryInfo ( bundleRoot ) ;
262+ var rootMtimeBefore = rootInfo . LastWriteTimeUtc ;
263263
264- // Re-invoke: should detect stale, (re)download/touch mtime to recent, succeed with marker
265- var ( exitStale , outStale ) = RunGo ( remoteRef , "--" , "-v:q" ) ;
266- File . WriteAllText ( logPath , "STALE-RE-INVOKE:\n " + outStale ) ;
267- Assert . Equal ( 0 , exitStale ) ;
268- Assert . Contains ( "run.cs" , outStale ) ;
264+ // Second run (rapid): always performs ETag check; on 304 we touch *only* the root dir,
265+ // individual source files are not touched.
266+ var mtimeBefore = srcInfo . LastWriteTimeUtc ;
267+ var ( exit2 , out2 ) = RunGo ( remoteRef , "--" , "-v:q" ) ;
268+ File . WriteAllText ( logPath , "SECOND-RUN:\n " + out2 ) ;
269+ Assert . Equal ( 0 , exit2 ) ;
270+ Assert . Contains ( "run.cs" , out2 ) ;
269271
270272 srcInfo . Refresh ( ) ;
271- var afterStaleMtime = srcInfo . LastWriteTimeUtc ;
272- File . AppendAllText ( logPath , $ "\n SOURCE-MTIME-AFTER-STALE: { afterStaleMtime : O} \n ") ;
273- Assert . True ( ( DateTime . UtcNow - afterStaleMtime ) . TotalMinutes < 5 , "source mtime should be refreshed to recent by re-dl" ) ;
273+ rootInfo . Refresh ( ) ;
274+ var mtimeAfter = srcInfo . LastWriteTimeUtc ;
275+ var rootMtimeAfter = rootInfo . LastWriteTimeUtc ;
276+
277+ File . AppendAllText ( logPath , $ "\n SOURCE-MTIME-AFTER: { mtimeAfter : O} \n ROOT-MTIME-AFTER: { rootMtimeAfter : O} \n ") ;
278+
279+ // Source file mtime must not have been updated by resolver (no more per-file touch on 304 or extract).
280+ var fileDelta = ( mtimeAfter - mtimeBefore ) . TotalSeconds ;
281+ Assert . True ( fileDelta < 2.0 , $ "source file mtime must be unchanged on revalidation (delta={ fileDelta } s)") ;
274282
275- // Immediate follow-up: must NOT update the source mtime (plan requirement)
276- var mtimeBeforeImmediate = srcInfo . LastWriteTimeUtc ;
283+ // Root download dir *must* be touched to mark usage for cleanup.
284+ Assert . True ( rootMtimeAfter >= rootMtimeBefore , "bundle root dir mtime should be updated on use" ) ;
285+
286+ // Immediate third run: same, no source mtime change.
287+ var mtimeBeforeImm = srcInfo . LastWriteTimeUtc ;
288+ var rootBeforeImm = rootInfo . LastWriteTimeUtc ;
277289 var ( exitImm , outImm ) = RunGo ( remoteRef , "--" , "-v:q" ) ;
278- File . AppendAllText ( logPath , "IMMEDIATE-FOLLOWUP :\n " + outImm ) ;
290+ File . AppendAllText ( logPath , "IMMEDIATE:\n " + outImm ) ;
279291 Assert . Equal ( 0 , exitImm ) ;
280- Assert . Contains ( "run.cs" , outImm ) ;
281292
282293 srcInfo . Refresh ( ) ;
283- var mtimeAfterImmediate = srcInfo . LastWriteTimeUtc ;
284- File . AppendAllText ( logPath , $ "SOURCE-MTIME-AFTER-IMMEDIATE: { mtimeAfterImmediate : O} \n ") ;
285-
286- var deltaSeconds = ( mtimeAfterImmediate - mtimeBeforeImmediate ) . TotalSeconds ;
287- // The immediate run (cache hit, no dl) must not have updated the source mtime via our logic or observable change.
288- Assert . True ( deltaSeconds < 2.0 , $ "immediate follow-up must not update source mtime (delta={ deltaSeconds } s)") ;
294+ rootInfo . Refresh ( ) ;
295+ var mtimeAfterImm = srcInfo . LastWriteTimeUtc ;
296+ var rootAfterImm = rootInfo . LastWriteTimeUtc ;
297+ File . AppendAllText ( logPath , $ "SOURCE-MTIME-IMM: { mtimeAfterImm : O} \n ROOT-MTIME-IMM: { rootAfterImm : O} \n ") ;
298+
299+ Assert . True ( ( mtimeAfterImm - mtimeBeforeImm ) . TotalSeconds < 2.0 , "immediate must not change source file mtime" ) ;
300+ Assert . True ( rootAfterImm > rootBeforeImm || rootAfterImm >= rootBeforeImm , "root dir touched on immediate use too" ) ;
301+
302+ // --force should succeed (forces full fetch, bypassing any conditional).
303+ var ( exitForce , outForce ) = RunGo ( "--force" , remoteRef , "--" , "-v:q" ) ;
304+ File . AppendAllText ( logPath , "FORCE:\n " + outForce ) ;
305+ Assert . Equal ( 0 , exitForce ) ;
306+ Assert . Contains ( "run.cs" , outForce ) ;
289307 }
290308 finally
291309 {
0 commit comments