OpenAPI generator for OCaml with Requests/Eio/Jsont
at main 341 lines 11 kB view raw
1(** Tests for ocaml-openapi *) 2 3module Spec = Openapi.Spec 4module Codegen = Openapi.Codegen 5module Runtime = Openapi.Runtime 6 7(** {1 Path Template Tests} *) 8 9let test_path_render_simple () = 10 let result = Runtime.Path.render ~params:[] "/users" in 11 Alcotest.(check string) "no params" "/users" result 12 13let test_path_render_one_param () = 14 let result = Runtime.Path.render ~params:[("id", "123")] "/users/{id}" in 15 Alcotest.(check string) "one param" "/users/123" result 16 17let test_path_render_multiple_params () = 18 let result = Runtime.Path.render 19 ~params:[("userId", "42"); ("postId", "99")] 20 "/users/{userId}/posts/{postId}" in 21 Alcotest.(check string) "multiple params" "/users/42/posts/99" result 22 23let test_path_parameters () = 24 let params = Runtime.Path.parameters "/users/{userId}/posts/{postId}" in 25 Alcotest.(check (list string)) "extract params" ["userId"; "postId"] params 26 27(** {1 Query Parameter Tests} *) 28 29let test_query_singleton () = 30 let params = Runtime.Query.singleton ~key:"name" ~value:"alice" in 31 Alcotest.(check (list (pair string string))) "singleton" [("name", "alice")] params 32 33let test_query_optional_some () = 34 let params = Runtime.Query.optional ~key:"name" ~value:(Some "alice") in 35 Alcotest.(check (list (pair string string))) "optional some" [("name", "alice")] params 36 37let test_query_optional_none () = 38 let params = Runtime.Query.optional ~key:"name" ~value:None in 39 Alcotest.(check (list (pair string string))) "optional none" [] params 40 41let test_query_encode_empty () = 42 let result = Runtime.Query.encode [] in 43 Alcotest.(check string) "empty query" "" result 44 45let test_query_encode_single () = 46 let result = Runtime.Query.encode [("name", "alice")] in 47 Alcotest.(check string) "single query" "?name=alice" result 48 49let test_query_encode_multiple () = 50 let result = Runtime.Query.encode [("name", "alice"); ("age", "30")] in 51 Alcotest.(check string) "multiple query" "?name=alice&age=30" result 52 53let test_query_encode_special_chars () = 54 let result = Runtime.Query.encode [("q", "hello world")] in 55 Alcotest.(check string) "special chars" "?q=hello%20world" result 56 57(** {1 Name Conversion Tests} *) 58 59let test_snake_case_simple () = 60 let result = Codegen.Name.to_snake_case "getUserById" in 61 Alcotest.(check string) "camel to snake" "get_user_by_id" result 62 63let test_snake_case_with_dashes () = 64 let result = Codegen.Name.to_snake_case "user-name" in 65 Alcotest.(check string) "dashes to underscore" "user_name" result 66 67let test_snake_case_reserved () = 68 let result = Codegen.Name.to_snake_case "type" in 69 Alcotest.(check string) "reserved word" "type_" result 70 71let test_module_name () = 72 let result = Codegen.Name.to_module_name "user_profile" in 73 Alcotest.(check string) "module name" "UserProfile" result 74 75let test_variant_name () = 76 let result = Codegen.Name.to_variant_name "active_user" in 77 Alcotest.(check string) "variant name" "Active_user" result 78 79(** {1 Spec Parsing Tests} *) 80 81let minimal_spec = {|{ 82 "openapi": "3.0.0", 83 "info": { 84 "title": "Test API", 85 "version": "1.0.0" 86 }, 87 "paths": {} 88}|} 89 90let test_parse_minimal_spec () = 91 match Spec.of_string minimal_spec with 92 | Error e -> Alcotest.fail e 93 | Ok spec -> 94 Alcotest.(check string) "openapi version" "3.0.0" spec.openapi; 95 Alcotest.(check string) "title" "Test API" spec.info.title; 96 Alcotest.(check string) "version" "1.0.0" spec.info.version 97 98let spec_with_schema = {|{ 99 "openapi": "3.0.0", 100 "info": { 101 "title": "Test API", 102 "version": "1.0.0" 103 }, 104 "paths": {}, 105 "components": { 106 "schemas": { 107 "User": { 108 "type": "object", 109 "properties": { 110 "id": { "type": "integer" }, 111 "name": { "type": "string" }, 112 "email": { "type": "string", "format": "email" } 113 }, 114 "required": ["id", "name"] 115 } 116 } 117 } 118}|} 119 120let test_parse_schema () = 121 match Spec.of_string spec_with_schema with 122 | Error e -> Alcotest.fail e 123 | Ok spec -> 124 match spec.components with 125 | None -> Alcotest.fail "expected components" 126 | Some c -> 127 Alcotest.(check int) "schema count" 1 (List.length c.schemas); 128 match List.assoc_opt "User" c.schemas with 129 | None -> Alcotest.fail "expected User schema" 130 | Some (Spec.Ref _) -> Alcotest.fail "expected value not ref" 131 | Some (Spec.Value s) -> 132 Alcotest.(check (option string)) "type" (Some "object") s.type_; 133 Alcotest.(check int) "properties" 3 (List.length s.properties); 134 Alcotest.(check (list string)) "required" ["id"; "name"] s.required 135 136let spec_with_enum = {|{ 137 "openapi": "3.0.0", 138 "info": { 139 "title": "Test API", 140 "version": "1.0.0" 141 }, 142 "paths": {}, 143 "components": { 144 "schemas": { 145 "Status": { 146 "type": "string", 147 "enum": ["active", "inactive", "pending"] 148 } 149 } 150 } 151}|} 152 153let test_parse_enum () = 154 match Spec.of_string spec_with_enum with 155 | Error e -> Alcotest.fail e 156 | Ok spec -> 157 match spec.components with 158 | None -> Alcotest.fail "expected components" 159 | Some c -> 160 match List.assoc_opt "Status" c.schemas with 161 | None -> Alcotest.fail "expected Status schema" 162 | Some (Spec.Ref _) -> Alcotest.fail "expected value not ref" 163 | Some (Spec.Value s) -> 164 match s.enum with 165 | None -> Alcotest.fail "expected enum" 166 | Some values -> 167 Alcotest.(check int) "enum count" 3 (List.length values) 168 169let spec_with_paths = {|{ 170 "openapi": "3.0.0", 171 "info": { 172 "title": "Test API", 173 "version": "1.0.0" 174 }, 175 "paths": { 176 "/users": { 177 "get": { 178 "operationId": "listUsers", 179 "summary": "List all users", 180 "responses": { 181 "200": { 182 "description": "Success" 183 } 184 } 185 }, 186 "post": { 187 "operationId": "createUser", 188 "summary": "Create a user", 189 "responses": { 190 "201": { 191 "description": "Created" 192 } 193 } 194 } 195 }, 196 "/users/{id}": { 197 "get": { 198 "operationId": "getUser", 199 "parameters": [ 200 { 201 "name": "id", 202 "in": "path", 203 "required": true, 204 "schema": { "type": "integer" } 205 } 206 ], 207 "responses": { 208 "200": { 209 "description": "Success" 210 } 211 } 212 } 213 } 214 } 215}|} 216 217let test_parse_paths () = 218 match Spec.of_string spec_with_paths with 219 | Error e -> Alcotest.fail e 220 | Ok spec -> 221 Alcotest.(check int) "path count" 2 (List.length spec.paths); 222 match List.assoc_opt "/users" spec.paths with 223 | None -> Alcotest.fail "expected /users path" 224 | Some path_item -> 225 (match path_item.get with 226 | None -> Alcotest.fail "expected GET" 227 | Some op -> 228 Alcotest.(check (option string)) "operation id" (Some "listUsers") op.operation_id); 229 (match path_item.post with 230 | None -> Alcotest.fail "expected POST" 231 | Some op -> 232 Alcotest.(check (option string)) "operation id" (Some "createUser") op.operation_id) 233 234(** {1 Code Generation Tests} *) 235 236let contains_substring s sub = 237 let len_s = String.length s in 238 let len_sub = String.length sub in 239 if len_sub > len_s then false 240 else 241 let rec check i = 242 if i > len_s - len_sub then false 243 else if String.sub s i len_sub = sub then true 244 else check (i + 1) 245 in 246 check 0 247 248let test_split_schema_name () = 249 let p, s = Codegen.Name.split_schema_name "AlbumResponseDto" in 250 Alcotest.(check string) "prefix" "Album" p; 251 Alcotest.(check string) "suffix" "ResponseDto" s 252 253let test_split_schema_name_no_suffix () = 254 let p, s = Codegen.Name.split_schema_name "User" in 255 Alcotest.(check string) "prefix" "User" p; 256 Alcotest.(check string) "suffix" "T" s 257 258let test_generate_files () = 259 match Spec.of_string spec_with_schema with 260 | Error e -> Alcotest.fail e 261 | Ok spec -> 262 let config = Codegen.{ 263 output_dir = "."; 264 package_name = "test_api"; 265 spec_path = None; 266 } in 267 let files = Codegen.generate ~config spec in 268 Alcotest.(check int) "file count" 4 (List.length files); 269 let ml = List.assoc_opt "test_api.ml" files in 270 (match ml with 271 | None -> Alcotest.fail "missing .ml file" 272 | Some content -> 273 Alcotest.(check bool) "contains module User" true 274 (contains_substring content "module User")) 275 276let test_generate_enum_schema () = 277 match Spec.of_string spec_with_enum with 278 | Error e -> Alcotest.fail e 279 | Ok spec -> 280 let config = Codegen.{ 281 output_dir = "."; 282 package_name = "test_enum"; 283 spec_path = None; 284 } in 285 let files = Codegen.generate ~config spec in 286 let ml = List.assoc_opt "test_enum.ml" files in 287 (match ml with 288 | None -> Alcotest.fail "missing .ml file" 289 | Some content -> 290 Alcotest.(check bool) "contains Active variant" true 291 (contains_substring content "Active")) 292 293(** {1 Test Suites} *) 294 295let path_tests = [ 296 "render simple", `Quick, test_path_render_simple; 297 "render one param", `Quick, test_path_render_one_param; 298 "render multiple params", `Quick, test_path_render_multiple_params; 299 "extract parameters", `Quick, test_path_parameters; 300] 301 302let query_tests = [ 303 "singleton", `Quick, test_query_singleton; 304 "optional some", `Quick, test_query_optional_some; 305 "optional none", `Quick, test_query_optional_none; 306 "encode empty", `Quick, test_query_encode_empty; 307 "encode single", `Quick, test_query_encode_single; 308 "encode multiple", `Quick, test_query_encode_multiple; 309 "encode special chars", `Quick, test_query_encode_special_chars; 310] 311 312let name_tests = [ 313 "snake case simple", `Quick, test_snake_case_simple; 314 "snake case dashes", `Quick, test_snake_case_with_dashes; 315 "snake case reserved", `Quick, test_snake_case_reserved; 316 "module name", `Quick, test_module_name; 317 "variant name", `Quick, test_variant_name; 318] 319 320let spec_tests = [ 321 "parse minimal", `Quick, test_parse_minimal_spec; 322 "parse schema", `Quick, test_parse_schema; 323 "parse enum", `Quick, test_parse_enum; 324 "parse paths", `Quick, test_parse_paths; 325] 326 327let codegen_tests = [ 328 "split schema name", `Quick, test_split_schema_name; 329 "split schema name no suffix", `Quick, test_split_schema_name_no_suffix; 330 "generate files", `Quick, test_generate_files; 331 "generate enum schema", `Quick, test_generate_enum_schema; 332] 333 334let () = 335 Alcotest.run "openapi" [ 336 "Path", path_tests; 337 "Query", query_tests; 338 "Name", name_tests; 339 "Spec", spec_tests; 340 "Codegen", codegen_tests; 341 ]