Skip to content

UserAgent returns imaginary HTTP 404 response if connection failed #2296

@dboehmer

Description

@dboehmer

I am working on a HTTP API client based on Mojo::UserAgent and writing tests including handling HTTP connection problems. When making my mock server drop the connection $tx->res->to_string is a imaginary HTTP 404 response including HTTP headers.

HTTP/1.1 404 Not Found
Content-Length: 0
Date: Mon, 15 Dec 2025 08:43:51 GMT

This made debugging my app & tests quite hard and it feels a bit similar to AI hallucination. I see the Mojo::Message::Response has a default status of 404 and sets these headers in fix_headers(). Can Mojo::UserAgent be changed that it doesn’t return a Transaction with a response in this state?

A simplified test:

use Mojo::Base -signatures;
use Test2::V0;

use Mojo::UserAgent;
use Mojolicious::Lite;
use Test::Mojo;

my $abort_connection;
hook before_dispatch => sub ($c) {
    if ( $abort_connection )
    {    # abort immediately to simulate HTTP connection problem
        my $id  =  $c->tx->connection or return;
        Mojo::IOLoop->stream($id )->close;
    }
};

post '/' => sub ($c) {
        $c->render(status => 200, json => {});
};

my $t = Test::Mojo->new();
my $ua =  $t->ua;

ok my $tx = $ua->post('/');
diag $tx->res->to_string;    # correct representation of actual HTTP response
is $tx->res->body => '{}', "body";
is $tx->res->code => 200;

$abort_connection = 1;
ok $tx = $ua->post('/');
diag $tx->res->to_string;    # imaginary HTTP 404 response!
is $tx->res->body => '{}', "body"; # fails because body is empty string
is $tx->res->code => 200; # fails because code is undef

done_testing;

Test output:

$ prove -v t/abort.t 
t/abort.t .. 
# Seeded srand with seed '20251215' from local date.
[2025-12-15 09:43:51.87173] [4082754] [trace] [EXjANFplB4xQ] POST "/"
[2025-12-15 09:43:51.87209] [4082754] [trace] [EXjANFplB4xQ] Routing to a callback
[2025-12-15 09:43:51.87238] [4082754] [trace] [EXjANFplB4xQ] 200 OK (0.000643s, 1555.210/s)
ok 1
# HTTP/1.1 200 OK
# Content-Length: 2
# Content-Type: application/json;charset=UTF-8
# Date: Mon, 15 Dec 2025 08:43:51 GMT
# Server: Mojolicious (Perl)
# 
# {}
ok 2 - body
ok 3
[2025-12-15 09:43:51.87564] [4082754] [trace] [IT9qoVFF6kyK] POST "/"
[2025-12-15 09:43:51.87584] [4082754] [trace] [IT9qoVFF6kyK] Routing to a callback
[2025-12-15 09:43:51.87604] [4082754] [trace] [IT9qoVFF6kyK] 200 OK (0.000399s, 2506.266/s)
ok 4
# HTTP/1.1 404 Not Found
# Content-Length: 0
# Date: Mon, 15 Dec 2025 08:43:51 GMT
# 
not ok 5 - body

# Failed test 'body'
# at t/abort.t line 32.
# +-----+----+-------+
# | GOT | OP | CHECK |
# +-----+----+-------+
# |     | eq | {}    |
# +-----+----+-------+
(If this table is too small, you can use the TABLE_TERM_SIZE=### env var to set a larger size, detected size is '78')

not ok 6

# Failed test at t/abort.t line 33.
# +---------+-------+
# | GOT     | CHECK |
# +---------+-------+
# | <UNDEF> | 200   |
# +---------+-------+
(If this table is too small, you can use the TABLE_TERM_SIZE=### env var to set a larger size, detected size is '78')

1..6
# Looks like you failed 2 tests of 6.
Dubious, test returned 2 (wstat 512, 0x200)
Failed 2/6 subtests 

Test Summary Report
-------------------
t/abort.t (Wstat: 512 (exited 2) Tests: 6 Failed: 2)
  Failed tests:  5-6
  Non-zero exit status: 2
Files=1, Tests=6,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.30 cusr  0.03 csys =  0.36 CPU)
Result: FAIL

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions