11from __future__ import annotations
22
3+ import typing as t
4+ from unittest .mock import MagicMock
5+ from unittest .mock import patch
6+
7+ import pytest
38from flask import Flask
9+ from flask import Response
410from flask .testing import FlaskClient
511
12+ from flask_debugtoolbar import DebugToolbarExtension
13+
614
715def load_app (name : str ) -> FlaskClient :
816 app : Flask = __import__ (name ).app
@@ -15,3 +23,145 @@ def test_basic_app() -> None:
1523 index = app .get ("/" )
1624 assert index .status_code == 200
1725 assert b'<div id="flDebug"' in index .data
26+
27+
28+ def app_with_config (
29+ app_config : dict [str , t .Any ], toolbar_config : dict [str , t .Any ]
30+ ) -> Flask :
31+ app = Flask (__name__ , ** app_config )
32+ app .config ["DEBUG" ] = True
33+ app .config ["SECRET_KEY" ] = "abc123"
34+
35+ for key , value in toolbar_config .items ():
36+ app .config [key ] = value
37+
38+ DebugToolbarExtension (app )
39+
40+ return app
41+
42+
43+ def test_toolbar_is_host_matching_but_flask_is_not () -> None :
44+ with pytest .raises (ValueError ) as e :
45+ app_with_config (
46+ app_config = dict (host_matching = False ),
47+ toolbar_config = dict (
48+ DEBUG_TB_ENABLED = True , DEBUG_TB_ROUTES_HOST = "myapp.com"
49+ ),
50+ )
51+
52+ assert str (e .value ) == (
53+ "`DEBUG_TB_ROUTES_HOST` should only be set if your Flask app is "
54+ "using `host_matching`."
55+ )
56+
57+
58+ def test_flask_is_host_matching_but_toolbar_is_not () -> None :
59+ with pytest .warns (UserWarning ) as record :
60+ app_with_config (
61+ app_config = dict (host_matching = True , static_host = "static.com" ),
62+ toolbar_config = dict (DEBUG_TB_ENABLED = True ),
63+ )
64+
65+ assert isinstance (record [0 ].message , UserWarning )
66+ assert record [0 ].message .args [0 ] == (
67+ "Flask-DebugToolbar requires DEBUG_TB_ROUTES_HOST to be set if Flask "
68+ "is running in `host_matching` mode. Static assets for the toolbar "
69+ "will not be served correctly unless this is set."
70+ )
71+
72+
73+ def test_toolbar_host_variables_rejected () -> None :
74+ with pytest .raises (ValueError ) as e :
75+ app_with_config (
76+ app_config = dict (host_matching = True , static_host = "static.com" ),
77+ toolbar_config = dict (
78+ DEBUG_TB_ENABLED = True , DEBUG_TB_ROUTES_HOST = "<host>.com"
79+ ),
80+ )
81+
82+ assert str (e .value ) == (
83+ "`DEBUG_TB_ROUTES_HOST` must either be a host name with no "
84+ "variables, to serve all Flask-DebugToolbar assets from a single "
85+ "host, or `*` to match the current request's host."
86+ )
87+
88+
89+ def test_toolbar_in_host_mode_injects_toolbar_html () -> None :
90+ app = app_with_config (
91+ app_config = dict (host_matching = True , static_host = "static.com" ),
92+ toolbar_config = dict (DEBUG_TB_ENABLED = True , DEBUG_TB_ROUTES_HOST = "myapp.com" ),
93+ )
94+
95+ @app .route ("/" , host = "myapp.com" )
96+ def index () -> str :
97+ return "<html><head></head><body>OK</body></html>"
98+
99+ with app .test_client () as client :
100+ with app .app_context ():
101+ response = client .get ("/" , headers = {"Host" : "myapp.com" })
102+ assert '<div id="flDebug" ' in response .text
103+
104+
105+ @pytest .mark .parametrize (
106+ "tb_routes_host, request_host, expected_static_path" ,
107+ (
108+ ("myapp.com" , "myapp.com" , "/_debug_toolbar/static/" ),
109+ ("toolbar.com" , "myapp.com" , "http://toolbar.com/_debug_toolbar/static/" ),
110+ ("*" , "myapp.com" , "/_debug_toolbar/static/" ),
111+ ),
112+ )
113+ def test_toolbar_injects_expected_static_path_for_host (
114+ tb_routes_host : str , request_host : str , expected_static_path : str
115+ ) -> None :
116+ app = app_with_config (
117+ app_config = dict (host_matching = True , static_host = "static.com" ),
118+ toolbar_config = dict (DEBUG_TB_ENABLED = True , DEBUG_TB_ROUTES_HOST = tb_routes_host ),
119+ )
120+
121+ @app .route ("/" , host = request_host )
122+ def index () -> str :
123+ return "<html><head></head><body>OK</body></html>"
124+
125+ with app .test_client () as client :
126+ with app .app_context ():
127+ response = client .get ("/" , headers = {"Host" : request_host })
128+
129+ assert (
130+ """<script type="text/javascript">"""
131+ f"""var DEBUG_TOOLBAR_STATIC_PATH = '{ expected_static_path } '"""
132+ """</script>"""
133+ ) in response .text
134+
135+
136+ @patch (
137+ "flask.helpers.werkzeug.utils.send_from_directory" ,
138+ return_value = Response (b"some-file" , mimetype = "text/css" , status = 200 ),
139+ )
140+ @pytest .mark .parametrize (
141+ "tb_routes_host, request_host, expected_status_code" ,
142+ (
143+ ("toolbar.com" , "toolbar.com" , 200 ),
144+ ("toolbar.com" , "myapp.com" , 404 ),
145+ ("toolbar.com" , "static.com" , 404 ),
146+ ("*" , "toolbar.com" , 200 ),
147+ ("*" , "myapp.com" , 200 ),
148+ ("*" , "static.com" , 200 ),
149+ ),
150+ )
151+ def test_toolbar_serves_assets_based_on_host_configuration (
152+ mock_send_from_directory : MagicMock ,
153+ tb_routes_host : str ,
154+ request_host : str ,
155+ expected_status_code : int ,
156+ ) -> None :
157+ app = app_with_config (
158+ app_config = dict (host_matching = True , static_host = "static.com" ),
159+ toolbar_config = dict (DEBUG_TB_ENABLED = True , DEBUG_TB_ROUTES_HOST = tb_routes_host ),
160+ )
161+
162+ with app .test_client () as client :
163+ with app .app_context ():
164+ response = client .get (
165+ "/_debug_toolbar/static/js/toolbar.js" , headers = {"Host" : request_host }
166+ )
167+ assert response .status_code == expected_status_code
0 commit comments