@@ -597,3 +597,177 @@ def test_transform_with_forwarded_headers(headers, expected_base_url):
597597 # but not include the forwarded path in the response URLs
598598 assert transformed ["links" ][0 ]["href" ] == f"{ expected_base_url } /proxy/collections"
599599 assert transformed ["links" ][1 ]["href" ] == f"{ expected_base_url } /proxy"
600+
601+
602+ @pytest .mark .parametrize (
603+ "upstream_url,root_path,request_host,input_links,expected_links" ,
604+ [
605+ # Basic localhost:PORT rewriting (common port 8080)
606+ (
607+ "http://eoapi-stac:8080" ,
608+ "/stac" ,
609+ "localhost" ,
610+ [
611+ {"rel" : "data" , "href" : "http://localhost:8080/collections" },
612+ ],
613+ [
614+ "http://localhost/stac/collections" ,
615+ ],
616+ ),
617+ # Standard HTTP port
618+ (
619+ "http://eoapi-stac:8080" ,
620+ "/stac" ,
621+ "localhost" ,
622+ [
623+ {"rel" : "self" , "href" : "http://localhost:80/collections" },
624+ ],
625+ [
626+ "http://localhost/stac/collections" ,
627+ ],
628+ ),
629+ # HTTPS port
630+ (
631+ "http://eoapi-stac:8080" ,
632+ "/stac" ,
633+ "localhost" ,
634+ [
635+ {"rel" : "self" , "href" : "https://localhost:443/collections" },
636+ ],
637+ [
638+ "https://localhost/stac/collections" ,
639+ ],
640+ ),
641+ # Arbitrary port
642+ (
643+ "http://eoapi-stac:8080" ,
644+ "/stac" ,
645+ "localhost" ,
646+ [
647+ {"rel" : "self" , "href" : "http://localhost:3000/collections" },
648+ ],
649+ [
650+ "http://localhost/stac/collections" ,
651+ ],
652+ ),
653+ # Multiple links with different ports
654+ (
655+ "http://eoapi-stac:8080" ,
656+ "/stac" ,
657+ "localhost" ,
658+ [
659+ {"rel" : "self" , "href" : "http://localhost:8080/collections" },
660+ {"rel" : "root" , "href" : "http://localhost:80/" },
661+ {"rel" : "items" , "href" : "https://localhost:443/collections/test/items" },
662+ ],
663+ [
664+ "http://localhost/stac/collections" ,
665+ "http://localhost/stac/" ,
666+ "https://localhost/stac/collections/test/items" ,
667+ ],
668+ ),
669+ # localhost:PORT with upstream path
670+ (
671+ "http://eoapi-stac:8080/api" ,
672+ "/stac" ,
673+ "localhost" ,
674+ [
675+ {"rel" : "self" , "href" : "http://localhost:8080/api/collections" },
676+ ],
677+ [
678+ "http://localhost/stac/collections" ,
679+ ],
680+ ),
681+ # Request host with port should still work (port removed in rewrite)
682+ (
683+ "http://eoapi-stac:8080" ,
684+ "/stac" ,
685+ "localhost:80" ,
686+ [
687+ {"rel" : "self" , "href" : "http://localhost:8080/collections" },
688+ ],
689+ [
690+ "http://localhost:80/stac/collections" ,
691+ ],
692+ ),
693+ ],
694+ )
695+ def test_transform_localhost_with_port (
696+ upstream_url , root_path , request_host , input_links , expected_links
697+ ):
698+ """Test transforming links with localhost:PORT (any port number)."""
699+ middleware = ProcessLinksMiddleware (
700+ app = None , upstream_url = upstream_url , root_path = root_path
701+ )
702+ request_scope = {
703+ "type" : "http" ,
704+ "path" : "/test" ,
705+ "headers" : [
706+ (b"host" , request_host .encode ()),
707+ (b"content-type" , b"application/json" ),
708+ ],
709+ }
710+
711+ data = {"links" : input_links }
712+ transformed = middleware .transform_json (data , Request (request_scope ))
713+
714+ for i , expected in enumerate (expected_links ):
715+ assert transformed ["links" ][i ]["href" ] == expected
716+
717+
718+ def test_localhost_with_port_preserves_other_hostnames ():
719+ """Test that links with other hostnames are not transformed."""
720+ middleware = ProcessLinksMiddleware (
721+ app = None ,
722+ upstream_url = "http://eoapi-stac:8080" ,
723+ root_path = "/stac" ,
724+ )
725+ request_scope = {
726+ "type" : "http" ,
727+ "path" : "/test" ,
728+ "headers" : [
729+ (b"host" , b"localhost" ),
730+ (b"content-type" , b"application/json" ),
731+ ],
732+ }
733+
734+ data = {
735+ "links" : [
736+ {"rel" : "external" , "href" : "http://example.com:8080/collections" },
737+ {"rel" : "other" , "href" : "http://other-host:3000/collections" },
738+ ]
739+ }
740+
741+ transformed = middleware .transform_json (data , Request (request_scope ))
742+
743+ # External hostnames should remain unchanged
744+ assert transformed ["links" ][0 ]["href" ] == "http://example.com:8080/collections"
745+ assert transformed ["links" ][1 ]["href" ] == "http://other-host:3000/collections"
746+
747+
748+ def test_localhost_with_port_upstream_service_name_still_works ():
749+ """Test that upstream service name matching still works."""
750+ middleware = ProcessLinksMiddleware (
751+ app = None ,
752+ upstream_url = "http://eoapi-stac:8080" ,
753+ root_path = "/stac" ,
754+ )
755+ request_scope = {
756+ "type" : "http" ,
757+ "path" : "/test" ,
758+ "headers" : [
759+ (b"host" , b"localhost" ),
760+ (b"content-type" , b"application/json" ),
761+ ],
762+ }
763+
764+ data = {
765+ "links" : [
766+ {"rel" : "self" , "href" : "http://eoapi-stac:8080/collections" },
767+ ]
768+ }
769+
770+ transformed = middleware .transform_json (data , Request (request_scope ))
771+
772+ # Upstream service name should be rewritten to request hostname
773+ assert transformed ["links" ][0 ]["href" ] == "http://localhost/stac/collections"
0 commit comments