@@ -1340,6 +1340,200 @@ describe('Not Found', () => {
13401340 } )
13411341} )
13421342
1343+ describe ( 'Method Not Allowed' , ( ) => {
1344+ it ( 'Should return 404 by default when method is not allowed' , async ( ) => {
1345+ const app = new Hono ( )
1346+
1347+ app . get ( '/hello' , ( c ) => {
1348+ return c . text ( 'hello' )
1349+ } )
1350+
1351+ const res = await app . request ( 'http://localhost/hello' , { method : 'POST' } )
1352+ expect ( res . status ) . toBe ( 404 )
1353+ expect ( await res . text ( ) ) . toBe ( '404 Not Found' )
1354+ } )
1355+
1356+ it ( 'Should return 405 when methodNotAllowed handler is set' , async ( ) => {
1357+ const app = new Hono ( )
1358+
1359+ app . get ( '/hello' , ( c ) => {
1360+ return c . text ( 'hello' )
1361+ } )
1362+
1363+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1364+ return c . text ( '405 Method Not Allowed' , 405 , {
1365+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1366+ } )
1367+ } )
1368+
1369+ const res = await app . request ( 'http://localhost/hello' , { method : 'POST' } )
1370+ expect ( res . status ) . toBe ( 405 )
1371+ expect ( res . headers . get ( 'Allow' ) ) . toBe ( 'GET' )
1372+ expect ( await res . text ( ) ) . toBe ( '405 Method Not Allowed' )
1373+ } )
1374+
1375+ it ( 'Should return 404 when path does not exist even with methodNotAllowed handler' , async ( ) => {
1376+ const app = new Hono ( )
1377+
1378+ app . get ( '/hello' , ( c ) => {
1379+ return c . text ( 'hello' )
1380+ } )
1381+
1382+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1383+ return c . text ( '405 Method Not Allowed' , 405 , {
1384+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1385+ } )
1386+ } )
1387+
1388+ const res = await app . request ( 'http://localhost/nonexistent' , { method : 'POST' } )
1389+ expect ( res . status ) . toBe ( 404 )
1390+ expect ( await res . text ( ) ) . toBe ( '404 Not Found' )
1391+ } )
1392+
1393+ it ( 'Should include all allowed methods in Allow header' , async ( ) => {
1394+ const app = new Hono ( )
1395+
1396+ app . get ( '/hello' , ( c ) => c . text ( 'GET' ) )
1397+ app . post ( '/hello' , ( c ) => c . text ( 'POST' ) )
1398+ app . put ( '/hello' , ( c ) => c . text ( 'PUT' ) )
1399+
1400+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1401+ return c . text ( '405 Method Not Allowed' , 405 , {
1402+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1403+ } )
1404+ } )
1405+
1406+ const res = await app . request ( 'http://localhost/hello' , { method : 'DELETE' } )
1407+ expect ( res . status ) . toBe ( 405 )
1408+ const allowHeader = res . headers . get ( 'Allow' )
1409+ expect ( allowHeader ) . toContain ( 'GET' )
1410+ expect ( allowHeader ) . toContain ( 'POST' )
1411+ expect ( allowHeader ) . toContain ( 'PUT' )
1412+ expect ( allowHeader ?. split ( ', ' ) . length ) . toBe ( 3 )
1413+ } )
1414+
1415+ it ( 'Should work with custom methodNotAllowed handler' , async ( ) => {
1416+ const app = new Hono ( )
1417+
1418+ app . get ( '/hello' , ( c ) => c . text ( 'hello' ) )
1419+
1420+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1421+ return c . json ( { error : 'Method not allowed' , allowed : allowedMethods } , 405 , {
1422+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1423+ } )
1424+ } )
1425+
1426+ const res = await app . request ( 'http://localhost/hello' , { method : 'POST' } )
1427+ expect ( res . status ) . toBe ( 405 )
1428+ expect ( res . headers . get ( 'Allow' ) ) . toBe ( 'GET' )
1429+ const json = await res . json ( )
1430+ expect ( json . error ) . toBe ( 'Method not allowed' )
1431+ expect ( json . allowed ) . toEqual ( [ 'GET' ] )
1432+ } )
1433+
1434+ it ( 'Should work with routes with parameters' , async ( ) => {
1435+ const app = new Hono ( )
1436+
1437+ app . get ( '/user/:id' , ( c ) => {
1438+ return c . text ( `User ${ c . req . param ( 'id' ) } ` )
1439+ } )
1440+
1441+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1442+ return c . text ( '405 Method Not Allowed' , 405 , {
1443+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1444+ } )
1445+ } )
1446+
1447+ const res = await app . request ( 'http://localhost/user/123' , { method : 'POST' } )
1448+ expect ( res . status ) . toBe ( 405 )
1449+ expect ( res . headers . get ( 'Allow' ) ) . toBe ( 'GET' )
1450+ } )
1451+
1452+ it ( 'Should work with HEAD method (which maps to GET)' , async ( ) => {
1453+ const app = new Hono ( )
1454+
1455+ app . get ( '/hello' , ( c ) => c . text ( 'hello' ) )
1456+
1457+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1458+ return c . text ( '405 Method Not Allowed' , 405 , {
1459+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1460+ } )
1461+ } )
1462+
1463+ // HEAD should work (maps to GET)
1464+ const headRes = await app . request ( 'http://localhost/hello' , { method : 'HEAD' } )
1465+ expect ( headRes . status ) . toBe ( 200 )
1466+
1467+ // But POST should return 405
1468+ const postRes = await app . request ( 'http://localhost/hello' , { method : 'POST' } )
1469+ expect ( postRes . status ) . toBe ( 405 )
1470+ expect ( postRes . headers . get ( 'Allow' ) ) . toBe ( 'GET' )
1471+ } )
1472+
1473+ it ( 'Should work with app.route()' , async ( ) => {
1474+ const app = new Hono ( )
1475+ const subApp = new Hono ( )
1476+
1477+ subApp . get ( '/hello' , ( c ) => c . text ( 'hello' ) )
1478+ app . route ( '/api' , subApp )
1479+
1480+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1481+ return c . text ( '405 Method Not Allowed' , 405 , {
1482+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1483+ } )
1484+ } )
1485+
1486+ const res = await app . request ( 'http://localhost/api/hello' , { method : 'POST' } )
1487+ expect ( res . status ) . toBe ( 405 )
1488+ expect ( res . headers . get ( 'Allow' ) ) . toBe ( 'GET' )
1489+ } )
1490+
1491+ it ( 'Should work with multiple methods on same path' , async ( ) => {
1492+ const app = new Hono ( )
1493+
1494+ app . get ( '/resource' , ( c ) => c . text ( 'GET' ) )
1495+ app . post ( '/resource' , ( c ) => c . text ( 'POST' ) )
1496+ app . delete ( '/resource' , ( c ) => c . text ( 'DELETE' ) )
1497+
1498+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1499+ return c . text ( '405 Method Not Allowed' , 405 , {
1500+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1501+ } )
1502+ } )
1503+
1504+ const res = await app . request ( 'http://localhost/resource' , { method : 'PUT' } )
1505+ expect ( res . status ) . toBe ( 405 )
1506+ const allowHeader = res . headers . get ( 'Allow' )
1507+ expect ( allowHeader ) . toContain ( 'GET' )
1508+ expect ( allowHeader ) . toContain ( 'POST' )
1509+ expect ( allowHeader ) . toContain ( 'DELETE' )
1510+ } )
1511+
1512+ it ( 'Should work with app.use() middleware' , async ( ) => {
1513+ const app = new Hono ( )
1514+
1515+ app . methodNotAllowed ( ( c , allowedMethods ) => {
1516+ return c . text ( `Method not allowed. Allowed: ${ allowedMethods . join ( ', ' ) } ` , 405 , {
1517+ Allow : allowedMethods . join ( ', ' ) . toUpperCase ( ) ,
1518+ } )
1519+ } )
1520+
1521+ // Adding middleware should not prevent methodNotAllowed from being called
1522+ app . use ( async ( c , next ) => {
1523+ await next ( )
1524+ } )
1525+
1526+ app . get ( '/' , ( c ) => {
1527+ return new Response ( 'Hello, World!' )
1528+ } )
1529+
1530+ const res = await app . request ( 'http://localhost/' , { method : 'POST' } )
1531+ expect ( res . status ) . toBe ( 405 )
1532+ expect ( res . headers . get ( 'Allow' ) ) . toBe ( 'GET' )
1533+ expect ( await res . text ( ) ) . toBe ( 'Method not allowed. Allowed: GET' )
1534+ } )
1535+ } )
1536+
13431537describe ( 'Redirect' , ( ) => {
13441538 const app = new Hono ( )
13451539 app . get ( '/redirect' , ( c ) => {
0 commit comments