@@ -287,3 +287,206 @@ func BenchmarkRefCache_vs_Uncached(b *testing.B) {
287287 }
288288 })
289289}
290+
291+ // Tests for empty targetLocation bug fix
292+ func TestResolveAbsoluteReferenceUncached_EmptyTargetLocation_Success (t * testing.T ) {
293+ t .Parallel ()
294+
295+ tests := []struct {
296+ name string
297+ targetLocation string
298+ reference string
299+ expectedAbsoluteRef string
300+ description string
301+ }{
302+ {
303+ name : "empty_target_with_empty_reference" ,
304+ targetLocation : "" ,
305+ reference : "" ,
306+ expectedAbsoluteRef : "." ,
307+ description : "Empty target location should default to current directory" ,
308+ },
309+ {
310+ name : "empty_target_with_relative_reference" ,
311+ targetLocation : "" ,
312+ reference : "schemas/user.yaml" ,
313+ expectedAbsoluteRef : "schemas/user.yaml" ,
314+ description : "Empty target location with relative reference should resolve relative to current directory" ,
315+ },
316+ {
317+ name : "empty_target_with_absolute_file_reference" ,
318+ targetLocation : "" ,
319+ reference : "/absolute/path/schema.yaml" ,
320+ expectedAbsoluteRef : "/absolute/path/schema.yaml" ,
321+ description : "Empty target location with absolute file reference should use absolute path as-is" ,
322+ },
323+ {
324+ name : "empty_target_with_absolute_url_reference" ,
325+ targetLocation : "" ,
326+ reference : "https://example.com/schema.yaml" ,
327+ expectedAbsoluteRef : "https://example.com/schema.yaml" ,
328+ description : "Empty target location with absolute URL reference should use URL as-is" ,
329+ },
330+ {
331+ name : "empty_target_with_fragment_reference" ,
332+ targetLocation : "" ,
333+ reference : "#/components/schemas/User" ,
334+ expectedAbsoluteRef : "." ,
335+ description : "Empty target location with fragment reference should resolve to current directory" ,
336+ },
337+ {
338+ name : "empty_target_with_uri_and_fragment_reference" ,
339+ targetLocation : "" ,
340+ reference : "schemas/user.yaml#/User" ,
341+ expectedAbsoluteRef : "schemas/user.yaml" ,
342+ description : "Empty target location with URI and fragment should resolve URI relative to current directory" ,
343+ },
344+ }
345+
346+ for _ , tt := range tests {
347+ t .Run (tt .name , func (t * testing.T ) {
348+ t .Parallel ()
349+
350+ ref := Reference (tt .reference )
351+ result , err := resolveAbsoluteReferenceUncached (ref , tt .targetLocation )
352+
353+ require .NoError (t , err , tt .description )
354+ require .NotNil (t , result )
355+ assert .Equal (t , tt .expectedAbsoluteRef , result .AbsoluteReference , tt .description )
356+ assert .NotNil (t , result .Classification , "Classification should not be nil" )
357+ })
358+ }
359+ }
360+
361+ func TestRefCache_Resolve_EmptyTargetLocation_Success (t * testing.T ) {
362+ t .Parallel ()
363+ cache := & RefCache {}
364+
365+ t .Run ("empty_target_location_cached_correctly" , func (t * testing.T ) {
366+ t .Parallel ()
367+ ref := Reference ("schemas/user.yaml" )
368+
369+ // First call with empty target location
370+ result1 , err1 := cache .Resolve (ref , "" )
371+ require .NoError (t , err1 )
372+ require .NotNil (t , result1 )
373+ assert .Equal (t , "schemas/user.yaml" , result1 .AbsoluteReference )
374+
375+ // Second call with same parameters should return cached result
376+ result2 , err2 := cache .Resolve (ref , "" )
377+ require .NoError (t , err2 )
378+ require .NotNil (t , result2 )
379+ assert .Equal (t , result1 .AbsoluteReference , result2 .AbsoluteReference )
380+ assert .Equal (t , result1 .Classification .Type , result2 .Classification .Type )
381+
382+ // Results should be copies (not the same instance) to prevent mutation
383+ assert .NotSame (t , result1 , result2 , "Cached results should be copies to prevent mutation" )
384+ assert .Equal (t , result1 .AbsoluteReference , result2 .AbsoluteReference , "But content should be identical" )
385+ })
386+
387+ t .Run ("empty_vs_dot_target_location_different_cache_keys" , func (t * testing.T ) {
388+ t .Parallel ()
389+ ref := Reference ("test.yaml" )
390+
391+ // Call with empty target location (internally becomes ".")
392+ result1 , err1 := cache .Resolve (ref , "" )
393+ require .NoError (t , err1 )
394+ require .NotNil (t , result1 )
395+
396+ // Call with explicit "." target location
397+ result2 , err2 := cache .Resolve (ref , "." )
398+ require .NoError (t , err2 )
399+ require .NotNil (t , result2 )
400+
401+ // Both should have the same absolute reference result
402+ assert .Equal (t , result1 .AbsoluteReference , result2 .AbsoluteReference )
403+ assert .Equal (t , result1 .Classification .Type , result2 .Classification .Type )
404+
405+ // But they should be cached separately (different cache keys)
406+ // This is expected behavior since the cache key includes the original targetLocation
407+ assert .NotSame (t , result1 , result2 , "Different target locations should have separate cache entries" )
408+ })
409+ }
410+
411+ //nolint:paralleltest // This test uses global cache and cannot be parallel
412+ func TestResolveAbsoluteReferenceCached_EmptyTargetLocation_Global (t * testing.T ) {
413+ // Clear global cache before test
414+ ClearGlobalRefCache ()
415+
416+ ref := Reference ("schemas/user.yaml" )
417+
418+ // Resolve using global function with empty target location
419+ result1 , err := ResolveAbsoluteReferenceCached (ref , "" )
420+ require .NoError (t , err )
421+ assert .Equal (t , "schemas/user.yaml" , result1 .AbsoluteReference )
422+
423+ // Verify it's cached globally
424+ stats := GetRefCacheStats ()
425+ assert .Equal (t , int64 (1 ), stats .Size )
426+
427+ // Resolve again - should use cache
428+ result2 , err := ResolveAbsoluteReferenceCached (ref , "" )
429+ require .NoError (t , err )
430+ assert .Equal (t , result1 .AbsoluteReference , result2 .AbsoluteReference )
431+ assert .NotSame (t , result1 , result2 , "should return copies" )
432+
433+ // Clean up
434+ ClearGlobalRefCache ()
435+ }
436+
437+ func TestResolveAbsoluteReferenceUncached_EmptyTargetLocation_InMemoryDocuments (t * testing.T ) {
438+ t .Parallel ()
439+
440+ // Test scenarios that represent in-memory documents (uploaded files, database content, etc.)
441+ tests := []struct {
442+ name string
443+ reference string
444+ expected string
445+ description string
446+ }{
447+ {
448+ name : "in_memory_document_self_reference" ,
449+ reference : "" ,
450+ expected : "." ,
451+ description : "In-memory document referencing itself should resolve to current directory" ,
452+ },
453+ {
454+ name : "in_memory_document_relative_schema" ,
455+ reference : "components/schemas/User.yaml" ,
456+ expected : "components/schemas/User.yaml" ,
457+ description : "In-memory document with relative schema reference" ,
458+ },
459+ {
460+ name : "in_memory_document_json_pointer" ,
461+ reference : "#/components/schemas/User" ,
462+ expected : "." ,
463+ description : "In-memory document with JSON pointer reference" ,
464+ },
465+ {
466+ name : "in_memory_document_external_url" ,
467+ reference : "https://schemas.example.com/common.yaml" ,
468+ expected : "https://schemas.example.com/common.yaml" ,
469+ description : "In-memory document referencing external URL should preserve absolute URL" ,
470+ },
471+ {
472+ name : "in_memory_document_uri_with_fragment" ,
473+ reference : "components/schemas/User.yaml#/properties/name" ,
474+ expected : "components/schemas/User.yaml" ,
475+ description : "In-memory document with URI and fragment should resolve to the URI part only" ,
476+ },
477+ }
478+
479+ for _ , tt := range tests {
480+ t .Run (tt .name , func (t * testing.T ) {
481+ t .Parallel ()
482+
483+ ref := Reference (tt .reference )
484+ result , err := resolveAbsoluteReferenceUncached (ref , "" )
485+
486+ require .NoError (t , err , tt .description )
487+ require .NotNil (t , result )
488+ assert .Equal (t , tt .expected , result .AbsoluteReference , tt .description )
489+ assert .NotNil (t , result .Classification , "Classification should not be nil" )
490+ })
491+ }
492+ }
0 commit comments