@@ -14,28 +14,126 @@ afterAll(async () => {
1414} ) ;
1515
1616describe ( "swagger" , ( ) => {
17- test ( "the swagger endpoint returns metadata about the server " , async ( ) => {
17+ test ( "the swagger endpoint returns valid OpenAPI 3.0 metadata " , async ( ) => {
1818 const res = await fetch ( url + "/api/swagger" ) ;
1919 expect ( res . status ) . toBe ( 200 ) ;
2020 const response = ( await res . json ( ) ) as ActionResponse < Swagger > ;
2121
2222 // OpenAPI 3.0 structure
2323 expect ( response . openapi ) . toBe ( "3.0.0" ) ;
2424 expect ( response . info . title ) . toBe ( "actionhero" ) ;
25+ expect ( response . info . version ) . toBeDefined ( ) ;
26+ expect ( response . info . license ) . toBeDefined ( ) ;
27+ expect ( response . info . description ) . toBeDefined ( ) ;
28+
2529 expect ( response . servers ) . toBeDefined ( ) ;
30+ expect ( response . servers ! . length ) . toBeGreaterThan ( 0 ) ;
2631 const serverUrl = response . servers ! [ 0 ] . url ;
2732 expect ( serverUrl . endsWith ( "/api" ) || serverUrl . endsWith ( "/api/" ) ) . toBe (
2833 true ,
2934 ) ;
35+ } ) ;
36+
37+ test ( "swagger documents all web actions" , async ( ) => {
38+ const res = await fetch ( url + "/api/swagger" ) ;
39+ const response = ( await res . json ( ) ) as ActionResponse < Swagger > ;
40+
41+ // Count web actions (actions with both route and method)
42+ const webActions = api . actions . actions . filter (
43+ ( action ) => action . web ?. route && action . web ?. method ,
44+ ) ;
45+
46+ // Count unique routes (since multiple methods on same route are grouped)
47+ const uniqueRoutes = new Set ( webActions . map ( ( action ) => action . web ! . route ) ) ;
48+ expect ( Object . keys ( response . paths ) . length ) . toBe ( uniqueRoutes . size ) ;
49+
50+ // Verify each web action is documented
51+ for ( const action of webActions ) {
52+ const path = action . web ! . route ;
53+ const method = action . web ! . method . toLowerCase ( ) ;
54+
55+ expect ( response . paths [ path ] ) . toBeDefined ( ) ;
56+ expect ( response . paths [ path ] ! [ method ] ) . toBeDefined ( ) ;
57+ expect ( response . paths [ path ] ! [ method ] ! . summary ) . toBe (
58+ action . description || action . name ,
59+ ) ;
60+ }
61+ } ) ;
62+
63+ test ( "swagger documents request bodies for actions with inputs" , async ( ) => {
64+ const res = await fetch ( url + "/api/swagger" ) ;
65+ const response = ( await res . json ( ) ) as ActionResponse < Swagger > ;
66+
67+ // Find actions with Zod inputs
68+ const actionsWithInputs = api . actions . actions . filter (
69+ ( action ) => action . web ?. route && action . web ?. method && action . inputs ,
70+ ) ;
71+
72+ for ( const action of actionsWithInputs ) {
73+ const path = action . web ! . route ;
74+ const method = action . web ! . method . toLowerCase ( ) ;
75+ const pathObj = response . paths [ path ] ! [ method ] ! ;
76+
77+ expect ( pathObj . requestBody ) . toBeDefined ( ) ;
78+ expect ( pathObj . requestBody ! . required ) . toBe ( true ) ;
79+ expect ( pathObj . requestBody ! . content [ "application/json" ] ) . toBeDefined ( ) ;
80+ expect (
81+ pathObj . requestBody ! . content [ "application/json" ] . schema ,
82+ ) . toBeDefined ( ) ;
83+ }
84+ } ) ;
85+
86+ test ( "swagger documents standard response codes" , async ( ) => {
87+ const res = await fetch ( url + "/api/swagger" ) ;
88+ const response = ( await res . json ( ) ) as ActionResponse < Swagger > ;
89+
90+ // Check a specific endpoint (swagger itself)
91+ const swaggerPath = response . paths [ "/swagger" ] ! . get ! ;
92+
93+ expect ( swaggerPath . responses [ "200" ] ) . toBeDefined ( ) ;
94+ expect ( swaggerPath . responses [ "400" ] ) . toBeDefined ( ) ;
95+ expect ( swaggerPath . responses [ "404" ] ) . toBeDefined ( ) ;
96+ expect ( swaggerPath . responses [ "422" ] ) . toBeDefined ( ) ;
97+ expect ( swaggerPath . responses [ "500" ] ) . toBeDefined ( ) ;
98+
99+ // Verify response schemas
100+ expect (
101+ swaggerPath . responses [ "200" ] ! . content [ "application/json" ] . schema ,
102+ ) . toBeDefined ( ) ;
103+ expect (
104+ swaggerPath . responses [ "400" ] ! . content [ "application/json" ] . schema ,
105+ ) . toBeDefined ( ) ;
106+ } ) ;
107+
108+ test ( "swagger groups actions by tags" , async ( ) => {
109+ const res = await fetch ( url + "/api/swagger" ) ;
110+ const response = ( await res . json ( ) ) as ActionResponse < Swagger > ;
111+
112+ // Check that actions are tagged by their namespace
113+ for ( const action of api . actions . actions ) {
114+ if ( ! action . web ?. route || ! action . web ?. method ) continue ;
115+
116+ const path = action . web . route ;
117+ const method = action . web . method . toLowerCase ( ) ;
118+ const pathObj = response . paths [ path ] ! [ method ] ! ;
119+
120+ expect ( pathObj . tags ) . toBeDefined ( ) ;
121+ expect ( pathObj . tags ! . length ) . toBeGreaterThan ( 0 ) ;
30122
31- expect ( Object . keys ( response . paths ) . length ) . toBeGreaterThan ( 2 ) ;
123+ const expectedTag = action . name . split ( ":" ) [ 0 ] ;
124+ expect ( pathObj . tags ) . toContain ( expectedTag ) ;
125+ }
126+ } ) ;
127+
128+ test ( "swagger endpoint itself is documented" , async ( ) => {
129+ const res = await fetch ( url + "/api/swagger" ) ;
130+ const response = ( await res . json ( ) ) as ActionResponse < Swagger > ;
32131
33- // Check that the swagger endpoint itself is documented
34132 expect ( response . paths [ "/swagger" ] ) . toBeDefined ( ) ;
35- expect ( response . paths [ "/swagger" ] ? .get ) . toBeDefined ( ) ;
36- expect ( response . paths [ "/swagger" ] ? .get ? .summary ) . toBe (
133+ expect ( response . paths [ "/swagger" ] ! . get ) . toBeDefined ( ) ;
134+ expect ( response . paths [ "/swagger" ] ! . get ! . summary ) . toBe (
37135 "Return API documentation in the OpenAPI specification" ,
38136 ) ;
39- expect ( response . paths [ "/swagger" ] ? .get ? .responses [ "200" ] ) . toBeDefined ( ) ;
137+ expect ( response . paths [ "/swagger" ] ! . get ! . responses [ "200" ] ) . toBeDefined ( ) ;
40138 } ) ;
41139} ) ;
0 commit comments