@@ -14,45 +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
22- expect ( response . basePath ) . toInclude ( "/api/" ) ;
23- expect ( response . host ) . toInclude (
24- config . server . web . applicationUrl
25- . replace ( / ^ h t t p s ? : \/ \/ / , "" )
26- . replace ( / ^ h t t p ? : \/ \/ / , "" ) ,
22+ // OpenAPI 3.0 structure
23+ expect ( response . openapi ) . toBe ( "3.0.0" ) ;
24+ 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+
29+ expect ( response . servers ) . toBeDefined ( ) ;
30+ expect ( response . servers ! . length ) . toBeGreaterThan ( 0 ) ;
31+ const serverUrl = response . servers ! [ 0 ] . url ;
32+ expect ( serverUrl . endsWith ( "/api" ) || serverUrl . endsWith ( "/api/" ) ) . toBe (
33+ true ,
34+ ) ;
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 ,
2744 ) ;
2845
29- expect ( Object . keys ( response . paths ) . length ) . toBeGreaterThan ( 2 ) ;
30-
31- expect ( response . paths [ "/swagger" ] ) . toEqual ( {
32- get : {
33- consumes : [ "application/json" ] ,
34- parameters : [ ] ,
35- produces : [ "application/json" ] ,
36- responses : {
37- "200" : {
38- description : "successful operation" ,
39- } ,
40- "400" : {
41- description : "Invalid input" ,
42- } ,
43- "404" : {
44- description : "Not Found" ,
45- } ,
46- "422" : {
47- description : "Missing or invalid params" ,
48- } ,
49- "500" : {
50- description : "Server error" ,
51- } ,
52- } ,
53- security : [ ] ,
54- summary : "Return API documentation in the OpenAPI specification" ,
55- } ,
56- } ) ;
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 ) ;
122+
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 > ;
131+
132+ expect ( response . paths [ "/swagger" ] ) . toBeDefined ( ) ;
133+ expect ( response . paths [ "/swagger" ] ! . get ) . toBeDefined ( ) ;
134+ expect ( response . paths [ "/swagger" ] ! . get ! . summary ) . toBe (
135+ "Return API documentation in the OpenAPI specification" ,
136+ ) ;
137+ expect ( response . paths [ "/swagger" ] ! . get ! . responses [ "200" ] ) . toBeDefined ( ) ;
57138 } ) ;
58139} ) ;
0 commit comments