From 09f5158b9b518c0ffb278ad37f0239f3ff7f9b8d Mon Sep 17 00:00:00 2001 From: Robin Mackaij Date: Wed, 8 Apr 2026 09:27:52 +0000 Subject: [PATCH 1/2] Move header sanitazion to validate_response_using_validator --- .../keyword_logic/validation.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/OpenApiLibCore/keyword_logic/validation.py b/src/OpenApiLibCore/keyword_logic/validation.py index 063bc83..d0a0d48 100644 --- a/src/OpenApiLibCore/keyword_logic/validation.py +++ b/src/OpenApiLibCore/keyword_logic/validation.py @@ -353,6 +353,19 @@ def validate_response_using_validator( ) -> None: openapi_request = RequestsOpenAPIRequest(response.request) openapi_response = RequestsOpenAPIResponse(response) + + # sanitize the response header; a charset in the content-type causes a deserialization error + content_type = response.headers.get("Content-Type", "") + if content_type: + key_value = "Content-Type" + else: + content_type = response.headers.get("content-type", "") + if content_type: + key_value = "content-type" + if "json" in content_type.lower(): + content_type, _, _ = content_type.partition(";") + response.headers.update({key_value: content_type}) # pyright: ignore[reportPossiblyUnboundVariable] + response_validator(request=openapi_request, response=openapi_response) @@ -365,16 +378,6 @@ def _validate_response( response_validation: str, ) -> None: try: - content_type = response.headers.get("Content-Type", "") - if content_type: - key_value = "Content-Type" - else: - content_type = response.headers.get("content-type", "") - if content_type: - key_value = "content-type" - if "json" in content_type.lower(): - content_type, _, _ = content_type.partition(";") - response.headers.update({key_value: content_type}) # pyright: ignore[reportPossiblyUnboundVariable] validate_response_using_validator( response=response, response_validator=response_validator, From 061dc6ffd28d66fe9fdd0dd27097d25cc8e87d2f Mon Sep 17 00:00:00 2001 From: Robin Mackaij Date: Wed, 8 Apr 2026 11:57:47 +0200 Subject: [PATCH 2/2] Updated release notes, version bump and updated docs. --- docs/coverage-badge.svg | 2 +- docs/openapi_libcore.html | 4 ++-- docs/openapidriver.html | 22 ++++++++++-------- docs/releases.md | 26 ++++++++++++++-------- pyproject.toml | 2 +- src/OpenApiDriver/openapidriver.libspec | 4 ++-- src/OpenApiLibCore/openapi_libcore.libspec | 4 ++-- 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/docs/coverage-badge.svg b/docs/coverage-badge.svg index 38d800a..65d624f 100644 --- a/docs/coverage-badge.svg +++ b/docs/coverage-badge.svg @@ -1 +1 @@ -coverage: 91.44%coverage91.44% \ No newline at end of file +coverage: 91.58%coverage91.58% \ No newline at end of file diff --git a/docs/openapi_libcore.html b/docs/openapi_libcore.html index db87e8e..e4ef741 100644 --- a/docs/openapi_libcore.html +++ b/docs/openapi_libcore.html @@ -4,9 +4,9 @@ - + diff --git a/docs/openapidriver.html b/docs/openapidriver.html index 047fffb..e8bf76e 100644 --- a/docs/openapidriver.html +++ b/docs/openapidriver.html @@ -4,9 +4,9 @@ - + @@ -32,7 +32,7 @@

Opening library documentation failed

- + @@ -58,7 +58,7 @@

Opening library documentation failed

>{{scope}}{{/if}}
-

{{t "intro"}}

+

{{t "intro"}}

{{{doc}}}
@@ -155,8 +155,12 @@

{{t "doc"}}

{{#if tags.length}} - + + {{/if}}

{{t "keywords"}} @@ -281,9 +285,9 @@

{{t "doc"}}

{{/if}} @@ -378,10 +382,10 @@

{{t "usages"}}

{{generated}}.

- + data-v-2754030d="" fill="var(--text-color)">`,t.classList.add("modal-close-button");let r=document.createElement("div");r.classList.add("modal-close-button-container"),r.appendChild(t),t.addEventListener("click",()=>{rl()}),e.appendChild(r),r.addEventListener("click",()=>{rl()});let n=document.createElement("div");n.id="modal",n.classList.add("modal"),n.addEventListener("click",({target:e})=>{"A"===e.tagName.toUpperCase()&&rl()});let o=document.createElement("div");o.id="modal-content",o.classList.add("modal-content"),n.appendChild(o),e.appendChild(n),document.body.appendChild(e),document.addEventListener("keydown",({key:e})=>{"Escape"===e&&rl()})}()}renderTemplates(){this.renderLibdocTemplate("base",this.libdoc,"#root"),this.libdoc.inits.length>0&&this.renderImporting(),this.renderShortcuts(),this.renderKeywords(),this.renderLibdocTemplate("data-types"),this.renderLibdocTemplate("footer")}initHashEvents(){window.addEventListener("hashchange",function(){document.getElementsByClassName("hamburger-menu")[0].checked=!1},!1),window.addEventListener("hashchange",function(){if(0==window.location.hash.indexOf("#type-")){let e="#type-modal-"+decodeURI(window.location.hash.slice(6)),t=document.querySelector(".data-types").querySelector(e);t&&rs(t)}},!1),this.scrollToHash()}initTagSearch(){let e=new URLSearchParams(window.location.search),t=this.libdoc.showTags||[],r=this.libdoc.hideTags||[];if(e.has("showtag")&&(t=e.getAll("showtag")),e.has("hidetag")&&(r=e.getAll("hidetag")),this.libdoc.tags.length){this.libdoc.selectedShowTags=t,this.libdoc.selectedHideTags=r,this.renderTemplate("tags-shortcuts",{tags:this.libdoc.tags,selectedTags:t},"#show-tags-shortcuts-container"),this.renderTemplate("tags-shortcuts",{tags:this.libdoc.tags,selectedTags:r},"#hide-tags-shortcuts-container");let e=()=>{let e=document.getElementById("show-tags-shortcuts-container"),t=document.getElementById("hide-tags-shortcuts-container"),r=Array.from(e.selectedOptions).map(e=>e.value),n=Array.from(t.selectedOptions).map(e=>e.value);this.tagsSearch(r,n,window.location.hash)};document.getElementById("show-tags-shortcuts-container").onchange=e,document.getElementById("hide-tags-shortcuts-container").onchange=e,(t.length>0||r.length>0)&&this.tagsSearch(t,r,window.location.hash)}}initLanguageMenu(){this.renderTemplate("language",{languages:this.translations.getLanguageCodes()}),document.querySelectorAll("#language-container ul a").forEach(e=>{e.innerHTML===this.translations.currentLanguage()&&e.classList.toggle("selected"),e.addEventListener("click",()=>{this.translations.setLanguage(e.innerHTML)&&this.render()})}),document.querySelector("#language-container button").addEventListener("click",()=>{document.querySelector("#language-container ul").classList.toggle("hidden")})}renderImporting(){this.renderLibdocTemplate("importing"),this.registerTypeDocHandlers("#importing-container")}renderShortcuts(){this.renderLibdocTemplate("shortcuts"),document.getElementById("toggle-keyword-shortcuts").addEventListener("click",()=>this.toggleShortcuts()),document.querySelector(".clear-search").addEventListener("click",()=>this.clearSearch()),document.querySelector(".search-input").addEventListener("keydown",()=>rc(()=>this.searching(),150)),this.renderLibdocTemplate("keyword-shortcuts"),document.querySelectorAll("a.match").forEach(e=>e.addEventListener("click",this.closeMenu))}registerTypeDocHandlers(e){document.querySelectorAll(`${e} a.type`).forEach(e=>e.addEventListener("click",e=>{let t=e.target.dataset.typedoc;rs(document.querySelector(`#type-modal-${t}`))}))}renderKeywords(e=null){null==e&&(e=this.libdoc),this.renderLibdocTemplate("keywords",e),document.querySelectorAll(".kw-tags span").forEach(e=>{e.addEventListener("click",e=>{let t=e.target.innerText,r=document.getElementById("show-tags-shortcuts-container");r&&Array.from(r.options).forEach(e=>{e.selected=e.value===t});let n=document.getElementById("hide-tags-shortcuts-container");n&&(n.selectedIndex=-1),this.tagsSearch([t],[],window.location.hash)})}),this.registerTypeDocHandlers("#keywords-container"),document.getElementById("keyword-statistics-header").innerText=""+this.libdoc.keywords.length}setTheme(){document.documentElement.setAttribute("data-theme",this.getTheme())}getTheme(){return null!=this.libdoc.theme?this.libdoc.theme:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}scrollToHash(){if(window.location.hash){let e=window.location.hash.substring(1),t=document.getElementById(decodeURIComponent(e));null!=t&&t.scrollIntoView()}}tagsSearch(e,t,r){this.libdoc.selectedShowTags=e,this.libdoc.selectedHideTags=t;let n=document.getElementsByClassName("search-input")[0].value,o=new URLSearchParams(window.location.search);o.delete("showtag"),o.delete("hidetag"),e.forEach(e=>o.append("showtag",e)),t.forEach(e=>o.append("hidetag",e));let i=o.toString();i&&(i="?"+i);let a=window.location.pathname+i+(r||"");this.applyFilters(n,e,t),n&&this.highlightMatches(n,{name:!0,args:!0,doc:!0,tags:!0}),history.replaceState&&history.replaceState(null,"",a),document.getElementById("keyword-shortcuts-container").scrollTop=0}searching(){this.searchTime=Date.now();let e=document.getElementsByClassName("search-input")[0].value,t=this.libdoc.selectedShowTags||[],r=this.libdoc.selectedHideTags||[];requestAnimationFrame(()=>{this.applyFilters(e,t,r,this.searchTime,()=>{e&&this.highlightMatches(e,{name:!0,args:!0,doc:!0,tags:!0},this.searchTime),document.getElementById("keyword-shortcuts-container").scrollTop=0})})}highlightMatches(e,t,n){if(n&&n!==this.searchTime)return;let o=document.querySelectorAll("#shortcuts-container .match"),i=document.querySelectorAll("#keywords-container .match");if(t.name&&(new(r(eg))(o).mark(e),new(r(eg))(i).mark(e)),t.args&&new(r(eg))(document.querySelectorAll("#keywords-container .match .args")).mark(e),t.doc&&new(r(eg))(document.querySelectorAll("#keywords-container .match .doc")).mark(e),t.tags){let n=document.querySelectorAll("#keywords-container .match .tags a, #tags-shortcuts-container .match .tags a");if(t.tagsExact){let t=[];n.forEach(r=>{r.textContent?.toUpperCase()==e.toUpperCase()&&t.push(r)}),new(r(eg))(t).mark(e)}else new(r(eg))(n).mark(e)}}applyFilters(e,t,r,n,o){if(n&&n!==this.searchTime)return;let i=e.length>0,a=null;i&&(a=RegExp(e.replace(/[-[\]{}()+?*.,\\^$|#]/g,"\\$&"),"i"));let s=a?a.test.bind(a):()=>!1,l={},c=0;l.keywords=this.libdoc.keywords.map(e=>{let n={...e};return n.hidden=!1,t.length>0&&(t.some(e=>"[No tags]"===e?0===n.tags.length:n.tags.includes(e))||(n.hidden=!0)),r.length>0&&r.some(e=>"[No tags]"===e?0===n.tags.length:n.tags.includes(e))&&(n.hidden=!0),i&&!n.hidden&&(s(n.name)||s(n.args)||s(n.doc)||n.tags.some(s)||(n.hidden=!0)),!n.hidden&&c++,n}),this.renderLibdocTemplate("keyword-shortcuts",l),this.renderKeywords(l),document.getElementById("keyword-statistics-header").innerText=c+" / "+l.keywords.length,0===c&&(document.querySelector("#keywords-container table").innerHTML=""),o&&requestAnimationFrame(o)}closeMenu(){document.getElementById("hamburger-menu-input").checked=!1}openKeywordWall(){document.getElementsByClassName("shortcuts")[0].classList.add("keyword-wall"),this.storage.set("keyword-wall","open"),document.getElementById("toggle-keyword-shortcuts").innerText="-"}closeKeywordWall(){document.getElementsByClassName("shortcuts")[0].classList.remove("keyword-wall"),this.storage.set("keyword-wall","close"),document.getElementById("toggle-keyword-shortcuts").innerText="+"}toggleShortcuts(){document.getElementsByClassName("shortcuts")[0].classList.contains("keyword-wall")?this.closeKeywordWall():this.openKeywordWall()}resetKeywords(){this.libdoc.selectedShowTags=[],this.libdoc.selectedHideTags=[];let e=document.getElementById("show-tags-shortcuts-container");e&&(e.selectedIndex=-1);let t=document.getElementById("hide-tags-shortcuts-container");t&&(t.selectedIndex=-1),this.applyFilters("",[],[]),history.replaceState&&history.replaceState(null,"",location.pathname)}clearSearch(){document.getElementsByClassName("search-input")[0].value="",this.resetKeywords()}renderLibdocTemplate(e,t=null,r=""){null==t&&(t=this.libdoc),this.renderTemplate(e,t,r)}renderTemplate(e,t,n=""){let o=document.getElementById(`${e}-template`)?.innerHTML,i=r(ef).compile(o);""===n&&(n=`#${e}-container`),document.body.querySelector(n).innerHTML=i(t)}},rh=libdoc;const rp=new eh("libdoc"),rd=ed.getInstance(rh.lang);new ru(rh,rp,rd).render(); diff --git a/docs/releases.md b/docs/releases.md index 45990be..d27de41 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,8 +1,18 @@ # Release notes -## OpenApiTools v2.0.0 +## OpenApiTools v2.0.1 -### Major changes and new features +### Bugfixes +- Moved header sanitazion to ensure it's always applied. + - This closes [issue #137: Error on DataValidationError](https://github.com/MarketSquare/robotframework-openapitools/issues/137). + +


+ +## Previous versions + +### OpenApiTools v2.0.0 + +#### Major changes and new features - Request bodies now support all JSON types, not just `objects` (`dicts`). - This closes [issue #9: No body generated when root is a list](https://github.com/MarketSquare/robotframework-openapitools/issues/9). - Some of the `Relations` still need to be reworked to (fully) align with this change. @@ -32,7 +42,7 @@ - The `Test Endpoint` keyword of OpenApiDriver now also support error codes from a `PathPropertiesConstraint`. - This closes [issue #126: Path invalidation based on error code not supported by OpenApiDriver](https://github.com/MarketSquare/robotframework-openapitools/issues/126). -### Bugfixes +#### Bugfixes - Added support for the `nullable` property in OAS 3.0 schemas when generating data. - This closes [issue #81: nullable not taken into account in get_valid_value](https://github.com/MarketSquare/robotframework-openapitools/issues/81). - Support added for multiple instances of OpenApiLibCore within the same suite. @@ -46,7 +56,7 @@ - Fixed validation errors caused by `Content-Type` not being handled case-insensitive. - Fixed an exception during validation caused by `charset` being included in the `Content-Type` header for `application/json`. -### Breaking changes +#### Breaking changes - Addressing [issue #95: Refactor: better name for Dto](https://github.com/MarketSquare/robotframework-openapitools/issues/95) introduces a number breaking renames: - `Dto` has been renamed to `RelationsMapping`. - `constraint_mapping` has been renamed to `relations_mapping` in a number of places. @@ -57,7 +67,7 @@ - The `relations_mapping` property was added. - `invalid_property_default_response` library parameter renamed to `invalid_data_default_response`. -### Additional changes +#### Additional changes - Special handling of `"format": "byte"` for `"type": "string"` (OAS 3.0) was removed. - While some logic related to this worked, the result was never JSON-serializable. - The devcontainer setup was updated. @@ -65,13 +75,11 @@ - Updated minimum version markers for many dependencies. - Annotations are now complete (as far as possible under Python 3.10). -### Notes +#### Notes - The documentation is not yet updated with many of the changes / improvements detailed above. - While some tests have been updated to check / demonstrate the above changes, more tests need to be added. -


- -## Previous versions +--- ### OpenApiTools v1.0.5 diff --git a/pyproject.toml b/pyproject.toml index 66f5a46..5b11431 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name="robotframework-openapitools" -version = "2.0.0" +version = "2.0.1" description = "A set of Robot Framework libraries to test APIs for which the OAS is available." authors = [ {name = "Robin Mackaij", email = "r.a.mackaij@gmail.com"}, diff --git a/src/OpenApiDriver/openapidriver.libspec b/src/OpenApiDriver/openapidriver.libspec index cb08874..c165ae3 100644 --- a/src/OpenApiDriver/openapidriver.libspec +++ b/src/OpenApiDriver/openapidriver.libspec @@ -1,6 +1,6 @@ - -2.0.0 + +2.0.1 The OpenApiDriver library provides the keywords and logic for execution of generated test cases based on an OpenAPI document. Visit the <a href="./index.html" target="_blank">OpenApiTools documentation</a> for an introduction. diff --git a/src/OpenApiLibCore/openapi_libcore.libspec b/src/OpenApiLibCore/openapi_libcore.libspec index 41abd26..8d0248c 100644 --- a/src/OpenApiLibCore/openapi_libcore.libspec +++ b/src/OpenApiLibCore/openapi_libcore.libspec @@ -1,6 +1,6 @@ - -2.0.0 + +2.0.1 The OpenApiLibCore library provides the keywords and core logic to interact with an OpenAPI server. Visit the <a href="./index.html" target="_blank">OpenApiTools documentation</a> for an introduction.