diff --git a/tests/unit/test_configs.py b/tests/unit/test_configs.py index ca026d2211..5cd50bb951 100644 --- a/tests/unit/test_configs.py +++ b/tests/unit/test_configs.py @@ -1,3 +1,11 @@ +"""Tests for Dash application configuration and path handling. + +This module contains pytest test cases that verify the behavior of Dash application +configuration, environment variable handling, pathname prefixing, and URL path utilities. +The tests cover various scenarios including valid and invalid configurations, +environment variable overrides, and path manipulation functions used in Dash applications. +""" + import os import logging @@ -26,6 +34,14 @@ def test_dash_env_vars(empty_environ): + """ + Tests that the DASH_ENV_VARS dictionary contains None values by default when no + environment variables are set. This ensures that without explicit configuration, + the application does not assume any default values for these variables. + + Args: + empty_environ: Pytest fixture providing a clean environment with no pre-set variables. + """ assert {None} == { val for _, val in DASH_ENV_VARS.items() }, "initial var values are None without extra OS environ setting" @@ -43,6 +59,18 @@ def test_dash_env_vars(empty_environ): def test_valid_pathname_prefix_init( empty_environ, route_prefix, req_prefix, expected_route, expected_req ): + """ + Tests the pathname_configs function with valid combinations of route and request + pathname prefixes. Verifies that the function correctly handles default values + and returns the expected routes and requests pathname prefixes. + + Args: + empty_environ: Pytest fixture providing a clean environment. + route_prefix: The routes pathname prefix to test (e.g., "/dash/"). + req_prefix: The requests pathname prefix to test (e.g., "/my-dash-app/"). + expected_route: The expected routes pathname prefix result. + expected_req: The expected requests pathname prefix result. + """ _, routes, req = pathname_configs( routes_pathname_prefix=route_prefix, requests_pathname_prefix=req_prefix ) @@ -53,6 +81,14 @@ def test_valid_pathname_prefix_init( def test_invalid_pathname_prefix(empty_environ): + """ + Tests that the pathname_configs function raises InvalidConfig exceptions for + invalid configurations, such as mismatched url_base_pathname and requests_pathname_prefix, + or prefixes that do not start or end with a '/'. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ with pytest.raises(_exc.InvalidConfig, match="url_base_pathname"): _, _, _ = pathname_configs("/my-path", "/another-path") @@ -76,6 +112,13 @@ def test_invalid_pathname_prefix(empty_environ): def test_pathname_prefix_from_environ_app_name(empty_environ): + """ + Tests that when the DASH_APP_NAME environment variable is set, the requests_pathname_prefix + is derived from it, and the routes_pathname_prefix defaults to '/'. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ os.environ["DASH_APP_NAME"] = "my-dash-app" _, routes, req = pathname_configs() assert req == "/my-dash-app/" @@ -83,12 +126,26 @@ def test_pathname_prefix_from_environ_app_name(empty_environ): def test_pathname_prefix_environ_routes(empty_environ): + """ + Tests that the routes_pathname_prefix is correctly set from the DASH_ROUTES_PATHNAME_PREFIX + environment variable when provided. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ os.environ["DASH_ROUTES_PATHNAME_PREFIX"] = "/routes/" _, routes, _ = pathname_configs() assert routes == "/routes/" def test_pathname_prefix_environ_requests(empty_environ): + """ + Tests that the requests_pathname_prefix is correctly set from the DASH_REQUESTS_PATHNAME_PREFIX + environment variable when provided. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ os.environ["DASH_REQUESTS_PATHNAME_PREFIX"] = "/requests/" _, _, req = pathname_configs() assert req == "/requests/" @@ -103,6 +160,15 @@ def test_pathname_prefix_environ_requests(empty_environ): ], ) def test_pathname_prefix_assets(empty_environ, req, expected): + """ + Tests the app_get_asset_url function to ensure it correctly generates asset URLs + by prepending the request pathname prefix to the asset path. + + Args: + empty_environ: Pytest fixture providing a clean environment. + req: The request pathname prefix to test (e.g., "/requests/"). + expected: The expected asset URL (e.g., "/requests/assets/reset.css"). + """ config = AttributeDict(assets_external_path=req, assets_url_path="assets") path = app_get_asset_url(config, "reset.css") assert path == expected @@ -131,6 +197,18 @@ def test_asset_url( assets_url_path, expected, ): + """ + Tests the get_asset_url function with various combinations of requests_pathname_prefix, + assets_external_path, and assets_url_path to ensure the correct asset URL is generated. + This includes scenarios with and without external asset paths. + + Args: + empty_environ: Pytest fixture providing a clean environment. + requests_pathname_prefix: The request pathname prefix (e.g., "/app/"). + assets_external_path: External path for assets (e.g., "http://external.com/"). + assets_url_path: The asset URL path (e.g., "css"). + expected: The expected asset URL result. + """ app = Dash( "Dash", requests_pathname_prefix=requests_pathname_prefix, @@ -155,6 +233,15 @@ def test_get_relative_path( requests_pathname_prefix, expected, ): + """ + Tests the get_relative_path function to ensure it correctly prepends the requests_pathname_prefix + to the given path, which is crucial for generating correct URLs in Dash applications. + + Args: + empty_environ: Pytest fixture providing a clean environment. + requests_pathname_prefix: The request pathname prefix (e.g., "/app/"). + expected: The expected relative path result (e.g., "/app/page2"). + """ app = Dash( "Dash", requests_pathname_prefix=requests_pathname_prefix, @@ -176,6 +263,15 @@ def test_strip_relative_path( requests_pathname_prefix, expected, ): + """ + Tests the strip_relative_path function to ensure it correctly removes the requests_pathname_prefix + from the given path, which is useful for internal path handling in Dash applications. + + Args: + empty_environ: Pytest fixture providing a clean environment. + requests_pathname_prefix: The request pathname prefix (e.g., "/app/"). + expected: The expected stripped path result (e.g., "/page2"). + """ app = Dash( "Dash", requests_pathname_prefix=requests_pathname_prefix, @@ -186,6 +282,14 @@ def test_strip_relative_path( def test_get_combined_config_dev_tools_ui(empty_environ): + """ + Tests the get_combined_config function for the 'ui' configuration, verifying the priority + order: init value > environment variable > default value. This ensures the correct configuration + value is used based on the provided inputs and environment settings. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ val1 = get_combined_config("ui", None, default=False) assert ( not val1 @@ -200,6 +304,14 @@ def test_get_combined_config_dev_tools_ui(empty_environ): def test_get_combined_config_props_check(empty_environ): + """ + Tests the get_combined_config function for the 'props_check' configuration, verifying the + priority order: init value > environment variable > default value. This ensures the correct + configuration value is used for property checking in Dash components. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ val1 = get_combined_config("props_check", None, default=False) assert ( not val1 @@ -214,6 +326,13 @@ def test_get_combined_config_props_check(empty_environ): def test_load_dash_env_vars_refects_to_os_environ(empty_environ): + """ + Tests the load_dash_env_vars function to ensure it accurately reflects changes in the OS + environment variables, updating the configuration values accordingly. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ for var in DASH_ENV_VARS.keys(): os.environ[var] = "true" vars = load_dash_env_vars() @@ -235,6 +354,17 @@ def test_load_dash_env_vars_refects_to_os_environ(empty_environ): ], ) def test_app_name_server(empty_environ, name, server, expected): + """ + Tests the Dash application name assignment logic, verifying that the name is correctly set + based on the provided name parameter and server configuration. This includes scenarios where + the name is explicitly provided, inferred from the server, or defaults to '__main__'. + + Args: + empty_environ: Pytest fixture providing a clean environment. + name: The explicit name to set for the app, if provided. + server: The server configuration (e.g., True, False, or a Flask instance). + expected: The expected application name result. + """ app = Dash(name=name, server=server) assert app.config.name == expected @@ -255,6 +385,15 @@ def test_app_name_server(empty_environ, name, server, expected): ], ) def test_pathname_prefix_relative_url(prefix, partial_path, expected): + """ + Tests the app_get_relative_path function to ensure it correctly constructs relative URLs + by combining the pathname prefix with the partial path, which is essential for routing in Dash. + + Args: + prefix: The pathname prefix to prepend (e.g., "/my-dash-app/"). + partial_path: The partial path to append (e.g., "/page-1"). + expected: The expected full relative URL result. + """ path = app_get_relative_path(prefix, partial_path) assert path == expected @@ -264,6 +403,14 @@ def test_pathname_prefix_relative_url(prefix, partial_path, expected): [("/", "relative-page-1"), ("/my-dash-app/", "relative-page-1")], ) def test_invalid_get_relative_path(prefix, partial_path): + """ + Tests that the app_get_relative_path function raises an UnsupportedRelativePath exception + when provided with a partial path that does not start with a '/', ensuring proper path validation. + + Args: + prefix: The pathname prefix to test. + partial_path: The invalid partial path to test (e.g., "relative-page-1"). + """ with pytest.raises(_exc.UnsupportedRelativePath): app_get_relative_path(prefix, partial_path) @@ -293,6 +440,15 @@ def test_invalid_get_relative_path(prefix, partial_path): ], ) def test_strip_relative_path(prefix, partial_path, expected): + """ + Tests the app_strip_relative_path function to ensure it correctly removes the pathname prefix + from the full path, returning the relative path component used internally by the application. + + Args: + prefix: The pathname prefix to strip (e.g., "/my-dash-app/"). + partial_path: The full path to strip (e.g., "/my-dash-app/page-1"). + expected: The expected stripped path result (e.g., "page-1"). + """ path = app_strip_relative_path(prefix, partial_path) assert path == expected @@ -306,11 +462,26 @@ def test_strip_relative_path(prefix, partial_path, expected): ], ) def test_invalid_strip_relative_path(prefix, partial_path): + """ + Tests that the app_strip_relative_path function raises an UnsupportedRelativePath exception + when the full path does not start with the expected pathname prefix, ensuring correct path handling. + + Args: + prefix: The pathname prefix to test. + partial_path: The invalid full path to test (e.g., "relative-page-1"). + """ with pytest.raises(_exc.UnsupportedRelativePath): app_strip_relative_path(prefix, partial_path) def test_port_env_fail_str(empty_environ): + """ + Tests that providing a non-integer port value (e.g., 'garbage') to the app.run method + raises a ValueError with a specific message, ensuring proper input validation. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ app = Dash() with pytest.raises(Exception) as excinfo: app.run(port="garbage") @@ -321,6 +492,13 @@ def test_port_env_fail_str(empty_environ): def test_port_env_fail_range(empty_environ): + """ + Tests that providing port values outside the valid range (1 to 65535) to the app.run method + raises an AssertionError with a specific message, ensuring ports are within the acceptable range. + + Args: + empty_environ: Pytest fixture providing a clean environment. + """ app = Dash() with pytest.raises(Exception) as excinfo: app.run(port="0") @@ -342,14 +520,22 @@ def test_port_env_fail_range(empty_environ): [False, True], ) def test_no_proxy_success(mocker, caplog, empty_environ, setlevel_warning): + """ + Tests that the Dash application can run successfully without a proxy configuration, + verifying the correct startup message is logged unless the logging level is set to WARNING. + + Args: + mocker: Pytest fixture for mocking objects. + caplog: Pytest fixture for capturing log output. + empty_environ: Pytest fixture providing a clean environment. + setlevel_warning: Boolean indicating whether to set the logging level to WARNING. + """ app = Dash() if setlevel_warning: app.logger.setLevel(logging.WARNING) - # mock out the run method so we don't actually start listening forever mocker.patch.object(app.server, "run") - app.run(port=8787) STARTUP_MESSAGE = "Dash is running on http://127.0.0.1:8787/\n" @@ -370,6 +556,19 @@ def test_no_proxy_success(mocker, caplog, empty_environ, setlevel_warning): ], ) def test_proxy_success(mocker, caplog, empty_environ, proxy, host, port, path): + """ + Tests that the Dash application can run successfully with various proxy configurations, + ensuring the correct startup message reflecting the proxy URL and path is logged. + + Args: + mocker: Pytest fixture for mocking objects. + caplog: Pytest fixture for capturing log output. + empty_environ: Pytest fixture providing a clean environment. + proxy: The proxy URL to test (e.g., "https://daash.plot.ly"). + host: The host address (e.g., "127.0.0.1"). + port: The port number (e.g., 8050). + path: The URL path (e.g., "/a/b/c/"). + """ proxystr = "http://{}:{}::{}".format(host, port, proxy) app = Dash(url_base_pathname=path) mocker.patch.object(app.server, "run") @@ -380,10 +579,15 @@ def test_proxy_success(mocker, caplog, empty_environ, proxy, host, port, path): def test_proxy_failure(mocker, empty_environ): + """ + Tests that invalid proxy configurations, such as mismatched protocols, hosts, or ports, + raise ProxyError exceptions with appropriate error messages. + + Args: + mocker: Pytest fixture for mocking objects. + empty_environ: Pytest fixture providing a clean environment. + """ app = Dash() - - # if the tests work we'll never get to server.run, but keep the mock - # in case something is amiss and we don't get an exception. mocker.patch.object(app.server, "run") with pytest.raises(_exc.ProxyError) as excinfo: @@ -407,6 +611,13 @@ def test_proxy_failure(mocker, empty_environ): def test_title(): + """ + Tests that the Dash application's title can be set via the constructor or property assignment, + and that it is correctly rendered in the HTML index page. + + Args: + None + """ app = Dash() assert "