Skip to content

Commit 39c6c71

Browse files
test: add tests for AutoHead feature
1 parent b68719f commit 39c6c71

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed

echo_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,159 @@ func TestDefaultHTTPErrorHandler_CommitedResponse(t *testing.T) {
12331233
assert.Equal(t, http.StatusOK, resp.Code)
12341234
}
12351235

1236+
func TestAutoHeadRoute(t *testing.T) {
1237+
tests := []struct {
1238+
name string
1239+
autoHead bool
1240+
method string
1241+
wantBody bool
1242+
wantCode int
1243+
wantCLen bool // expect Content-Length header
1244+
}{
1245+
{
1246+
name: "AutoHead disabled - HEAD returns 405",
1247+
autoHead: false,
1248+
method: http.MethodHead,
1249+
wantCode: http.StatusMethodNotAllowed,
1250+
wantBody: false,
1251+
},
1252+
{
1253+
name: "AutoHead enabled - HEAD returns 200 with Content-Length",
1254+
autoHead: true,
1255+
method: http.MethodHead,
1256+
wantCode: http.StatusOK,
1257+
wantBody: false,
1258+
wantCLen: true,
1259+
},
1260+
{
1261+
name: "GET request works normally with AutoHead enabled",
1262+
autoHead: true,
1263+
method: http.MethodGet,
1264+
wantCode: http.StatusOK,
1265+
wantBody: true,
1266+
},
1267+
}
1268+
1269+
for _, tt := range tests {
1270+
t.Run(tt.name, func(t *testing.T) {
1271+
// Create Echo instance with AutoHead configuration
1272+
e := New()
1273+
e.AutoHead = tt.autoHead
1274+
1275+
// Register a simple GET route
1276+
testBody := "Hello, World!"
1277+
e.GET("/hello", func(c *Context) error {
1278+
return c.String(http.StatusOK, testBody)
1279+
})
1280+
1281+
// Create request and response
1282+
req := httptest.NewRequest(tt.method, "/hello", nil)
1283+
rec := httptest.NewRecorder()
1284+
1285+
// Serve the request
1286+
e.ServeHTTP(rec, req)
1287+
1288+
// Verify status code
1289+
if rec.Code != tt.wantCode {
1290+
t.Errorf("expected status %d, got %d", tt.wantCode, rec.Code)
1291+
}
1292+
1293+
// Verify response body
1294+
if tt.wantBody {
1295+
if rec.Body.String() != testBody {
1296+
t.Errorf("expected body %q, got %q", testBody, rec.Body.String())
1297+
}
1298+
} else {
1299+
if rec.Body.String() != "" {
1300+
t.Errorf("expected empty body for HEAD, got %q", rec.Body.String())
1301+
}
1302+
}
1303+
1304+
// Verify Content-Length header for HEAD
1305+
if tt.wantCLen && tt.method == http.MethodHead {
1306+
clen := rec.Header().Get("Content-Length")
1307+
if clen == "" {
1308+
t.Error("expected Content-Length header for HEAD request")
1309+
}
1310+
}
1311+
})
1312+
}
1313+
}
1314+
1315+
func TestAutoHeadExplicitHeadTakesPrecedence(t *testing.T) {
1316+
e := New()
1317+
e.AutoHead = true
1318+
1319+
// Register explicit HEAD route FIRST with custom behavior
1320+
e.HEAD("/api/users", func(c *Context) error {
1321+
c.Response().Header().Set("X-Custom-Header", "explicit-head")
1322+
return c.NoContent(http.StatusOK)
1323+
})
1324+
1325+
// Then register GET route - AutoHead will try to add a HEAD route but fail silently
1326+
// since one already exists
1327+
e.GET("/api/users", func(c *Context) error {
1328+
return c.JSON(http.StatusOK, map[string]string{"name": "John"})
1329+
})
1330+
1331+
// Test that the explicit HEAD route behavior is preserved
1332+
req := httptest.NewRequest(http.MethodHead, "/api/users", nil)
1333+
rec := httptest.NewRecorder()
1334+
e.ServeHTTP(rec, req)
1335+
1336+
if rec.Code != http.StatusOK {
1337+
t.Errorf("expected status 200, got %d", rec.Code)
1338+
}
1339+
1340+
if rec.Header().Get("X-Custom-Header") != "explicit-head" {
1341+
t.Error("expected explicit HEAD route to be used")
1342+
}
1343+
1344+
// Verify body is empty
1345+
if rec.Body.String() != "" {
1346+
t.Errorf("expected empty body for HEAD, got %q", rec.Body.String())
1347+
}
1348+
}
1349+
1350+
func TestAutoHeadWithMiddleware(t *testing.T) {
1351+
e := New()
1352+
e.AutoHead = true
1353+
1354+
// Add request logger middleware
1355+
middlewareExecuted := false
1356+
e.Use(func(next HandlerFunc) HandlerFunc {
1357+
return func(c *Context) error {
1358+
middlewareExecuted = true
1359+
c.Response().Header().Set("X-Middleware", "executed")
1360+
return next(c)
1361+
}
1362+
})
1363+
1364+
// Register GET route
1365+
e.GET("/test", func(c *Context) error {
1366+
return c.String(http.StatusOK, "test response")
1367+
})
1368+
1369+
// Test HEAD request goes through middleware
1370+
req := httptest.NewRequest(http.MethodHead, "/test", nil)
1371+
rec := httptest.NewRecorder()
1372+
1373+
middlewareExecuted = false
1374+
e.ServeHTTP(rec, req)
1375+
1376+
if !middlewareExecuted {
1377+
t.Error("middleware should execute for automatic HEAD route")
1378+
}
1379+
1380+
if rec.Header().Get("X-Middleware") != "executed" {
1381+
t.Error("middleware header not set")
1382+
}
1383+
1384+
if rec.Body.String() != "" {
1385+
t.Errorf("expected empty body for HEAD, got %q", rec.Body.String())
1386+
}
1387+
}
1388+
12361389
func benchmarkEchoRoutes(b *testing.B, routes []testRoute) {
12371390
e := New()
12381391
req := httptest.NewRequest(http.MethodGet, "/", nil)
@@ -1278,3 +1431,23 @@ func BenchmarkEchoGitHubAPIMisses(b *testing.B) {
12781431
func BenchmarkEchoParseAPI(b *testing.B) {
12791432
benchmarkEchoRoutes(b, parseAPI)
12801433
}
1434+
1435+
func BenchmarkAutoHeadRoute(b *testing.B) {
1436+
e := New()
1437+
e.AutoHead = true
1438+
1439+
e.GET("/bench", func(c *Context) error {
1440+
return c.String(http.StatusOK, "benchmark response body")
1441+
})
1442+
1443+
req := httptest.NewRequest(http.MethodHead, "/bench", nil)
1444+
rec := httptest.NewRecorder()
1445+
1446+
b.ReportAllocs()
1447+
b.ResetTimer()
1448+
1449+
for i := 0; i < b.N; i++ {
1450+
rec.Body.Reset()
1451+
e.ServeHTTP(rec, req)
1452+
}
1453+
}

0 commit comments

Comments
 (0)