HttpCtrl: set_stub_reply must not double-encode str/bytes bodies

The vendored 0.3.1 fork has:

response = Response(int(status), None, json.dumps(body), None, None)

— body is always passed through json.dumps(). This was presumably added to fix the opposite bug (dict bodies served raw to the wire — see MR !273 (merged) / D010_01_aux). But callers that already pass a serialised JSON string (e.g. via Convert JSON To String in JsonUtils.resource) now get their body double-encoded: a Python str of '[{"id":"X"}]' becomes the wire body '"[{\\"id\\":\\"X\\"}]"' — a JSON string wrapping the intended JSON array. The broker's response parser sees a String where it expects an Array, and the merge silently fails.

Conditional auto-serialisation handles both cases:

if not isinstance(body, (str, bytes)) and body is not None:
    body = json.dumps(body)
response = Response(int(status), None, body, None, None)

Verified: D011_02_exc_01 (which was failing as "missing $.speed") now passes; full distops sub-suite shows ~9 tests flipping from FAIL→PASS. The earlier D010_01_aux test-side workaround (Evaluate json.dumps( $entity_body)) remains harmless (re-json.dumps-ing a string just returns the same string), so MR !273 (merged) needs no follow-up.

Documented as testsuite-doubts.md #77 (closed).

Merge request reports

Loading
+11 −1
Changes for libraries/robotframework-httpctrl/src/HttpCtrl/__init__.py: 11 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -1090,7 +1090,10 @@ class Server:

        `status` [in] (int|string): HTTP status code for response that is used by server stub.

        `body` [in] (string|bytes): Response body that is used by server stub.
        `body` [in] (string|bytes|None or any JSON-serialisable Python object): Response body that is used by server stub.
            A `str` or `bytes` body is sent on the wire as-is — useful when the caller has already serialised
            JSON (e.g. via `Convert JSON To String`). Any other non-`None` value is run through `json.dumps()`
            so callers can also pass a Python `dict` / `list` directly. `None` sends an empty body.

        Example how to set stub to reply automatically to request `POST` `/api/v1/request` by status `200`.

@@ -1118,6 +1121,13 @@ class Server:
            raise AssertionError(message_error)
        
        criteria = HttpStubCriteria(method=method, url=url)
        # Auto-serialise non-string bodies (e.g. Python dicts / lists) so callers
        # can pass either a Python data structure OR an already-serialised JSON
        # string. The previous unconditional `json.dumps(body)` double-encoded
        # string bodies — a stub body of `'[{"id":"X"}]'` (e.g. from
        # `Convert JSON To String`) was sent on the wire as
        # `'"[{\\"id\\":\\"X\\"}]"'` and parsed by the client as a string, not
        # an array.
        if not isinstance(body, (str, bytes)) and body is not None:
            body = json.dumps(body)
        response = Response(int(status), None, body, None, None)
Loading