@@ -389,6 +389,111 @@ func TestLoadOperationsFromCache(t *testing.T) {
389389 }
390390}
391391
392+ func TestLoadOperationSetFromCacheAcceptsLegacyLocalSpecFileSecondPrecisionModTime (t * testing.T ) {
393+ dir := t .TempDir ()
394+ specPath := filepath .Join (dir , "spec.yaml" )
395+ raw := []byte (`{"openapi":"3.1.0","info":{"title":"Test","version":"1.0.0"},"paths":{"/items":{"get":{"operationId":"listItems","responses":{"200":{"description":"OK"}}}}}}` )
396+ if err := os .WriteFile (specPath , raw , 0o600 ); err != nil {
397+ t .Fatalf ("write spec: %v" , err )
398+ }
399+ mtime := time .Unix (1700000000 , 987654321 )
400+ if err := os .Chtimes (specPath , mtime , mtime ); err != nil {
401+ t .Fatalf ("chtimes: %v" , err )
402+ }
403+ info , err := os .Stat (specPath )
404+ if err != nil {
405+ t .Fatalf ("stat spec: %v" , err )
406+ }
407+ if info .ModTime ().Nanosecond () == 0 {
408+ t .Skip ("filesystem does not preserve subsecond mtimes" )
409+ }
410+ loaded , err := load ("application/json" , raw , DefaultLoaders ())
411+ if err != nil {
412+ t .Fatalf ("load: %v" , err )
413+ }
414+ set , err := loaded .OperationSet (OperationOptions {})
415+ if err != nil {
416+ t .Fatalf ("operation set: %v" , err )
417+ }
418+
419+ entry := & cacheEntry {
420+ Version : "v2" ,
421+ FetchedAt : info .ModTime ().Add (time .Minute ),
422+ ExpiresAt : time .Now ().Add (time .Hour ),
423+ Spec : cachedRaw {
424+ ContentType : "application/json" ,
425+ Raw : raw ,
426+ LocalPath : specPath ,
427+ },
428+ SpecFiles : []cachedSpecFile {{
429+ Source : specPath ,
430+ Local : true ,
431+ Path : specPath ,
432+ ModTime : info .ModTime ().Truncate (time .Second ),
433+ Size : info .Size (),
434+ }},
435+ }
436+ entry .upsertOperationSet (OperationOptions {}, set )
437+ if err := writeCache (dir , "testapi" , entry ); err != nil {
438+ t .Fatalf ("writeCache: %v" , err )
439+ }
440+
441+ got , _ , ok := LoadOperationSetFromCacheStatus (dir , "testapi" , "v2" , []string {specPath }, OperationOptions {}, true )
442+ if ! ok {
443+ t .Fatal ("expected operations cache hit" )
444+ }
445+ if len (got .Operations ) != 1 || got .Operations [0 ].ID != "listItems" {
446+ t .Fatalf ("unexpected operations: %#v" , got .Operations )
447+ }
448+ }
449+
450+ func TestStoreSpecInCachePreservesLocalSpecFileNanosecondModTime (t * testing.T ) {
451+ dir := t .TempDir ()
452+ specPath := filepath .Join (dir , "spec.yaml" )
453+ raw := []byte (`{"openapi":"3.1.0","info":{"title":"Test","version":"1.0.0"},"paths":{"/items":{"get":{"operationId":"listItems","responses":{"200":{"description":"OK"}}}}}}` )
454+ if err := os .WriteFile (specPath , raw , 0o600 ); err != nil {
455+ t .Fatalf ("write spec: %v" , err )
456+ }
457+ mtime := time .Unix (1700000000 , 456789123 )
458+ if err := os .Chtimes (specPath , mtime , mtime ); err != nil {
459+ t .Fatalf ("chtimes: %v" , err )
460+ }
461+ info , err := os .Stat (specPath )
462+ if err != nil {
463+ t .Fatalf ("stat spec: %v" , err )
464+ }
465+ if info .ModTime ().Nanosecond () == 0 {
466+ t .Skip ("filesystem does not preserve subsecond mtimes" )
467+ }
468+ apiSpec , err := OpenAPILoader {}.Load (raw )
469+ if err != nil {
470+ t .Fatalf ("load: %v" , err )
471+ }
472+
473+ if err := StoreSpecInCache (dir , "testapi" , "v2" , apiSpec , []string {specPath }, OperationOptions {}, time .Hour ); err != nil {
474+ t .Fatalf ("StoreSpecInCache: %v" , err )
475+ }
476+
477+ entry , ok := readCacheEntry (dir , "testapi" , "v2" , true )
478+ if ! ok {
479+ t .Fatal ("expected cache entry" )
480+ }
481+ if len (entry .SpecFiles ) != 1 {
482+ t .Fatalf ("SpecFiles len = %d, want 1" , len (entry .SpecFiles ))
483+ }
484+ if got , want := entry .SpecFiles [0 ].ModTimeUnixNano , info .ModTime ().UnixNano (); got != want {
485+ t .Fatalf ("cached ModTimeUnixNano = %d, want %d" , got , want )
486+ }
487+
488+ got , _ , ok := LoadOperationSetFromCacheStatus (dir , "testapi" , "v2" , []string {specPath }, OperationOptions {}, true )
489+ if ! ok {
490+ t .Fatal ("expected operations cache hit" )
491+ }
492+ if len (got .Operations ) != 1 || got .Operations [0 ].ID != "listItems" {
493+ t .Fatalf ("unexpected operations: %#v" , got .Operations )
494+ }
495+ }
496+
392497func TestLoadOperationSetFromCacheStatusAllowsStaleRemoteMetadata (t * testing.T ) {
393498 dir := t .TempDir ()
394499 raw := []byte (`{"openapi":"3.1.0","info":{"title":"Test","version":"1.0.0"},"paths":{"/items":{"get":{"operationId":"listItems","responses":{"200":{"description":"OK"}}}}}}` )
0 commit comments