Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,15 @@ func AssertBodyEqual(expContent string) Assertion {
return err
}

if len(body) == 0 {
return fmt.Errorf("body: expected %q, missing", expContent)
}

if c := string(body); expContent != c {
// "missing" reads better than `got ""` for the common case of an
// empty body where content was expected, but it is a wording
// choice inside the failure -- deciding the verdict on it is what
// made --assert-body-eq '' impossible to satisfy (#22).
if len(body) == 0 {
return fmt.Errorf("body: expected %q, missing", expContent)
}

return fmt.Errorf("body: expected %q, got %q", expContent, c)
}

Expand All @@ -166,11 +170,14 @@ func AssertBodyMatch(expPattern string) (Assertion, error) {
return err
}

if len(body) == 0 {
return fmt.Errorf("body: expected to match %q, missing", expPattern)
}

if c := string(body); !re.MatchString(c) {
// As above: an empty body is a legitimate subject for a pattern.
// `^$`, `.*` and `\A\z` all match it, and none of them could pass
// while emptiness was checked before the pattern was.
if len(body) == 0 {
return fmt.Errorf("body: expected to match %q, missing", expPattern)
}

return fmt.Errorf("body: expected to match %q, got %q", expPattern, c)
}

Expand Down
51 changes: 51 additions & 0 deletions assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,57 @@ func Test_AssertBody(t *testing.T) {
}
}

// Test_AssertBody_emptyIsAssertable covers the expectations that an empty body
// satisfies. They were unreachable while emptiness was checked before the
// comparison: the guard existed to word the failure nicely and ended up
// deciding it (#22).
func Test_AssertBody_emptyIsAssertable(t *testing.T) {
t.Parallel()

// Patterns an empty body legitimately matches. `.*` is the one a user is
// most likely to reach for, `^$` the one they mean.
patterns := []string{"^$", ".*", `\A\z`, ""}

t.Run("equal to the empty string", func(t *testing.T) {
res := &httpResponse{BodyBytes: []byte{}}
checkErr(t, "equal", AssertBodyEqual("")(res), "")

// And a nil body, which is what a 204 produces.
checkErr(t, "equal, nil body", AssertBodyEqual("")(&httpResponse{}), "")
})

for _, p := range patterns {
t.Run("matching "+strconv.Quote(p), func(t *testing.T) {
a, err := AssertBodyMatch(p)
if err != nil {
t.Fatalf("cannot build the assertion: %s", err)
}

checkErr(t, "match", a(&httpResponse{BodyBytes: []byte{}}), "")
checkErr(t, "match, nil body", a(&httpResponse{}), "")
})
}

// The verdict moved; the wording did not. A body that is empty when
// something was expected still reads as "missing" rather than `got ""`.
t.Run("an empty body still reports as missing", func(t *testing.T) {
res := &httpResponse{BodyBytes: []byte{}}
checkErr(t, "equal", AssertBodyEqual("value")(res), `body: expected "value", missing`)

a, err := AssertBodyMatch("^value$")
if err != nil {
t.Fatalf("cannot build the assertion: %s", err)
}
checkErr(t, "match", a(res), `body: expected to match "^value$", missing`)
})

// The inverse must keep failing: a non-empty body is not the empty string.
t.Run("a non-empty body does not equal the empty string", func(t *testing.T) {
res := &httpResponse{BodyBytes: []byte("x")}
checkErr(t, "equal", AssertBodyEqual("")(res), `body: expected "", got "x"`)
})
}

func Test_AssertRedirect(t *testing.T) {
t.Parallel()

Expand Down
29 changes: 29 additions & 0 deletions e2e_assert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,32 @@ func TestE2ELargePayloadCropped(t *testing.T) {
assertContains(t, r, "Payload is cropped")
assertContains(t, r, "4744 bytes are hidden")
}

// TestE2EAssertEmptyBody covers the expectations an empty body satisfies.
//
// They were unreachable until #22: both --assert-body-eq and --assert-body
// checked whether the body was empty before checking what was asked of it, so
// a 204 could not be asserted to have the body a 204 is defined to have.
func TestE2EAssertEmptyBody(t *testing.T) {
t.Run("--assert-body-eq '' passes against a 204", func(t *testing.T) {
assertExit(t, run(t, nil, "--assert-body-eq", "", url("/empty")), exitOK)
})

t.Run("--assert-body '^$' passes against a 204", func(t *testing.T) {
assertExit(t, run(t, nil, "--assert-body", "^$", url("/empty")), exitOK)
})

// The inverse still fails, so the fix did not simply stop checking.
t.Run("--assert-body-eq '' fails against a body", func(t *testing.T) {
r := run(t, nil, "--assert-body-eq", "", url("/ok"))
assertExit(t, r, exitRequestFail)
assertContains(t, r, `body: expected "", got`)
})

// And the wording that the old guard existed to produce is still there.
t.Run("an empty body still reports as missing", func(t *testing.T) {
r := run(t, nil, "--assert-body-eq", "value", url("/empty"))
assertExit(t, r, exitRequestFail)
assertContains(t, r, `body: expected "value", missing`)
})
}
10 changes: 0 additions & 10 deletions e2e_known_issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,6 @@ func TestKnownIssue20AssertOkAcceptsRedirects(t *testing.T) {
}
}

// TestKnownIssue22EmptyBodyEqualsNeverPasses: --assert-body-eq "" short-circuits
// on an empty body and reports that the empty string is "missing".
func TestKnownIssue22EmptyBodyEqualsNeverPasses(t *testing.T) {
characterizes(t, 22, `--assert-body-eq "" cannot pass, even against a 204`)

r := run(t, nil, "--assert-body-eq", "", url("/empty"))
assertExit(t, r, exitRequestFail)
assertContains(t, r, `body: expected "", missing`)
}

// TestKnownIssue23WildcardMaphostUnreachable: hostMapping.Matches handles "*"
// and "*:*", but the parser rejects both, so the branches are dead code.
func TestKnownIssue23WildcardMaphostUnreachable(t *testing.T) {
Expand Down
Loading