@@ -597,3 +597,180 @@ 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+ {
662+ "rel" : "items" ,
663+ "href" : "https://localhost:443/collections/test/items" ,
664+ },
665+ ],
666+ [
667+ "http://localhost/stac/collections" ,
668+ "http://localhost/stac/" ,
669+ "https://localhost/stac/collections/test/items" ,
670+ ],
671+ ),
672+ # localhost:PORT with upstream path
673+ (
674+ "http://eoapi-stac:8080/api" ,
675+ "/stac" ,
676+ "localhost" ,
677+ [
678+ {"rel" : "self" , "href" : "http://localhost:8080/api/collections" },
679+ ],
680+ [
681+ "http://localhost/stac/collections" ,
682+ ],
683+ ),
684+ # Request host with port should still work (port removed in rewrite)
685+ (
686+ "http://eoapi-stac:8080" ,
687+ "/stac" ,
688+ "localhost:80" ,
689+ [
690+ {"rel" : "self" , "href" : "http://localhost:8080/collections" },
691+ ],
692+ [
693+ "http://localhost:80/stac/collections" ,
694+ ],
695+ ),
696+ ],
697+ )
698+ def test_transform_localhost_with_port (
699+ upstream_url , root_path , request_host , input_links , expected_links
700+ ):
701+ """Test transforming links with localhost:PORT (any port number)."""
702+ middleware = ProcessLinksMiddleware (
703+ app = None , upstream_url = upstream_url , root_path = root_path
704+ )
705+ request_scope = {
706+ "type" : "http" ,
707+ "path" : "/test" ,
708+ "headers" : [
709+ (b"host" , request_host .encode ()),
710+ (b"content-type" , b"application/json" ),
711+ ],
712+ }
713+
714+ data = {"links" : input_links }
715+ transformed = middleware .transform_json (data , Request (request_scope ))
716+
717+ for i , expected in enumerate (expected_links ):
718+ assert transformed ["links" ][i ]["href" ] == expected
719+
720+
721+ def test_localhost_with_port_preserves_other_hostnames ():
722+ """Test that links with other hostnames are not transformed."""
723+ middleware = ProcessLinksMiddleware (
724+ app = None ,
725+ upstream_url = "http://eoapi-stac:8080" ,
726+ root_path = "/stac" ,
727+ )
728+ request_scope = {
729+ "type" : "http" ,
730+ "path" : "/test" ,
731+ "headers" : [
732+ (b"host" , b"localhost" ),
733+ (b"content-type" , b"application/json" ),
734+ ],
735+ }
736+
737+ data = {
738+ "links" : [
739+ {"rel" : "external" , "href" : "http://example.com:8080/collections" },
740+ {"rel" : "other" , "href" : "http://other-host:3000/collections" },
741+ ]
742+ }
743+
744+ transformed = middleware .transform_json (data , Request (request_scope ))
745+
746+ # External hostnames should remain unchanged
747+ assert transformed ["links" ][0 ]["href" ] == "http://example.com:8080/collections"
748+ assert transformed ["links" ][1 ]["href" ] == "http://other-host:3000/collections"
749+
750+
751+ def test_localhost_with_port_upstream_service_name_still_works ():
752+ """Test that upstream service name matching still works."""
753+ middleware = ProcessLinksMiddleware (
754+ app = None ,
755+ upstream_url = "http://eoapi-stac:8080" ,
756+ root_path = "/stac" ,
757+ )
758+ request_scope = {
759+ "type" : "http" ,
760+ "path" : "/test" ,
761+ "headers" : [
762+ (b"host" , b"localhost" ),
763+ (b"content-type" , b"application/json" ),
764+ ],
765+ }
766+
767+ data = {
768+ "links" : [
769+ {"rel" : "self" , "href" : "http://eoapi-stac:8080/collections" },
770+ ]
771+ }
772+
773+ transformed = middleware .transform_json (data , Request (request_scope ))
774+
775+ # Upstream service name should be rewritten to request hostname
776+ assert transformed ["links" ][0 ]["href" ] == "http://localhost/stac/collections"
0 commit comments