mirror of https://github.com/OCA/web.git
Adding extra functionality for svg (rotate, color, ... )
parent
be5d3f3c26
commit
12e11b00c6
|
@ -42,8 +42,34 @@ Usage
|
||||||
=====
|
=====
|
||||||
|
|
||||||
This module works in conjunction with web_iconify. Once installed, icons
|
This module works in conjunction with web_iconify. Once installed, icons
|
||||||
will be served through the proxy and cached locally. No specific usage
|
will be served through the proxy and cached locally.
|
||||||
instructions are required.
|
|
||||||
|
SVG Icon Parameters
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
You can customize SVG icons by adding query parameters to the URL. The
|
||||||
|
format is:
|
||||||
|
|
||||||
|
``/web_iconify_proxy/<prefix>/<icon>.svg?param1=value1¶m2=value2...``
|
||||||
|
|
||||||
|
Available parameters:
|
||||||
|
|
||||||
|
- **color**: Icon color (e.g., ``color=red``, ``color=%23ff0000``).
|
||||||
|
- **width**: Icon width (e.g., ``width=50``, ``width=50px``).
|
||||||
|
- **height**: Icon height (e.g., ``height=50``, ``height=50px``). If
|
||||||
|
only one dimension is specified, the other will be calculated
|
||||||
|
automatically to maintain aspect ratio.
|
||||||
|
- **flip**: Flip the icon. Possible values: ``horizontal``,
|
||||||
|
``vertical``, or both (e.g., ``flip=horizontal``, ``flip=vertical``,
|
||||||
|
``flip=horizontal,vertical``).
|
||||||
|
- **rotate**: Rotate the icon by 90, 180, or 270 degrees (e.g.,
|
||||||
|
``rotate=90``, ``rotate=180``).
|
||||||
|
- **box**: Set to ``true`` to add an empty rectangle to the SVG that
|
||||||
|
matches the viewBox (e.g., ``box=true``).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
``/web_iconify_proxy/mdi/home.svg?color=blue&width=64&flip=horizontal``
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import ast
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
@ -15,7 +16,13 @@ class IconifyProxyController(http.Controller):
|
||||||
"""Controller for proxying Iconify requests."""
|
"""Controller for proxying Iconify requests."""
|
||||||
|
|
||||||
def _fetch_iconify_data(
|
def _fetch_iconify_data(
|
||||||
self, upstream_url, content_type, prefix, icons=None, icon=None
|
self,
|
||||||
|
upstream_url,
|
||||||
|
content_type,
|
||||||
|
prefix,
|
||||||
|
icons=None,
|
||||||
|
icon=None,
|
||||||
|
normalized_params_string="",
|
||||||
):
|
):
|
||||||
"""Fetches data from the Iconify API or the local cache.
|
"""Fetches data from the Iconify API or the local cache.
|
||||||
|
|
||||||
|
@ -25,22 +32,28 @@ class IconifyProxyController(http.Controller):
|
||||||
prefix (str): The icon prefix.
|
prefix (str): The icon prefix.
|
||||||
icons (str, optional): Comma-separated list of icons (for CSS and JSON).
|
icons (str, optional): Comma-separated list of icons (for CSS and JSON).
|
||||||
icon (str, optional): The icon name (for SVG).
|
icon (str, optional): The icon name (for SVG).
|
||||||
|
normalized_params_string (str, optional): Normalized parameters string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response: The HTTP response.
|
Response: The HTTP response.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Validate prefix
|
||||||
|
prefix = prefix.lower()
|
||||||
|
|
||||||
# Validate prefix
|
# Validate prefix
|
||||||
if not re.match(r"^[a-z0-9-]+$", prefix):
|
if not re.match(r"^[a-z0-9-]+$", prefix):
|
||||||
raise request.not_found()
|
raise request.not_found()
|
||||||
|
|
||||||
# Validate icon (if provided)
|
# Validate icon (if provided)
|
||||||
if icon and not re.match(r"^[a-z0-9:-]+$", icon):
|
if icon:
|
||||||
raise request.not_found()
|
icon = icon.lower()
|
||||||
|
if not re.match(r"^[a-z0-9:-]+$", icon):
|
||||||
|
raise request.not_found()
|
||||||
|
|
||||||
# Validate icons (if provided)
|
# Validate icons (if provided)
|
||||||
if icons:
|
if icons:
|
||||||
icon_list = icons.split(",")
|
icon_list = [i.lower() for i in icons.split(",")]
|
||||||
for single_icon in icon_list:
|
for single_icon in icon_list:
|
||||||
if not re.match(r"^[a-z0-9:-]+$", single_icon):
|
if not re.match(r"^[a-z0-9:-]+$", single_icon):
|
||||||
raise request.not_found()
|
raise request.not_found()
|
||||||
|
@ -48,13 +61,13 @@ class IconifyProxyController(http.Controller):
|
||||||
|
|
||||||
Attachment = request.env["ir.attachment"].sudo()
|
Attachment = request.env["ir.attachment"].sudo()
|
||||||
if content_type == "image/svg+xml":
|
if content_type == "image/svg+xml":
|
||||||
name = f"{prefix}-{icon}"
|
name = f"{prefix}-{icon}-{normalized_params_string.lower()}"
|
||||||
res_model = "iconify.svg"
|
res_model = "iconify.svg"
|
||||||
elif content_type == "text/css":
|
elif content_type == "text/css":
|
||||||
name = f"{prefix}-{icons}"
|
name = f"{prefix}-{icons}-{normalized_params_string.lower()}"
|
||||||
res_model = "iconify.css"
|
res_model = "iconify.css"
|
||||||
elif content_type == "application/json":
|
elif content_type == "application/json":
|
||||||
name = f"{prefix}-{icons}"
|
name = f"{prefix}-{icons}-{normalized_params_string.lower()}"
|
||||||
res_model = "iconify.json"
|
res_model = "iconify.json"
|
||||||
else:
|
else:
|
||||||
raise request.not_found()
|
raise request.not_found()
|
||||||
|
@ -98,6 +111,36 @@ class IconifyProxyController(http.Controller):
|
||||||
]
|
]
|
||||||
return request.make_response(data, headers)
|
return request.make_response(data, headers)
|
||||||
|
|
||||||
|
def _normalize_params_common(self, params):
|
||||||
|
"""Normalizes common parameters for Iconify requests."""
|
||||||
|
normalized = []
|
||||||
|
for key in sorted(params.keys()): # Sort keys alphabetically
|
||||||
|
normalized.append(f"{key.lower()}={params[key]}")
|
||||||
|
return ";".join(normalized)
|
||||||
|
|
||||||
|
def _normalize_params_svg(self, params):
|
||||||
|
"""Normalizes parameters specifically for SVG requests."""
|
||||||
|
allowed_params = ["color", "width", "height", "flip", "rotate", "box"]
|
||||||
|
normalized = []
|
||||||
|
for key in sorted(params.keys()):
|
||||||
|
if key in allowed_params:
|
||||||
|
value = params[key]
|
||||||
|
key = key.lower()
|
||||||
|
# Basic type validation
|
||||||
|
if key in ("width", "height", "rotate") and not (
|
||||||
|
isinstance(value, str) or isinstance(value, int)
|
||||||
|
):
|
||||||
|
continue # Skip invalid values
|
||||||
|
if key == "box":
|
||||||
|
try:
|
||||||
|
value = ast.literal_eval(str(value).lower())
|
||||||
|
if not isinstance(value, bool):
|
||||||
|
continue
|
||||||
|
except (ValueError, SyntaxError):
|
||||||
|
continue
|
||||||
|
normalized.append(f"{key}={value}")
|
||||||
|
return ";".join(normalized)
|
||||||
|
|
||||||
@http.route(
|
@http.route(
|
||||||
"/web_iconify_proxy/<string:prefix>/<string:icon>.svg",
|
"/web_iconify_proxy/<string:prefix>/<string:icon>.svg",
|
||||||
type="http",
|
type="http",
|
||||||
|
@ -115,9 +158,21 @@ class IconifyProxyController(http.Controller):
|
||||||
Returns:
|
Returns:
|
||||||
Response: The HTTP response containing the SVG data.
|
Response: The HTTP response containing the SVG data.
|
||||||
"""
|
"""
|
||||||
upstream_url = f"https://api.iconify.design/{prefix}/{icon}.svg"
|
normalized_params = self._normalize_params_svg(params)
|
||||||
|
if normalized_params:
|
||||||
|
query_string = normalized_params.replace(";", "&")
|
||||||
|
upstream_url = (
|
||||||
|
f"https://api.iconify.design/{prefix}/{icon}.svg?{query_string}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
upstream_url = f"https://api.iconify.design/{prefix}/{icon}.svg"
|
||||||
|
|
||||||
return self._fetch_iconify_data(
|
return self._fetch_iconify_data(
|
||||||
upstream_url, "image/svg+xml", prefix, icon=icon
|
upstream_url,
|
||||||
|
"image/svg+xml",
|
||||||
|
prefix,
|
||||||
|
icon=icon,
|
||||||
|
normalized_params_string=normalized_params,
|
||||||
)
|
)
|
||||||
|
|
||||||
@http.route(
|
@http.route(
|
||||||
|
|
|
@ -1,3 +1,21 @@
|
||||||
This module works in conjunction with web_iconify. Once installed, icons
|
This module works in conjunction with web_iconify. Once installed, icons
|
||||||
will be served through the proxy and cached locally. No specific usage
|
will be served through the proxy and cached locally.
|
||||||
instructions are required.
|
|
||||||
|
## SVG Icon Parameters
|
||||||
|
|
||||||
|
You can customize SVG icons by adding query parameters to the URL. The format is:
|
||||||
|
|
||||||
|
`/web_iconify_proxy/<prefix>/<icon>.svg?param1=value1¶m2=value2...`
|
||||||
|
|
||||||
|
Available parameters:
|
||||||
|
|
||||||
|
* **color**: Icon color (e.g., `color=red`, `color=%23ff0000`).
|
||||||
|
* **width**: Icon width (e.g., `width=50`, `width=50px`).
|
||||||
|
* **height**: Icon height (e.g., `height=50`, `height=50px`). If only one dimension is specified, the other will be calculated automatically to maintain aspect ratio.
|
||||||
|
* **flip**: Flip the icon. Possible values: `horizontal`, `vertical`, or both (e.g., `flip=horizontal`, `flip=vertical`, `flip=horizontal,vertical`).
|
||||||
|
* **rotate**: Rotate the icon by 90, 180, or 270 degrees (e.g., `rotate=90`, `rotate=180`).
|
||||||
|
* **box**: Set to `true` to add an empty rectangle to the SVG that matches the viewBox (e.g., `box=true`).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
`/web_iconify_proxy/mdi/home.svg?color=blue&width=64&flip=horizontal`
|
||||||
|
|
|
@ -377,16 +377,19 @@ ir.attachment model.</p>
|
||||||
<p><strong>Table of contents</strong></p>
|
<p><strong>Table of contents</strong></p>
|
||||||
<div class="contents local topic" id="contents">
|
<div class="contents local topic" id="contents">
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
|
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a><ul>
|
||||||
<li><a class="reference internal" href="#changelog" id="toc-entry-2">Changelog</a><ul>
|
<li><a class="reference internal" href="#svg-icon-parameters" id="toc-entry-2">SVG Icon Parameters</a></li>
|
||||||
<li><a class="reference internal" href="#section-1" id="toc-entry-3">18.0.1.0.0 (2025-02-23)</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
|
<li><a class="reference internal" href="#changelog" id="toc-entry-3">Changelog</a><ul>
|
||||||
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
|
<li><a class="reference internal" href="#section-1" id="toc-entry-4">18.0.1.0.0 (2025-02-23)</a></li>
|
||||||
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
|
</ul>
|
||||||
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
|
</li>
|
||||||
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
|
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-5">Bug Tracker</a></li>
|
||||||
|
<li><a class="reference internal" href="#credits" id="toc-entry-6">Credits</a><ul>
|
||||||
|
<li><a class="reference internal" href="#authors" id="toc-entry-7">Authors</a></li>
|
||||||
|
<li><a class="reference internal" href="#contributors" id="toc-entry-8">Contributors</a></li>
|
||||||
|
<li><a class="reference internal" href="#maintainers" id="toc-entry-9">Maintainers</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -394,20 +397,42 @@ ir.attachment model.</p>
|
||||||
<div class="section" id="usage">
|
<div class="section" id="usage">
|
||||||
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
|
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
|
||||||
<p>This module works in conjunction with web_iconify. Once installed, icons
|
<p>This module works in conjunction with web_iconify. Once installed, icons
|
||||||
will be served through the proxy and cached locally. No specific usage
|
will be served through the proxy and cached locally.</p>
|
||||||
instructions are required.</p>
|
<div class="section" id="svg-icon-parameters">
|
||||||
|
<h2><a class="toc-backref" href="#toc-entry-2">SVG Icon Parameters</a></h2>
|
||||||
|
<p>You can customize SVG icons by adding query parameters to the URL. The
|
||||||
|
format is:</p>
|
||||||
|
<p><tt class="docutils literal"><span class="pre">/web_iconify_proxy/<prefix>/<icon>.svg?param1=value1&param2=value2...</span></tt></p>
|
||||||
|
<p>Available parameters:</p>
|
||||||
|
<ul class="simple">
|
||||||
|
<li><strong>color</strong>: Icon color (e.g., <tt class="docutils literal">color=red</tt>, <tt class="docutils literal"><span class="pre">color=%23ff0000</span></tt>).</li>
|
||||||
|
<li><strong>width</strong>: Icon width (e.g., <tt class="docutils literal">width=50</tt>, <tt class="docutils literal">width=50px</tt>).</li>
|
||||||
|
<li><strong>height</strong>: Icon height (e.g., <tt class="docutils literal">height=50</tt>, <tt class="docutils literal">height=50px</tt>). If
|
||||||
|
only one dimension is specified, the other will be calculated
|
||||||
|
automatically to maintain aspect ratio.</li>
|
||||||
|
<li><strong>flip</strong>: Flip the icon. Possible values: <tt class="docutils literal">horizontal</tt>,
|
||||||
|
<tt class="docutils literal">vertical</tt>, or both (e.g., <tt class="docutils literal">flip=horizontal</tt>, <tt class="docutils literal">flip=vertical</tt>,
|
||||||
|
<tt class="docutils literal">flip=horizontal,vertical</tt>).</li>
|
||||||
|
<li><strong>rotate</strong>: Rotate the icon by 90, 180, or 270 degrees (e.g.,
|
||||||
|
<tt class="docutils literal">rotate=90</tt>, <tt class="docutils literal">rotate=180</tt>).</li>
|
||||||
|
<li><strong>box</strong>: Set to <tt class="docutils literal">true</tt> to add an empty rectangle to the SVG that
|
||||||
|
matches the viewBox (e.g., <tt class="docutils literal">box=true</tt>).</li>
|
||||||
|
</ul>
|
||||||
|
<p>Example:</p>
|
||||||
|
<p><tt class="docutils literal"><span class="pre">/web_iconify_proxy/mdi/home.svg?color=blue&width=64&flip=horizontal</span></tt></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="changelog">
|
<div class="section" id="changelog">
|
||||||
<h1><a class="toc-backref" href="#toc-entry-2">Changelog</a></h1>
|
<h1><a class="toc-backref" href="#toc-entry-3">Changelog</a></h1>
|
||||||
<div class="section" id="section-1">
|
<div class="section" id="section-1">
|
||||||
<h2><a class="toc-backref" href="#toc-entry-3">18.0.1.0.0 (2025-02-23)</a></h2>
|
<h2><a class="toc-backref" href="#toc-entry-4">18.0.1.0.0 (2025-02-23)</a></h2>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li>Initial version.</li>
|
<li>Initial version.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="bug-tracker">
|
<div class="section" id="bug-tracker">
|
||||||
<h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
|
<h1><a class="toc-backref" href="#toc-entry-5">Bug Tracker</a></h1>
|
||||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
|
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
|
||||||
In case of trouble, please check there if your issue has already been reported.
|
In case of trouble, please check there if your issue has already been reported.
|
||||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||||
|
@ -415,21 +440,21 @@ If you spotted it first, help us to smash it by providing a detailed and welcome
|
||||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="credits">
|
<div class="section" id="credits">
|
||||||
<h1><a class="toc-backref" href="#toc-entry-5">Credits</a></h1>
|
<h1><a class="toc-backref" href="#toc-entry-6">Credits</a></h1>
|
||||||
<div class="section" id="authors">
|
<div class="section" id="authors">
|
||||||
<h2><a class="toc-backref" href="#toc-entry-6">Authors</a></h2>
|
<h2><a class="toc-backref" href="#toc-entry-7">Authors</a></h2>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li>jaco.tech</li>
|
<li>jaco.tech</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="contributors">
|
<div class="section" id="contributors">
|
||||||
<h2><a class="toc-backref" href="#toc-entry-7">Contributors</a></h2>
|
<h2><a class="toc-backref" href="#toc-entry-8">Contributors</a></h2>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li>Jaco Waes <<a class="reference external" href="mailto:jaco@jaco.tech">jaco@jaco.tech</a>></li>
|
<li>Jaco Waes <<a class="reference external" href="mailto:jaco@jaco.tech">jaco@jaco.tech</a>></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="maintainers">
|
<div class="section" id="maintainers">
|
||||||
<h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2>
|
<h2><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h2>
|
||||||
<p>This module is maintained by the OCA.</p>
|
<p>This module is maintained by the OCA.</p>
|
||||||
<a class="reference external image-reference" href="https://odoo-community.org">
|
<a class="reference external image-reference" href="https://odoo-community.org">
|
||||||
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
|
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
|
||||||
|
|
|
@ -3,8 +3,7 @@ from unittest.mock import patch
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from odoo.tests import tagged
|
from odoo.tests.common import HttpCase, tagged
|
||||||
from odoo.tests.common import HttpCase
|
|
||||||
|
|
||||||
|
|
||||||
@tagged("post_install", "-at_install")
|
@tagged("post_install", "-at_install")
|
||||||
|
@ -129,6 +128,140 @@ class TestIconifyProxyController(HttpCase):
|
||||||
# Check that content is the same
|
# Check that content is the same
|
||||||
self.assertEqual(response1.content, response2.content)
|
self.assertEqual(response1.content, response2.content)
|
||||||
|
|
||||||
|
@patch("odoo.addons.web_iconify_proxy.controllers.main.requests.get")
|
||||||
|
def test_caching_with_params(self, mock_get):
|
||||||
|
"""Test caching with different parameters."""
|
||||||
|
mock_response = requests.Response()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response._content = b"<svg>dummy content</svg>"
|
||||||
|
mock_response.headers["Content-Type"] = "image/svg+xml"
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
# First request with specific parameters
|
||||||
|
response1 = self.url_open(
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?color=red&width=50&flip=horizontal"
|
||||||
|
)
|
||||||
|
self.assertEqual(response1.status_code, 200)
|
||||||
|
self.assertFalse("X-Cached-At" in response1.headers)
|
||||||
|
|
||||||
|
# Second request with the same parameters, should be cached
|
||||||
|
response2 = self.url_open(
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?color=red&width=50&flip=horizontal"
|
||||||
|
)
|
||||||
|
self.assertEqual(response2.status_code, 200)
|
||||||
|
self.assertTrue("X-Cached-At" in response2.headers)
|
||||||
|
|
||||||
|
# Third request with different parameters, should not be cached
|
||||||
|
response3 = self.url_open(
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?color=blue&width=100"
|
||||||
|
)
|
||||||
|
self.assertEqual(response3.status_code, 200)
|
||||||
|
self.assertFalse("X-Cached-At" in response3.headers)
|
||||||
|
|
||||||
|
@patch("odoo.addons.web_iconify_proxy.controllers.main.requests.get")
|
||||||
|
def test_caching_parameter_order(self, mock_get):
|
||||||
|
"""Test that parameter order doesn't affect caching."""
|
||||||
|
mock_response = requests.Response()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response._content = b"<svg>dummy content</svg>"
|
||||||
|
mock_response.headers["Content-Type"] = "image/svg+xml"
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
# First request with specific parameter order
|
||||||
|
response1 = self.url_open(
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?color=red&width=50&flip=horizontal"
|
||||||
|
)
|
||||||
|
self.assertEqual(response1.status_code, 200)
|
||||||
|
self.assertFalse("X-Cached-At" in response1.headers)
|
||||||
|
|
||||||
|
# Second request with different parameter order, should be cached
|
||||||
|
response2 = self.url_open(
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?flip=horizontal&width=50&color=red"
|
||||||
|
)
|
||||||
|
self.assertEqual(response2.status_code, 200)
|
||||||
|
self.assertTrue("X-Cached-At" in response2.headers)
|
||||||
|
|
||||||
|
# Check that content is the same
|
||||||
|
self.assertEqual(response1.content, response2.content)
|
||||||
|
|
||||||
|
@patch("odoo.addons.web_iconify_proxy.controllers.main.requests.get")
|
||||||
|
def test_caching_boolean_values(self, mock_get):
|
||||||
|
"""Test that boolean values are case-insensitive for caching."""
|
||||||
|
mock_response = requests.Response()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response._content = b"<svg>dummy content</svg>"
|
||||||
|
mock_response.headers["Content-Type"] = "image/svg+xml"
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
# First request with "true"
|
||||||
|
response1 = self.url_open("/web_iconify_proxy/mdi/home.svg?box=true")
|
||||||
|
self.assertEqual(response1.status_code, 200)
|
||||||
|
self.assertFalse("X-Cached-At" in response1.headers)
|
||||||
|
|
||||||
|
# Second request with "True", should be cached
|
||||||
|
response2 = self.url_open("/web_iconify_proxy/mdi/home.svg?box=True")
|
||||||
|
self.assertEqual(response2.status_code, 200)
|
||||||
|
self.assertTrue("X-Cached-At" in response2.headers)
|
||||||
|
|
||||||
|
# Third request with "TRUE", should be cached
|
||||||
|
response3 = self.url_open("/web_iconify_proxy/mdi/home.svg?box=TRUE")
|
||||||
|
self.assertEqual(response3.status_code, 200)
|
||||||
|
self.assertTrue("X-Cached-At" in response3.headers)
|
||||||
|
|
||||||
|
# Check that content is the same for all
|
||||||
|
self.assertEqual(response1.content, response2.content)
|
||||||
|
self.assertEqual(response1.content, response3.content)
|
||||||
|
|
||||||
|
@patch("odoo.addons.web_iconify_proxy.controllers.main.requests.get")
|
||||||
|
def test_get_svg_with_parameters(self, mock_get):
|
||||||
|
"""Test the get_svg route with various valid parameters."""
|
||||||
|
mock_response = requests.Response()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response._content = b"<svg>dummy content</svg>"
|
||||||
|
mock_response.headers["Content-Type"] = "image/svg+xml"
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?color=red",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?width=50",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?height=50",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?flip=horizontal",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?flip=vertical",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?flip=horizontal,vertical",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?rotate=90",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?rotate=180",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?rotate=270",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?box=true",
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?color=blue&width=64&flip=horizontal&rotate=180",
|
||||||
|
]
|
||||||
|
for url in test_cases:
|
||||||
|
with self.subTest(url=url):
|
||||||
|
response = self.url_open(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.headers["Content-Type"], "image/svg+xml")
|
||||||
|
|
||||||
|
@patch("odoo.addons.web_iconify_proxy.controllers.main.requests.get")
|
||||||
|
def test_get_svg_with_invalid_parameters(self, mock_get):
|
||||||
|
"""Test the get_svg route with invalid parameters."""
|
||||||
|
mock_response = requests.Response()
|
||||||
|
mock_response.status_code = 200
|
||||||
|
mock_response._content = b"<svg>dummy content</svg>"
|
||||||
|
mock_response.headers["Content-Type"] = "image/svg+xml"
|
||||||
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?width=invalid", # Invalid width
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?height=invalid", # Invalid height
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?rotate=invalid", # Invalid rotate
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?box=invalid", # Invalid box
|
||||||
|
"/web_iconify_proxy/mdi/home.svg?unknown=param", # Unknown parameter
|
||||||
|
]
|
||||||
|
for url in test_cases:
|
||||||
|
with self.subTest(url=url):
|
||||||
|
response = self.url_open(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.headers["Content-Type"], "image/svg+xml")
|
||||||
|
|
||||||
@patch("odoo.addons.web_iconify_proxy.controllers.main.requests.get")
|
@patch("odoo.addons.web_iconify_proxy.controllers.main.requests.get")
|
||||||
def test_api_error(self, mock_get):
|
def test_api_error(self, mock_get):
|
||||||
# Mock requests.get to simulate an API error
|
# Mock requests.get to simulate an API error
|
||||||
|
|
Loading…
Reference in New Issue