Skip to content

fix: consistent Content-Type on the HTTP status assault (plain-text body declared as JSON) #69

Description

@ErwanLT

v0.4.0 -- Resilience verification, human or agent-driven

Found by an AI agent during a live test on the quarkus-goblin-demo application.

Problem

The httpStatus assault on HTTP_IN answers a plain-text body with a Content-Type: application/json header.

HttpStatusAssault aborts the request like this:

context.getRequestContext().abortWith(Response.status(config.getHttpStatusCode())
        .entity(config.getHttpStatusMessage())   // a plain String: "Service Unavailable (Goblin chaos)"
        .build());

No media type is set on the aborted response, so Quarkus REST negotiates it from the resource method -- i.e. from
its @Produces, or from the default for its return type. A resource returning a POJO is declared
application/json, so the client receives:

HTTP/1.1 503 Service Unavailable
Content-Type: application/json

Service Unavailable (Goblin chaos)

The body is not valid JSON. Any client (or agent) that parses the error payload fails on it, and a resilience test
asserting on the error shape cannot be trusted. It also means an agent that reports "the endpoint returned 503" is
reporting a response that a real service would never send.

Same defect, same fix, second call site: DependencyDegradationAssault aborts with
Response.status(503).entity("Dependency unavailable (Goblin chaos)").build() -- same missing type. The
INTERMITTENT profile (HTTP 500) and SLOW_FAILURE reach it too. Fixing only httpStatus would leave the identical
bug one assault over.

First step: pin the actual behaviour

The roadmap flagged this as "to be confirmed", and the code explains why the reported Content-Type appears, but
the negotiated type depends on the resource's @Produces and on the Quarkus REST default. Before choosing a fix, add a
failing test that pins what is actually emitted for (a) a resource returning a POJO, (b) a resource returning a
String, (c) a resource with an explicit @Produces, and keep its output as the reference for the fix.

Design decisions to make

  • Honest type, or matching body? Two coherent options:
    1. declare what we send -- set the media type explicitly to text/plain on the abort, since the entity is always
      a plain String. Simplest, and never lies. It is a user-visible change: clients that switch on the content type
      of the 503 would see text/plain instead of application/json.
    2. send what we declare -- when the resource produces JSON, emit a JSON body (e.g. {"message": "..."}), and
      text/plain otherwise. The client contract is preserved, and the body becomes machine-readable -- which is what
      makes assertions on the client-visible outcome (feat: post-assault assertions (resilience verification) #50) practical. It requires reading the resource's @Produces,
      which GoblinChaosFilter already does by reflection for targeting.
  • Recommend (2), with (1) as the fallback when the resource declares no JSON type. Rationale: a chaos tool exists to
    make failures realistic, and a 503 with an unparseable body is not what the real service sends. Option (2) also
    gives the post-assault assertions a real payload to assert on.
  • Which body shape? If (2), the JSON shape must be stable and documented -- {"message": "..."} is the obvious
    default, but an error code field would help agents classify the failure. Keep it minimal and documented.
  • Exception vs status assaults: the exception assault is a different path (an exception thrown before the endpoint
    runs, handled by Quarkus REST's mapper). It is out of scope here, but the fix should not create an inconsistency with
    it -- check what content type an assaulted-thrown exception produces and note it in the docs if the two differ.

Acceptance criteria

  • A test-first commit pins the current, wrong Content-Type for each affected call site, and the fix turns it green.
  • The HTTP status assault answers with a body that is valid for its declared content type, on every resource shape
    (POJO, String, explicit @Produces, no entity).
  • The dependency degradation assault is fixed at the same time -- the two are covered by the same test, and the fix is
    shared rather than duplicated per assault.
  • No regression on the happy paths: the response body and header assaults, the Markdown report and the Dev UI history
    are untouched by the change.
  • The chosen body/JSON shape is documented in assault-types.adoc, and the behaviour change gets a release-notes entry
    (clients may observe a different content type than before).
  • If (2) is chosen, an integration test proves an assaulted 503 on a JSON resource is parseable, and that a
    text/plain resource still gets text/plain.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpriority: mediumMedium priority

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions