From 26054d91207b9770618675ee4692d2c30a3bb31f Mon Sep 17 00:00:00 2001 From: beppe Date: Sat, 14 Mar 2026 16:15:39 +0100 Subject: [PATCH 1/5] Add folder description --- .../languages/PostmanCollectionCodegen.java | 44 ++++++++++++++++--- .../postman-collection/postman.mustache | 3 +- .../postman/PostmanCollectionCodegenTest.java | 30 ++++++++++--- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java index 42191ffbe899..6f06047612df 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java @@ -80,7 +80,7 @@ public class PostmanCollectionCodegen extends DefaultCodegen implements CodegenC // operations grouped by tag - public Map> codegenOperationsByTag = new HashMap<>(); + public Map> codegenOperationsByTag = new HashMap<>(); // list of operations public List codegenOperationsList = new ArrayList<>(); @@ -319,22 +319,28 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List list = codegenOperationsByTag.get(key); + PostmanRequestFolder folder = new PostmanRequestFolder(tagName, tagDescription); + List list = codegenOperationsByTag.get(folder); if (list == null) { list = new ArrayList<>(); } list.add(codegenOperation); - codegenOperationsByTag.put(key, list); + codegenOperationsByTag.put(folder, list); // sort requests by path Collections.sort(list, Comparator.comparing(obj -> obj.path)); @@ -883,6 +889,30 @@ public String getPostmanType(CodegenProperty codegenProperty) { } } + @Getter + public static class PostmanRequestFolder { + private final String name; + private final String description; + + public PostmanRequestFolder(String name, String description) { + this.name = name; + this.description = description; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PostmanRequestFolder that = (PostmanRequestFolder) o; + return Objects.equals(name, that.name) && Objects.equals(description, that.description); + } + + @Override + public int hashCode() { + return Objects.hash(name, description); + } + } + // Supporting models @Getter @Setter diff --git a/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache b/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache index d99e8420d66c..a2212e034091 100644 --- a/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache +++ b/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache @@ -12,7 +12,8 @@ {{#codegenOperationsByTag}} {{#entrySet}} { - "name": "{{key}}", + "name": "{{key.name}}", + "description": "{{key.description}}", "item": [{{#value}} {{>item}}{{^-last}},{{/-last}}{{/value}} ] diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java index f26878a91386..f0fdc5e54cfd 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java @@ -17,8 +17,10 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.openapitools.codegen.TestUtils.*; @@ -75,6 +77,9 @@ public void testBasicGeneration() throws IOException { assertFileContains(path, "\"name\": \"Get User\""); // verify request endpoint TestUtils.assertFileContains(path, "\"name\": \"/users/:userId\""); + // verify folder name and description + TestUtils.assertFileContains(path, "\"name\": \"basic\""); + TestUtils.assertFileContains(path, "\"description\": \"Basic tag\""); } @@ -702,24 +707,26 @@ public void testAddToMap() { CodegenOperation operationUsers = new CodegenOperation(); operationUsers.path = "/users"; - operationUsers.tags = new ArrayList<>(Arrays.asList(new Tag().name("basic"))); + operationUsers.tags = new ArrayList<>(Arrays.asList(new Tag().name("basic").description("Basic tag"))); postmanV2Generator.addToMap(operationUsers); CodegenOperation operationGroups = new CodegenOperation(); operationGroups.path = "/groups"; - operationGroups.tags = new ArrayList<>(Arrays.asList(new Tag().name("basic"))); + operationGroups.tags = new ArrayList<>(Arrays.asList(new Tag().name("basic").description("Basic tag"))); postmanV2Generator.addToMap(operationGroups); CodegenOperation operationUserId = new CodegenOperation(); operationUserId.path = "/users/{id}"; - operationUserId.tags = new ArrayList<>(Arrays.asList(new Tag().name("basic"))); + operationUserId.tags = new ArrayList<>(Arrays.asList(new Tag().name("basic").description("Basic tag"))); postmanV2Generator.addToMap(operationUserId); + PostmanCollectionCodegen.PostmanRequestFolder folder = new PostmanCollectionCodegen.PostmanRequestFolder("basic", "Basic tag"); + // verify tag 'basic' assertEquals(1, postmanV2Generator.codegenOperationsByTag.size()); - assertEquals(true, postmanV2Generator.codegenOperationsByTag.containsKey("basic")); + assertEquals(true, postmanV2Generator.codegenOperationsByTag.containsKey(folder)); - List operations = postmanV2Generator.codegenOperationsByTag.get("basic"); + List operations = postmanV2Generator.codegenOperationsByTag.get(folder); // verify order assertEquals("/groups", operations.get(0).path); assertEquals("/users", operations.get(1).path); @@ -737,7 +744,18 @@ public void testAddToMapUsingDefaultTag() { // verify tag 'default' is used assertEquals(1, postmanV2Generator.codegenOperationsByTag.size()); - assertEquals(true, postmanV2Generator.codegenOperationsByTag.containsKey("default")); + assertEquals(true, postmanV2Generator.codegenOperationsByTag.containsKey(new PostmanCollectionCodegen.PostmanRequestFolder("Default", "Default tag"))); + } + + @Test + public void testPostmanRequestFolderInMap() { + PostmanCollectionCodegen.PostmanRequestFolder folder1 = new PostmanCollectionCodegen.PostmanRequestFolder("test", "descr"); + Map map = new HashMap<>(); + map.put(folder1, "folder1"); + PostmanCollectionCodegen.PostmanRequestFolder folder2 = new PostmanCollectionCodegen.PostmanRequestFolder("test", "descr"); + + assertTrue(map.containsKey(folder2)); + assertEquals("folder1", map.get(folder2)); } @Test From 6aeb0cdc875cb76f97f305bd9c2482522b0e5421 Mon Sep 17 00:00:00 2001 From: beppe Date: Sat, 14 Mar 2026 16:15:45 +0100 Subject: [PATCH 2/5] Generate samples --- .../schema/postman-collection/postman.json | 377 +++++++++--------- 1 file changed, 190 insertions(+), 187 deletions(-) diff --git a/samples/schema/postman-collection/postman.json b/samples/schema/postman-collection/postman.json index 17162a658ded..aa7ad25ef7a9 100644 --- a/samples/schema/postman-collection/postman.json +++ b/samples/schema/postman-collection/postman.json @@ -10,16 +10,17 @@ }, "item": [ { - "name": "default", + "name": "basic", + "description": "Basic tag", "item": [ { - "name": "/users/:userId (DEPRECATED)", - "description": "Update the information of an existing user.", + "name": "/user", + "description": "Create a new user.", "item": [ { - "name": "Example patch user", + "name": "Example request for Get User", "request": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -32,23 +33,11 @@ "value": "application/json", "description": "", "disabled": false - }, - { - "key": "strCode", - "value": "code_one", - "description": "Code as header", - "disabled": false - }, - { - "key": "strCode2", - "value": "", - "description": "Code as header2", - "disabled": true } ], "body": { "mode": "raw", - "raw": "{\n \"firstName\" : \"John\",\n \"tags\" : [ \"user\" ]\n}", + "raw": "{\n \"id\" : 777,\n \"firstName\" : \"Alotta\",\n \"lastName\" : \"Rotta\",\n \"email\" : \"alotta.rotta@gmail.com\",\n \"dateOfBirth\" : \"1997-10-31\",\n \"emailVerified\" : true,\n \"createDate\" : \"2019-08-24\",\n \"tags\" : [ \"user\", \"admin\", \"guest\" ]\n}", "options": { "raw": { "language": "json" @@ -56,28 +45,22 @@ } }, "url": { - "raw": "{{baseUrl}}/users/:userId", + "raw": "{{baseUrl}}/user", "host": [ "{{baseUrl}}" ], "path": [ - "users", - ":userId" + "user" ], "variable": [ - { - "key": "userId", - "value": "", - "description": "Id of an existing user." - } ], "query": [ ] }, - "description": "Update the information of an existing user." + "description": "Create a new user." } ,"response": [ - {"name": "User Updated", + {"name": "User Created", "code": 200, "status": "OK", "header": [{ @@ -86,9 +69,9 @@ ], "_postman_previewlanguage": "json", "cookie": [], - "body" : "{\n \"id\" : 1,\n \"firstName\" : \"Ron\",\n \"lastName\" : \"Edwardz\",\n \"email\" : \"ron.edwardz@example.com\",\n \"dateOfBirth\" : \"1980-10-31\",\n \"emailVerified\" : true,\n \"tags\" : [ \"admin\" ]\n}", + "body" : "{\n \"id\" : 777,\n \"firstName\" : \"Alotta\",\n \"lastName\" : \"Rotta\",\n \"email\" : \"alotta.rotta@gmail.com\",\n \"dateOfBirth\" : \"1997-10-31\",\n \"emailVerified\" : true,\n \"createDate\" : \"2019-08-24\",\n \"tags\" : [ \"user\", \"admin\", \"guest\" ]\n}", "originalRequest": { - "method": "PATCH", + "method": "POST", "header": [ { "key": "Content-Type", @@ -101,23 +84,11 @@ "value": "application/json", "description": "", "disabled": false - }, - { - "key": "strCode", - "value": "code_one", - "description": "Code as header", - "disabled": false - }, - { - "key": "strCode2", - "value": "", - "description": "Code as header2", - "disabled": true } ], "body": { "mode": "raw", - "raw": "{\n \"firstName\" : \"John\",\n \"tags\" : [ \"user\" ]\n}", + "raw": "{\n \"id\" : 777,\n \"firstName\" : \"Alotta\",\n \"lastName\" : \"Rotta\",\n \"email\" : \"alotta.rotta@gmail.com\",\n \"dateOfBirth\" : \"1997-10-31\",\n \"emailVerified\" : true,\n \"createDate\" : \"2019-08-24\",\n \"tags\" : [ \"user\", \"admin\", \"guest\" ]\n}", "options": { "raw": { "language": "json" @@ -125,40 +96,34 @@ } }, "url": { - "raw": "{{baseUrl}}/users/:userId", + "raw": "{{baseUrl}}/user", "host": [ "{{baseUrl}}" ], "path": [ - "users", - ":userId" + "user" ], "variable": [ - { - "key": "userId", - "value": "", - "description": "Id of an existing user." - } ], "query": [ ] }, - "description": "Update the information of an existing user." + "description": "Create a new user." } } ] - }, + } + ] + }, + { + "name": "/users/", + "description": "Retrieve the information of the user with the matching user ID.", + "item": [ { - "name": "Example patch another user", + "name": "Get User Info by Query Param", "request": { - "method": "PATCH", + "method": "GET", "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "disabled": false - }, { "key": "Accept", "value": "application/json", @@ -166,90 +131,21 @@ "disabled": false }, { - "key": "strCode", - "value": "code_one", - "description": "Code as header", - "disabled": false - }, - { - "key": "strCode2", + "key": "Custom-Header", "value": "", - "description": "Code as header2", + "description": "Custom HTTP header", "disabled": true - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\" : \"Bill\",\n \"tags\" : [ \"admin\" ]\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{baseUrl}}/users/:userId", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "users", - ":userId" - ], - "variable": [ - { - "key": "userId", - "value": "", - "description": "Id of an existing user." - } - ], - "query": [ - ] - }, - "description": "Update the information of an existing user." -} - ,"response": [ - {"name": "User Updated", - "code": 200, - "status": "OK", - "header": [{ - "key": "Content-Type", - "value": "application/json"} - ], - "_postman_previewlanguage": "json", - "cookie": [], - "body" : "{\n \"id\" : 2,\n \"firstName\" : \"Rik\",\n \"lastName\" : \"Tom\",\n \"email\" : \"rik.tom@example.com\",\n \"dateOfBirth\" : \"1981-10-11\",\n \"emailVerified\" : true,\n \"tags\" : [ \"guest\" ]\n}", - "originalRequest": { - "method": "PATCH", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "disabled": false - }, - { - "key": "Accept", - "value": "application/json", - "description": "", - "disabled": false }, { - "key": "strCode", - "value": "code_one", - "description": "Code as header", + "key": "Another-Custom-Header", + "value": "abc", + "description": "Custom HTTP header with default", "disabled": false - }, - { - "key": "strCode2", - "value": "", - "description": "Code as header2", - "disabled": true } ], "body": { "mode": "raw", - "raw": "{\n \"firstName\" : \"Bill\",\n \"tags\" : [ \"admin\" ]\n}", + "raw": "", "options": { "raw": { "language": "json" @@ -257,27 +153,27 @@ } }, "url": { - "raw": "{{baseUrl}}/users/:userId", + "raw": "{{baseUrl}}/users/", "host": [ "{{baseUrl}}" ], "path": [ - "users", - ":userId" + "users" ], "variable": [ - { - "key": "userId", - "value": "", - "description": "Id of an existing user." - } ], "query": [ + { + "key": "pUserId", + "value": "888", + "description": "Query Id.", + "disabled": false + } ] }, - "description": "Update the information of an existing user." + "description": "Retrieve the information of the user with the matching user ID." } - } + ,"response": [ ] } ] @@ -286,6 +182,7 @@ }, { "name": "advanced", + "description": "Advanced tag", "item": [ { "name": "/groups/:groupId", @@ -404,16 +301,17 @@ ] }, { - "name": "basic", + "name": "default", + "description": "default tag", "item": [ { - "name": "/user", - "description": "Create a new user.", + "name": "/users/:userId (DEPRECATED)", + "description": "Update the information of an existing user.", "item": [ { - "name": "Example request for Get User", + "name": "Example patch user", "request": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -426,11 +324,23 @@ "value": "application/json", "description": "", "disabled": false + }, + { + "key": "strCode", + "value": "code_one", + "description": "Code as header", + "disabled": false + }, + { + "key": "strCode2", + "value": "", + "description": "Code as header2", + "disabled": true } ], "body": { "mode": "raw", - "raw": "{\n \"id\" : 777,\n \"firstName\" : \"Alotta\",\n \"lastName\" : \"Rotta\",\n \"email\" : \"alotta.rotta@gmail.com\",\n \"dateOfBirth\" : \"1997-10-31\",\n \"emailVerified\" : true,\n \"createDate\" : \"2019-08-24\",\n \"tags\" : [ \"user\", \"admin\", \"guest\" ]\n}", + "raw": "{\n \"firstName\" : \"John\",\n \"tags\" : [ \"user\" ]\n}", "options": { "raw": { "language": "json" @@ -438,22 +348,28 @@ } }, "url": { - "raw": "{{baseUrl}}/user", + "raw": "{{baseUrl}}/users/:userId", "host": [ "{{baseUrl}}" ], "path": [ - "user" + "users", + ":userId" ], "variable": [ + { + "key": "userId", + "value": "", + "description": "Id of an existing user." + } ], "query": [ ] }, - "description": "Create a new user." + "description": "Update the information of an existing user." } ,"response": [ - {"name": "User Created", + {"name": "User Updated", "code": 200, "status": "OK", "header": [{ @@ -462,9 +378,9 @@ ], "_postman_previewlanguage": "json", "cookie": [], - "body" : "{\n \"id\" : 777,\n \"firstName\" : \"Alotta\",\n \"lastName\" : \"Rotta\",\n \"email\" : \"alotta.rotta@gmail.com\",\n \"dateOfBirth\" : \"1997-10-31\",\n \"emailVerified\" : true,\n \"createDate\" : \"2019-08-24\",\n \"tags\" : [ \"user\", \"admin\", \"guest\" ]\n}", + "body" : "{\n \"id\" : 1,\n \"firstName\" : \"Ron\",\n \"lastName\" : \"Edwardz\",\n \"email\" : \"ron.edwardz@example.com\",\n \"dateOfBirth\" : \"1980-10-31\",\n \"emailVerified\" : true,\n \"tags\" : [ \"admin\" ]\n}", "originalRequest": { - "method": "POST", + "method": "PATCH", "header": [ { "key": "Content-Type", @@ -477,11 +393,23 @@ "value": "application/json", "description": "", "disabled": false + }, + { + "key": "strCode", + "value": "code_one", + "description": "Code as header", + "disabled": false + }, + { + "key": "strCode2", + "value": "", + "description": "Code as header2", + "disabled": true } ], "body": { "mode": "raw", - "raw": "{\n \"id\" : 777,\n \"firstName\" : \"Alotta\",\n \"lastName\" : \"Rotta\",\n \"email\" : \"alotta.rotta@gmail.com\",\n \"dateOfBirth\" : \"1997-10-31\",\n \"emailVerified\" : true,\n \"createDate\" : \"2019-08-24\",\n \"tags\" : [ \"user\", \"admin\", \"guest\" ]\n}", + "raw": "{\n \"firstName\" : \"John\",\n \"tags\" : [ \"user\" ]\n}", "options": { "raw": { "language": "json" @@ -489,56 +417,62 @@ } }, "url": { - "raw": "{{baseUrl}}/user", + "raw": "{{baseUrl}}/users/:userId", "host": [ "{{baseUrl}}" ], "path": [ - "user" + "users", + ":userId" ], "variable": [ + { + "key": "userId", + "value": "", + "description": "Id of an existing user." + } ], "query": [ ] }, - "description": "Create a new user." + "description": "Update the information of an existing user." } } ] - } - ] - }, - { - "name": "/users/", - "description": "Retrieve the information of the user with the matching user ID.", - "item": [ + }, { - "name": "Get User Info by Query Param", + "name": "Example patch another user", "request": { - "method": "GET", + "method": "PATCH", "header": [ { - "key": "Accept", + "key": "Content-Type", "value": "application/json", "description": "", "disabled": false }, { - "key": "Custom-Header", - "value": "", - "description": "Custom HTTP header", - "disabled": true + "key": "Accept", + "value": "application/json", + "description": "", + "disabled": false }, { - "key": "Another-Custom-Header", - "value": "abc", - "description": "Custom HTTP header with default", + "key": "strCode", + "value": "code_one", + "description": "Code as header", "disabled": false + }, + { + "key": "strCode2", + "value": "", + "description": "Code as header2", + "disabled": true } ], "body": { "mode": "raw", - "raw": "", + "raw": "{\n \"firstName\" : \"Bill\",\n \"tags\" : [ \"admin\" ]\n}", "options": { "raw": { "language": "json" @@ -546,27 +480,96 @@ } }, "url": { - "raw": "{{baseUrl}}/users/", + "raw": "{{baseUrl}}/users/:userId", "host": [ "{{baseUrl}}" ], "path": [ - "users" + "users", + ":userId" ], "variable": [ + { + "key": "userId", + "value": "", + "description": "Id of an existing user." + } ], "query": [ + ] + }, + "description": "Update the information of an existing user." +} + ,"response": [ + {"name": "User Updated", + "code": 200, + "status": "OK", + "header": [{ + "key": "Content-Type", + "value": "application/json"} + ], + "_postman_previewlanguage": "json", + "cookie": [], + "body" : "{\n \"id\" : 2,\n \"firstName\" : \"Rik\",\n \"lastName\" : \"Tom\",\n \"email\" : \"rik.tom@example.com\",\n \"dateOfBirth\" : \"1981-10-11\",\n \"emailVerified\" : true,\n \"tags\" : [ \"guest\" ]\n}", + "originalRequest": { + "method": "PATCH", + "header": [ { - "key": "pUserId", - "value": "888", - "description": "Query Id.", + "key": "Content-Type", + "value": "application/json", + "description": "", "disabled": false + }, + { + "key": "Accept", + "value": "application/json", + "description": "", + "disabled": false + }, + { + "key": "strCode", + "value": "code_one", + "description": "Code as header", + "disabled": false + }, + { + "key": "strCode2", + "value": "", + "description": "Code as header2", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\" : \"Bill\",\n \"tags\" : [ \"admin\" ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/:userId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + ":userId" + ], + "variable": [ + { + "key": "userId", + "value": "", + "description": "Id of an existing user." } + ], + "query": [ ] }, - "description": "Retrieve the information of the user with the matching user ID." + "description": "Update the information of an existing user." } - ,"response": [ + } ] } ] From 0252d3c96e8ea01e593169a153c3997371967e5c Mon Sep 17 00:00:00 2001 From: beppe Date: Sat, 14 Mar 2026 16:49:28 +0100 Subject: [PATCH 3/5] Refactor test to avoid relying on endpoints order --- .../python/test/test_endpoints.py | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/samples/schema/postman-collection/python/test/test_endpoints.py b/samples/schema/postman-collection/python/test/test_endpoints.py index 30a5e544bd4f..faa07fbe4e23 100644 --- a/samples/schema/postman-collection/python/test/test_endpoints.py +++ b/samples/schema/postman-collection/python/test/test_endpoints.py @@ -17,21 +17,35 @@ def setUp(self): def tearDown(self): pass + def _iter_items(self, items): + for item in items: + yield item + children = item.get('item', []) + if isinstance(children, list): + for child in self._iter_items(children): + yield child + + def _find_item_by_name(self, name): + for item in self._iter_items(self.json_data.get('item', [])): + if item.get('name') == name: + return item + return None + def test_endpoint_deprecated(self): - # path - path = self.json_data['item'][0]['item'][0] + path = self._find_item_by_name('/users/:userId (DEPRECATED)') + self.assertIsNotNone(path) self.assertEqual(path['name'], '/users/:userId (DEPRECATED)') def test_request_from_inline_examples(self): - # item - item = self.json_data['item'][0]['item'][0]['item'][0] + item = self._find_item_by_name('Example patch user') + self.assertIsNotNone(item) self.assertEqual(item['name'], 'Example patch user') self.assertEqual(item['request']["method"], 'PATCH') self.assertEqual(item['request']["body"]["raw"], '{\n "firstName" : "John",\n "tags" : [ "user" ]\n}') def test_request_with_array_strings(self): - # item - item = self.json_data['item'][2]['item'][0]['item'][0] + item = self._find_item_by_name('Example request for Get User') + self.assertIsNotNone(item) self.assertEqual(item['request']["method"], 'POST') data = json.loads(item['request']["body"]["raw"]) # check is list @@ -40,8 +54,8 @@ def test_request_with_array_strings(self): self.assertTrue(set(data.get("tags")) == {"user", "admin", "guest"}) def test_request_boolean_field(self): - # item - item = self.json_data['item'][0]['item'][0]['item'][1] + item = self._find_item_by_name('Example patch another user') + self.assertIsNotNone(item) self.assertEqual(item['name'], 'Example patch another user') self.assertEqual(item['request']["method"], 'PATCH') self.assertEqual(item['request']["body"]["raw"], '{\n "firstName" : "Bill",\n "tags" : [ "admin" ]\n}') From 77836de73bc8f64bcf8bc246287b72f0286c5205 Mon Sep 17 00:00:00 2001 From: beppe Date: Sat, 14 Mar 2026 16:56:54 +0100 Subject: [PATCH 4/5] Address cubic-dev comments --- .../languages/PostmanCollectionCodegen.java | 3 ++ .../postman-collection/postman.mustache | 4 +- .../postman/PostmanCollectionCodegenTest.java | 50 +++++++++++++++++-- .../TagDescriptionEscaping.yaml | 35 +++++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/postman-collection/TagDescriptionEscaping.yaml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java index 6f06047612df..2c40a4824754 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PostmanCollectionCodegen.java @@ -332,6 +332,9 @@ public void addToMap(CodegenOperation codegenOperation) { } } + tagName = formatDescription(tagName); + tagDescription = formatDescription(tagDescription); + PostmanRequestFolder folder = new PostmanRequestFolder(tagName, tagDescription); List list = codegenOperationsByTag.get(folder); diff --git a/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache b/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache index a2212e034091..5e8d2aa61612 100644 --- a/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache +++ b/modules/openapi-generator/src/main/resources/postman-collection/postman.mustache @@ -12,8 +12,8 @@ {{#codegenOperationsByTag}} {{#entrySet}} { - "name": "{{key.name}}", - "description": "{{key.description}}", + "name": "{{{key.name}}}", + "description": "{{{key.description}}}", "item": [{{#value}} {{>item}}{{^-last}},{{/-last}}{{/value}} ] diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java index f0fdc5e54cfd..4ea8ef22ccdf 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/postman/PostmanCollectionCodegenTest.java @@ -77,9 +77,21 @@ public void testBasicGeneration() throws IOException { assertFileContains(path, "\"name\": \"Get User\""); // verify request endpoint TestUtils.assertFileContains(path, "\"name\": \"/users/:userId\""); - // verify folder name and description - TestUtils.assertFileContains(path, "\"name\": \"basic\""); - TestUtils.assertFileContains(path, "\"description\": \"Basic tag\""); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode root = objectMapper.readTree(new File(output + "/postman.json")); + JsonNode folders = root.get("item"); + + JsonNode basicFolder = null; + for (JsonNode folder : folders) { + if ("basic".equals(folder.get("name").asText())) { + basicFolder = folder; + break; + } + } + + assertNotNull(basicFolder); + assertEquals("Basic tag", basicFolder.get("description").asText()); } @@ -106,6 +118,38 @@ public void testBasicGenerationJson() throws IOException { assertFileContains(path, "\"schema\": \"https://schema.getpostman.com/json/collection/v2.1.0/collection.json\""); } + @Test + public void testTagDescriptionIsJsonEscaped() throws IOException { + File output = Files.createTempDirectory("postmantest_").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("postman-collection") + .setInputSpec("src/test/resources/3_0/postman-collection/TagDescriptionEscaping.yaml") + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + final ClientOptInput clientOptInput = configurator.toClientOptInput(); + DefaultGenerator generator = new DefaultGenerator(); + List files = generator.opts(clientOptInput).generate(); + + files.forEach(File::deleteOnExit); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode root = objectMapper.readTree(new File(output + "/postman.json")); + JsonNode folders = root.get("item"); + + JsonNode basicFolder = null; + for (JsonNode folder : folders) { + if ("basic".equals(folder.get("name").asText())) { + basicFolder = folder; + break; + } + } + + assertNotNull(basicFolder); + assertEquals("Basic \"quoted\" tag\nsecond line", basicFolder.get("description").asText()); + } + @Test public void testValidatePostmanJson() throws IOException { diff --git a/modules/openapi-generator/src/test/resources/3_0/postman-collection/TagDescriptionEscaping.yaml b/modules/openapi-generator/src/test/resources/3_0/postman-collection/TagDescriptionEscaping.yaml new file mode 100644 index 000000000000..aaa8a11c81ca --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/postman-collection/TagDescriptionEscaping.yaml @@ -0,0 +1,35 @@ +openapi: 3.0.0 +info: + title: Tag Description Escaping + version: '1.0' +servers: + - url: 'http://localhost:5001' +paths: + '/users/{userId}': + get: + summary: Get User + operationId: get-users-userId + tags: + - basic + parameters: + - description: Unique identifier of the user + name: userId + in: path + required: true + schema: + type: string + responses: + '200': + description: User Found + content: + application/json: + schema: + type: object + properties: + id: + type: integer +tags: + - name: basic + description: |- + Basic "quoted" tag + second line From 2dbc78d571cdd98584b17c91cfea8d957f9eb215 Mon Sep 17 00:00:00 2001 From: beppe Date: Sat, 14 Mar 2026 17:05:34 +0100 Subject: [PATCH 5/5] Refactor test to avoid relying on endpoints order --- .../python/test/test_parameters.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/samples/schema/postman-collection/python/test/test_parameters.py b/samples/schema/postman-collection/python/test/test_parameters.py index 947ba8f22f58..69a8b6de67ac 100644 --- a/samples/schema/postman-collection/python/test/test_parameters.py +++ b/samples/schema/postman-collection/python/test/test_parameters.py @@ -17,9 +17,27 @@ def setUp(self): def tearDown(self): pass + def _iter_items(self, items): + for item in items: + yield item + children = item.get('item', []) + if isinstance(children, list): + for child in self._iter_items(children): + yield child + + def _find_item_by_name(self, name): + for item in self._iter_items(self.json_data.get('item', [])): + if item.get('name') == name: + return item + return None + + def _get_query_param_request(self): + item = self._find_item_by_name('Get User Info by Query Param') + self.assertIsNotNone(item) + return item['request'] + def test_request_parameter_description(self): - # request url - request = self.json_data['item'][2]['item'][1]['item'][0]['request'] + request = self._get_query_param_request() self.assertEqual(request['url']['raw'], '{{baseUrl}}/users/') # first query parameter self.assertEqual(request['url']['query'][0]['key'], 'pUserId') @@ -27,15 +45,13 @@ def test_request_parameter_description(self): self.assertEqual(request['url']['query'][0]['description'], 'Query Id.') def test_request_parameter_required(self): - # request url - request = self.json_data['item'][2]['item'][1]['item'][0]['request'] + request = self._get_query_param_request() self.assertEqual(request['url']['raw'], '{{baseUrl}}/users/') # first query parameter self.assertEqual(request['url']['query'][0]['disabled'], False) def test_request_header(self): - # request url - request = self.json_data['item'][2]['item'][1]['item'][0]['request'] + request = self._get_query_param_request() self.assertEqual(request['url']['raw'], '{{baseUrl}}/users/') # headers self.assertEqual(request['header'][0]['key'], 'Accept')