From fbef5be362ea527e486d7586a3c51b956f8cc046 Mon Sep 17 00:00:00 2001 From: khayashi4337 Date: Thu, 5 Nov 2026 11:58:53 +0900 Subject: [PATCH 1/2] fix(python-flask, python-blueplanet): emitted tests are invalid Python when an operation has no cookie params The {{#cookieParams}} block opens mid-line after 8 spaces of indentation, so when the section is empty it leaves those spaces behind while the standalone close tag swallows the line break. The dangling indent merges into the next statement, producing ` response = ...` (an unexpected-indent IndentationError) for any operation without cookie parameters. Move the section tags onto their own lines so an empty section emits nothing; operations with cookie params render the same block, with continuation items indented under the headers assignment. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Claude Sonnet 5.5 --- .../app/{{packageName}}/test/controller_test.mustache | 3 ++- .../src/main/resources/python-flask/controller_test.mustache | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/python-blueplanet/app/{{packageName}}/test/controller_test.mustache b/modules/openapi-generator/src/main/resources/python-blueplanet/app/{{packageName}}/test/controller_test.mustache index ae6636192d50..899bdcdb5aa7 100644 --- a/modules/openapi-generator/src/main/resources/python-blueplanet/app/{{packageName}}/test/controller_test.mustache +++ b/modules/openapi-generator/src/main/resources/python-blueplanet/app/{{packageName}}/test/controller_test.mustache @@ -28,7 +28,8 @@ class {{#operations}}Test{{classname}}(BaseTestCase): {{#headerParams}} {{#-first}}headers = [{{/-first}}{{^-first}} {{/-first}}('{{baseName}}', {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}{{{example}}}{{/vendorExtensions.x-python-connexion-test-header-value}}){{^-last}},{{/-last}}{{#-last}}]{{/-last}} {{/headerParams}} - {{#cookieParams}}{{#-first}}{{^headerParams}}headers = [] + {{#cookieParams}} + {{#-first}}{{^headerParams}}headers = [] {{/headerParams}}headers += [('Cookie', '; '.join([{{/-first}}'{{baseName}}=' + {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}str({{{example}}}){{/vendorExtensions.x-python-connexion-test-header-value}}{{^-last}}, {{/-last}}{{#-last}}]))]{{/-last}} {{/cookieParams}} {{#formParams}} diff --git a/modules/openapi-generator/src/main/resources/python-flask/controller_test.mustache b/modules/openapi-generator/src/main/resources/python-flask/controller_test.mustache index 92872aa1fb02..e1ae56ac5a93 100644 --- a/modules/openapi-generator/src/main/resources/python-flask/controller_test.mustache +++ b/modules/openapi-generator/src/main/resources/python-flask/controller_test.mustache @@ -32,7 +32,8 @@ class {{#operations}}Test{{classname}}(BaseTestCase): {{#isOAuth}}'Authorization': 'Bearer special-key',{{/isOAuth}}{{#isApiKey}}{{#isKeyInHeader}}'{{keyParamName}}': 'special-key',{{/isKeyInHeader}}{{/isApiKey}}{{#isBasicBasic}}'Authorization': 'Basic Zm9vOmJhcg==',{{/isBasicBasic}}{{#isBasicBearer}}'Authorization': 'Bearer special-key',{{/isBasicBearer}}{{/authMethods}}{{#vendorExtensions.x-test-apikey-cookie}} 'Cookie': '{{{vendorExtensions.x-test-apikey-cookie}}}',{{/vendorExtensions.x-test-apikey-cookie}} } - {{#cookieParams}}{{#-first}}headers['Cookie'] = '; '.join([{{/-first}}'{{{vendorExtensions.x-python-connexion-quoted-name}}}=' + {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}str({{{example}}}){{/vendorExtensions.x-python-connexion-test-header-value}}{{^-last}}, {{/-last}}{{#-last}}]){{/-last}} + {{#cookieParams}} + {{#-first}}headers['Cookie'] = '; '.join([{{/-first}}'{{{vendorExtensions.x-python-connexion-quoted-name}}}=' + {{#vendorExtensions.x-python-connexion-test-header-value}}{{{vendorExtensions.x-python-connexion-test-header-value}}}{{/vendorExtensions.x-python-connexion-test-header-value}}{{^vendorExtensions.x-python-connexion-test-header-value}}str({{{example}}}){{/vendorExtensions.x-python-connexion-test-header-value}}{{^-last}}, {{/-last}}{{#-last}}]){{/-last}} {{/cookieParams}} {{#formParams}} {{#-first}}data = { {{/-first}}{{^-first}} {{/-first}}'{{baseName}}': {{{example}}}{{^-last}},{{/-last}}{{#-last}} }{{/-last}} From 1348d47851d23221255edb0116f122765cfad092 Mon Sep 17 00:00:00 2001 From: khayashi4337 Date: Thu, 5 Nov 2026 12:19:39 +0900 Subject: [PATCH 2/2] test(python-flask, python-blueplanet): pin generated-test statement indentation without python3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Java-only regression check for the cookie-param indentation fix: for an operation without cookie params and one with only cookie params, the generated test_default_controller.py is read back and every affected statement is pinned to its expected column — `response = ...`/`assert200` at 8, plus `headers['Cookie'] = ...` (flask) or `headers = []` / `headers += [('Cookie', ...)]` (blueplanet) for the cookie operation. Reverting the template fix makes the check fail (response line rendered at column 16), so it catches the IndentationError shape without invoking python3; python-aiohttp is out of scope as its cookie block already opens the section on its own line. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-Authored-By: Claude Sonnet 5.5 --- ...tractPythonConnexionServerCodegenTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java index 50db4a9252d7..3de1432b1059 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/AbstractPythonConnexionServerCodegenTest.java @@ -1015,6 +1015,51 @@ public void testJsonContentHeaderParamsGeneratedTestsCompile() throws IOExceptio } } + @Test(description = "statements in generated tests keep their 8-column indent whether or not the operation declares cookie params (an empty {{#cookieParams}} section must not leak the open line's indent into the next statement)") + public void testGeneratedTestsStatementIndentWithAndWithoutCookieParams() throws IOException { + // /ping declares no parameters at all: the empty cookieParams section + // must render nothing, so `response = ...` stays at the same indent as + // the statements around it — a mid-line {{#cookieParams}} tag used to + // leave its 8 leading spaces behind, which merged into the next + // statement and produced an unexpected-indent IndentationError + for (Map.Entry variant : generateConnexionVariants( + "src/test/resources/3_0/python/info-title-special-chars.yaml").entrySet()) { + if (variant.getKey().startsWith("python-aiohttp")) { + // aiohttp's cookie block already opens the section on its own + // line and emits a `cookies = {...}` dict instead of a header + continue; + } + Path testFile = generatedTestPath(variant.getKey(), variant.getValue()); + assertFileExists(testFile); + String ping = generatedTestMethodBody(testFile, "test_ping"); + assertStatementIndent(ping, "response = self.client.open(", 8); + assertStatementIndent(ping, "self.assert200(response,", 8); + } + + // get_cookie_only declares only cookie params: the Cookie statement + // must render at statement level too — flask mutates the headers dict, + // blueplanet appends a ('Cookie', ...) tuple (creating the list first + // when the operation has no header params to initialize it) + for (Map.Entry variant : generateConnexionVariants( + "src/test/resources/3_0/python/connexion-json-header-test-values.yaml").entrySet()) { + String variantKey = variant.getKey(); + if (variantKey.startsWith("python-aiohttp")) { + continue; + } + Path testFile = generatedTestPath(variantKey, variant.getValue()); + assertFileExists(testFile); + String cookieOnly = generatedTestMethodBody(testFile, "test_get_cookie_only"); + if (isBlueplanetVariant(variantKey)) { + assertStatementIndent(cookieOnly, "headers = []", 8); + assertStatementIndent(cookieOnly, "headers += [('Cookie',", 8); + } else { + assertStatementIndent(cookieOnly, "headers['Cookie'] = '; '.join([", 8); + } + assertStatementIndent(cookieOnly, "response = self.client.open(", 8); + assertStatementIndent(cookieOnly, "self.assert200(response,", 8); + } + } + @Test(description = "content (application/json) header/cookie params get X.from_dict() only for generated model classes; inline object schemas pass the json.loads result through") public void testJsonContentHeaderParamsConvertOnlyGeneratedModels() throws IOException { String specPath = "src/test/resources/3_0/python/connexion-json-header-test-values.yaml"; @@ -1276,6 +1321,28 @@ public void testBlueplanetRequirementsPinConnexion2() throws IOException { "swagger-ui-bundle >= 0.0.2"); } + /** + * Asserts every line in {@code text} containing {@code marker} starts + * with exactly {@code expectedIndent} spaces — a substring check cannot + * pin indentation, since a line indented too deeply still contains the + * marker. + */ + private static void assertStatementIndent(String text, String marker, int expectedIndent) { + List matching = text.lines() + .filter(line -> line.contains(marker)) + .collect(Collectors.toList()); + assertThat(matching).as("no generated line contains: %s", marker).isNotEmpty(); + for (String line : matching) { + int indent = 0; + while (indent < line.length() && line.charAt(indent) == ' ') { + indent++; + } + assertThat(indent) + .as("line must start at column %d: %s", expectedIndent, line.trim()) + .isEqualTo(expectedIndent); + } + } + /** * Asserts the file contains a physical line equal to {@code expected} * after trimming — a stronger check than substring containment, used to