From 9ebe9480452945859900bebc6886cf0a82ead945 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 10:15:03 +0000 Subject: [PATCH 1/3] Bump flask from 2.3.2 to 3.1.3 in /requirements Bumps [flask](https://github.com/pallets/flask) from 2.3.2 to 3.1.3. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.3.2...3.1.3) --- updated-dependencies: - dependency-name: flask dependency-version: 3.1.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements/requirements-prod.in | 2 +- requirements/requirements-prod.txt | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/requirements/requirements-prod.in b/requirements/requirements-prod.in index 0af2ccf9a..76ae03291 100644 --- a/requirements/requirements-prod.in +++ b/requirements/requirements-prod.in @@ -15,7 +15,7 @@ colorlog==6.5.0 cryptography==46.0.7 dnspython==2.6.1 flanker @ https://github.com/closeio/flanker-new/archive/fa272d95345d45e8bb1f9bc32bc2c074bf52a484.zip -Flask==2.3.2 +Flask==3.1.3 Flask-RESTful==0.3.9 gdata==2.0.18 gunicorn==23.0.0 diff --git a/requirements/requirements-prod.txt b/requirements/requirements-prod.txt index a4eff3155..044c71975 100644 --- a/requirements/requirements-prod.txt +++ b/requirements/requirements-prod.txt @@ -318,9 +318,9 @@ filelock==3.25.2 \ flanker @ https://github.com/closeio/flanker-new/archive/fa272d95345d45e8bb1f9bc32bc2c074bf52a484.zip \ --hash=sha256:a197ba8c42aa6ee69c126b001de56ccbc5c3c24bdaba683359668820d2229de7 # via -r requirements-prod.in -flask==2.3.2 \ - --hash=sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0 \ - --hash=sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef +flask==3.1.3 \ + --hash=sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb \ + --hash=sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c # via # -r requirements-prod.in # flask-restful @@ -538,9 +538,9 @@ ipython==8.10.0 \ --hash=sha256:b13a1d6c1f5818bd388db53b7107d17454129a70de2b87481d555daede5eb49e \ --hash=sha256:b38c31e8fc7eff642fc7c597061fff462537cf2314e3225a19c906b7b0d8a345 # via -r requirements-prod.in -itsdangerous==2.1.2 \ - --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ - --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a +itsdangerous==2.2.0 \ + --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \ + --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173 # via flask jedi==0.18.2 \ --hash=sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e \ @@ -698,6 +698,7 @@ markupsafe==2.1.2 \ --hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \ --hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58 # via + # flask # jinja2 # mako # werkzeug From a3cce6f58562c26198cb779f501b66bd057b1dd0 Mon Sep 17 00:00:00 2001 From: Marcos Prieto Date: Tue, 12 May 2026 12:43:07 +0200 Subject: [PATCH 2/3] Tweaks for flask upgrade and test for srv --- inbox/api/srv.py | 2 +- inbox/mailsync/frontend.py | 2 +- tests/api/test_srv.py | 40 ++++++++++++++++++++++++++++++++++++++ tests/conftest.py | 7 ------- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/inbox/api/srv.py b/inbox/api/srv.py index efe3a0943..86029239e 100644 --- a/inbox/api/srv.py +++ b/inbox/api/srv.py @@ -33,7 +33,7 @@ from .ns_api import app as ns_api app = Flask(__name__) -app.config["JSON_SORT_KEYS"] = False +app.json.sort_keys = False # Handle both /endpoint and /endpoint/ without redirecting. # Note that we need to set this *before* registering the blueprint. app.url_map.strict_slashes = False diff --git a/inbox/mailsync/frontend.py b/inbox/mailsync/frontend.py index 6e575be0d..1a5484b90 100644 --- a/inbox/mailsync/frontend.py +++ b/inbox/mailsync/frontend.py @@ -33,7 +33,7 @@ def __init__(self, port, profile) -> None: # type: ignore[no-untyped-def] def _create_app(self): # type: ignore[no-untyped-def] app = Flask(__name__) - app.config["JSON_SORT_KEYS"] = False + app.json.sort_keys = False self._create_app_impl(app) return app diff --git a/tests/api/test_srv.py b/tests/api/test_srv.py index e3425413b..8f75e4b8a 100644 --- a/tests/api/test_srv.py +++ b/tests/api/test_srv.py @@ -1,6 +1,46 @@ import json +def test_cors_headers_set_when_origin_present(test_client) -> None: + resp = test_client.get("/", headers={"Origin": "http://example.com"}) + assert resp.headers["Access-Control-Allow-Origin"] == "http://example.com" + assert "Authorization" in resp.headers["Access-Control-Allow-Headers"] + assert "Content-Type" in resp.headers["Access-Control-Allow-Headers"] + assert "GET" in resp.headers["Access-Control-Allow-Methods"] + assert resp.headers["Access-Control-Allow-Credentials"] == "true" + + +def test_cors_headers_absent_without_origin(test_client) -> None: + resp = test_client.get("/") + assert "Access-Control-Allow-Origin" not in resp.headers + + +def test_invalid_resource_id_returns_json_error(db, api_client) -> None: + # A malformed base-36 ID cannot refer to any resource and must be rejected + # before any DB query is attempted. + resp = api_client.get_raw("/threads/not-a-valid-id") + assert resp.status_code == 400 + data = json.loads(resp.data) + assert data["type"] == "invalid_request_error" + assert "message" in data + + +def test_account_response_content_type(db, api_client) -> None: + resp = api_client.post_data( + "/accounts/", + { + "type": "generic", + "email_address": "content-type-test@example.com", + "imap_server_host": "imap.example.com", + "imap_server_port": 143, + "imap_username": "user", + "imap_password": "pass", + }, + ) + assert resp.status_code == 200 + assert "application/json" in resp.content_type + + def test_create_generic_account(db, api_client) -> None: resp = api_client.post_data( "/accounts/", diff --git a/tests/conftest.py b/tests/conftest.py index 92e7dc30a..075df8a9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,7 @@ """Fixtures don't go here; see util/base.py and friends.""" -import importlib import os -import werkzeug - os.environ["NYLAS_ENV"] = "test" from pytest import fixture # noqa: PT013 @@ -21,10 +18,6 @@ def _make_api_client(db, namespace): from inbox.api.srv import app app.config["TESTING"] = True - # test_client uses werkzeug.__version__ attribute - # which has been deprecated - # To avoid a rushed flask upgrade we'll patch it here - werkzeug.__version__ = importlib.metadata.version("werkzeug") with app.test_client() as c: return TestAPIClient(c, namespace.public_id) From 62650a3bb8497587a0a8cceafc755c5679f4e052 Mon Sep 17 00:00:00 2001 From: Marcos Prieto Date: Tue, 12 May 2026 12:50:53 +0200 Subject: [PATCH 3/3] Cast to DefaultJSONProvider for mypy app.json is typed as the abstract JSONProvider but at runtime Flask always instantiates DefaultJSONProvider (which carries the sort_keys attribute) --- inbox/api/srv.py | 5 +++-- inbox/mailsync/frontend.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/inbox/api/srv.py b/inbox/api/srv.py index 86029239e..1ca04cc7e 100644 --- a/inbox/api/srv.py +++ b/inbox/api/srv.py @@ -1,6 +1,7 @@ -from typing import Any +from typing import Any, cast from flask import Flask, g, jsonify, make_response, request +from flask.json.provider import DefaultJSONProvider from flask_restful import reqparse # type: ignore[import-untyped] from sqlalchemy.orm.exc import NoResultFound # type: ignore[import-untyped] @@ -33,7 +34,7 @@ from .ns_api import app as ns_api app = Flask(__name__) -app.json.sort_keys = False +cast(DefaultJSONProvider, app.json).sort_keys = False # Handle both /endpoint and /endpoint/ without redirecting. # Note that we need to set this *before* registering the blueprint. app.url_map.strict_slashes = False diff --git a/inbox/mailsync/frontend.py b/inbox/mailsync/frontend.py index 1a5484b90..dcc543ecb 100644 --- a/inbox/mailsync/frontend.py +++ b/inbox/mailsync/frontend.py @@ -1,9 +1,11 @@ import random import threading import time +from typing import cast import structlog from flask import Flask, jsonify, request +from flask.json.provider import DefaultJSONProvider from flask.typing import ResponseReturnValue from pympler import muppy, summary # type: ignore[import-untyped] from werkzeug.serving import WSGIRequestHandler, run_simple @@ -33,7 +35,7 @@ def __init__(self, port, profile) -> None: # type: ignore[no-untyped-def] def _create_app(self): # type: ignore[no-untyped-def] app = Flask(__name__) - app.json.sort_keys = False + cast(DefaultJSONProvider, app.json).sort_keys = False self._create_app_impl(app) return app