HttpCtrl: __is_satisfy must rstrip trailing slash from BOTH stub and request URLs

HttpStubContainer.__is_satisfy only stripped the trailing / from the request side of the URL comparison, not from the stub side. A stub registered with a trailing / — e.g.

Set Stub Reply    POST    /ngsi-ld/v1/entities/${entity_id}/attrs/    204

could therefore never match an inbound request whose path also ended with /. The mock then silently dropped the request and the broker under test logged the forward as a transport failure (Connection closed by peer).

This MR makes rstrip("/") apply to BOTH operands at the three comparison sites:

  • GET branch with ? in the stub URL.
  • Non-GET branch with ? in the request URL.
  • Final fallback equality check.

A stub URL without a trailing / still matches a request with trailing / (pre-existing behaviour); the new behaviour is just that a stub URL WITH a trailing / also matches.

Surfaced by

D003_01_inc — swBroker registers the speed/isParked append, forwards POST /ngsi-ld/v1/entities/{id}/attrs/ to the CSR, the mock dropped the connection without responding because of the mismatch, and the broker returned 207 Multi-Status instead of the expected 204.

With this fix the forward matches the stub, the mock replies 204, and the broker returns 204.

Likely scope

At least these tests register stubs with a trailing /attrs/ and are expected to benefit:

  • D003_01_exc / D003_01_inc / D003_01_red
  • D003_02_exc / D003_02_inc / D003_02_red
  • D004_01_red

Note

This is the vendored 0.3.1 fork under libraries/robotframework-httpctrl/. The same bug exists upstream (Andrei Novikov 0.3.1+). If the maintainer intends to drop the fork in favour of upstream + a contrib (see !277 (closed)'s substring matcher), this fix would need to be upstreamed too — or the upstream bump would carry the bug forward.

Merge request reports

Loading
+6 −4
Changes for libraries/robotframework-httpctrl/src/HttpCtrl/http_stub.py: 6 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -85,9 +85,11 @@ class HttpStubContainer(metaclass=Singleton):
                if '?' in stub.criteria.url:
                    stub_url_components = stub.criteria.url.split('?')

                    # the last slash should be removed
                    # the last slash should be removed (on BOTH sides — a stub
                    # registered with a trailing `/` must still match a request
                    # whose path also ends with `/`)
                    criteria_url = criteria_url_components[0].rstrip("/")
                    if criteria_url != stub_url_components[0]:
                    if criteria_url != stub_url_components[0].rstrip("/"):
                        return False

                    # extraction of attributes from the response body (only works when response body is a dictionary)
@@ -127,7 +129,7 @@ class HttpStubContainer(metaclass=Singleton):
            
            # if the method is not GET, we should ignore parameters and check if the url is the same
            else:
                if stub.criteria.url == criteria_url_components[0].rstrip("/"):
                if stub.criteria.url.rstrip("/") == criteria_url_components[0].rstrip("/"):
                    # if the request is a query via POST, we should have a specific check
                    if stub.criteria.url == "/ngsi-ld/v1/entityoperations/query":
                        response_body = json.loads(stub.response.get_body())
@@ -143,7 +145,7 @@ class HttpStubContainer(metaclass=Singleton):
                            return False
                    return True
        
        if stub.criteria.url == criteria.url.rstrip("/"):
        if stub.criteria.url.rstrip("/") == criteria.url.rstrip("/"):
            # if the request is a query via POST, we should have a specific check
            if stub.criteria.url == "/ngsi-ld/v1/entityoperations/query":
                response_body = json.loads(stub.response.get_body())
Loading