diff --git a/changelog/+pytest-httpx.housekeeping.md b/changelog/+pytest-httpx.housekeeping.md new file mode 100644 index 00000000..c53aabc5 --- /dev/null +++ b/changelog/+pytest-httpx.housekeeping.md @@ -0,0 +1 @@ +Update Pytest-httpx and set all responses as reusable \ No newline at end of file diff --git a/changelog/+pytest.housekeeping.md b/changelog/+pytest.housekeeping.md new file mode 100644 index 00000000..51076069 --- /dev/null +++ b/changelog/+pytest.housekeeping.md @@ -0,0 +1 @@ +Add a fixture to always reset some environment variables before running tests \ No newline at end of file diff --git a/infrahub_sdk/yaml.py b/infrahub_sdk/yaml.py index 69d973cf..a0cd159d 100644 --- a/infrahub_sdk/yaml.py +++ b/infrahub_sdk/yaml.py @@ -25,7 +25,7 @@ class InfrahubFileData(BaseModel): api_version: InfrahubFileApiVersion = Field(InfrahubFileApiVersion.V1, alias="apiVersion") kind: InfrahubFileKind spec: dict - metadata: dict | None = Field(default_factory=dict) + metadata: dict = Field(default_factory=dict) class LocalFile(BaseModel): diff --git a/poetry.lock b/poetry.lock index 9938fd39..f8ca5a2e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -572,14 +572,14 @@ trio = ["trio (>=0.22.0,<0.26.0)"] [[package]] name = "httpx" -version = "0.27.2" +version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, - {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, ] [package.dependencies] @@ -587,7 +587,6 @@ anyio = "*" certifi = "*" httpcore = "==1.*" idna = "*" -sniffio = "*" [package.extras] brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] @@ -983,6 +982,7 @@ version = "1.12.0" description = "Common helper functions useful in network automation." optional = false python-versions = "<4.0,>=3.8" +groups = ["main"] files = [ {file = "netutils-1.12.0-py3-none-any.whl", hash = "sha256:7cb37796ce86637814f8c899f64db2b054986b0eda719d3fcadc293d451a4db1"}, {file = "netutils-1.12.0.tar.gz", hash = "sha256:96a790d11921063a6a64ee79c6e8c5a5ffcd05cbee07dd2b614d98c4416cffdd"}, @@ -1511,22 +1511,22 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "pytest-httpx" -version = "0.30.0" +version = "0.35.0" description = "Send responses to httpx." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-httpx-0.30.0.tar.gz", hash = "sha256:755b8edca87c974dd4f3605c374fda11db84631de3d163b99c0df5807023a19a"}, - {file = "pytest_httpx-0.30.0-py3-none-any.whl", hash = "sha256:6d47849691faf11d2532565d0c8e0e02b9f4ee730da31687feae315581d7520c"}, + {file = "pytest_httpx-0.35.0-py3-none-any.whl", hash = "sha256:ee11a00ffcea94a5cbff47af2114d34c5b231c326902458deed73f9c459fd744"}, + {file = "pytest_httpx-0.35.0.tar.gz", hash = "sha256:d619ad5d2e67734abfbb224c3d9025d64795d4b8711116b1a13f72a251ae511f"}, ] [package.dependencies] -httpx = "==0.27.*" -pytest = ">=7,<9" +httpx = "==0.28.*" +pytest = "==8.*" [package.extras] -testing = ["pytest-asyncio (==0.23.*)", "pytest-cov (==4.*)"] +testing = ["pytest-asyncio (==0.24.*)", "pytest-cov (==6.*)"] [[package]] name = "pytest-xdist" diff --git a/tests/conftest.py b/tests/conftest.py index 841eb805..a78523b5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import asyncio +import os import pytest @@ -6,6 +7,8 @@ pytest_plugins = ["pytester"] +ENV_VARS_TO_CLEAN = ["INFRAHUB_ADDRESS", "INFRAHUB_TOKEN", "INFRAHUB_BRANCH", "INFRAHUB_USERNAME", "INFRAHUB_PASSWORD"] + @pytest.fixture(scope="session") def event_loop(): @@ -20,3 +23,19 @@ def event_loop(): def execute_before_any_test(): config.SETTINGS.load_and_exit() config.SETTINGS.active.server_address = "http://mock" + + +@pytest.fixture(scope="session", autouse=True) +def clean_env_vars(): + """Cleans the environment variables before any test is run.""" + original_values = {} + for name in ENV_VARS_TO_CLEAN: + original_values[name] = os.environ.get(name) + if original_values[name] is not None: + del os.environ[name] + + yield + + for name in ENV_VARS_TO_CLEAN: + if original_values[name] is not None: + os.environ[name] = original_values[name] diff --git a/tests/unit/sdk/checks/conftest.py b/tests/unit/sdk/checks/conftest.py index 8aada87d..4fa6d1cc 100644 --- a/tests/unit/sdk/checks/conftest.py +++ b/tests/unit/sdk/checks/conftest.py @@ -17,5 +17,6 @@ async def mock_gql_query_my_query(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=response, url="http://localhost:8000/api/query/my_query?branch=main&update_group=false", + is_reusable=True, ) return httpx_mock diff --git a/tests/unit/sdk/conftest.py b/tests/unit/sdk/conftest.py index afb9250c..3b82e43c 100644 --- a/tests/unit/sdk/conftest.py +++ b/tests/unit/sdk/conftest.py @@ -1467,6 +1467,7 @@ async def mock_branches_list_query(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-branch-all"}, + is_reusable=True, ) return httpx_mock @@ -1500,8 +1501,8 @@ async def mock_repositories_query_no_pagination(httpx_mock: HTTPXMock) -> HTTPXM } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1) - httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1, is_reusable=True) + httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2, is_reusable=True) return httpx_mock @@ -1534,6 +1535,7 @@ async def mock_query_repository_all_01_no_pagination( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-repository-all"}, + is_reusable=True, ) return httpx_mock @@ -1599,8 +1601,8 @@ async def mock_repositories_query(httpx_mock: HTTPXMock) -> HTTPXMock: } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1) - httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response1, is_reusable=True) + httpx_mock.add_response(method="POST", url="http://mock/graphql/cr1234", json=response2, is_reusable=True) return httpx_mock @@ -1640,6 +1642,7 @@ async def mock_query_repository_page1_1( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) return httpx_mock @@ -1674,13 +1677,14 @@ async def mock_query_corenode_page1_1(httpx_mock: HTTPXMock, client: InfrahubCli method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corenode-page1"}, + is_reusable=True, ) return httpx_mock @pytest.fixture async def mock_query_repository_count(httpx_mock: HTTPXMock, client: InfrahubClient, mock_schema_query_01) -> HTTPXMock: - httpx_mock.add_response(method="POST", json={"data": {"CoreRepository": {"count": 5}}}) + httpx_mock.add_response(method="POST", json={"data": {"CoreRepository": {"count": 5}}}, is_reusable=True) return httpx_mock @@ -1694,6 +1698,7 @@ async def mock_query_repository_page1_empty( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) return httpx_mock @@ -1743,6 +1748,7 @@ async def mock_query_repository_page1_2( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) return httpx_mock @@ -1783,6 +1789,7 @@ async def mock_query_repository_page2_2( method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page2"}, + is_reusable=True, ) return httpx_mock @@ -1795,6 +1802,7 @@ async def mock_schema_query_01(httpx_mock: HTTPXMock) -> HTTPXMock: method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(response_text), + is_reusable=True, ) return httpx_mock @@ -1803,14 +1811,17 @@ async def mock_schema_query_01(httpx_mock: HTTPXMock) -> HTTPXMock: async def mock_schema_query_02(httpx_mock: HTTPXMock) -> HTTPXMock: response_text = (get_fixtures_dir() / "schema_02.json").read_text(encoding="UTF-8") httpx_mock.add_response( - method="GET", url=re.compile(r"^http://mock/api/schema\?branch=(main|cr1234)"), json=ujson.loads(response_text) + method="GET", + url=re.compile(r"^http://mock/api/schema\?branch=(main|cr1234)"), + json=ujson.loads(response_text), + is_reusable=True, ) return httpx_mock @pytest.fixture async def mock_rest_api_artifact_definition_generate(httpx_mock: HTTPXMock) -> HTTPXMock: - httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*")) + httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*"), is_reusable=True) return httpx_mock @@ -1822,6 +1833,7 @@ async def mock_rest_api_artifact_fetch(httpx_mock: HTTPXMock) -> HTTPXMock: method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(schema_response), + is_reusable=True, ) graphql_response = { @@ -1902,7 +1914,9 @@ async def mock_rest_api_artifact_fetch(httpx_mock: HTTPXMock) -> HTTPXMock: ip name-server 1.1.1.1 """ - httpx_mock.add_response(method="GET", url=re.compile(r"^http://mock/api/storage/object/.*"), text=artifact_content) + httpx_mock.add_response( + method="GET", url=re.compile(r"^http://mock/api/storage/object/.*"), text=artifact_content, is_reusable=True + ) return httpx_mock @@ -1914,6 +1928,7 @@ async def mock_rest_api_artifact_generate(httpx_mock: HTTPXMock) -> HTTPXMock: method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(schema_response), + is_reusable=True, ) artifact_graphql_response = { @@ -1987,7 +2002,9 @@ async def mock_rest_api_artifact_generate(httpx_mock: HTTPXMock) -> HTTPXMock: }, } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=artifact_graphql_response) + httpx_mock.add_response( + method="POST", url="http://mock/graphql/main", json=artifact_graphql_response, is_reusable=True + ) artifact_definition_graphql_response = { "data": { @@ -2090,32 +2107,34 @@ async def mock_rest_api_artifact_generate(httpx_mock: HTTPXMock) -> HTTPXMock: } } } - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=artifact_definition_graphql_response) - httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*")) + httpx_mock.add_response( + method="POST", url="http://mock/graphql/main", json=artifact_definition_graphql_response, is_reusable=True + ) + httpx_mock.add_response(method="POST", url=re.compile(r"^http://mock/api/artifact/generate/.*"), is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_dropdown_add(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaDropdownAdd": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_dropdown_remove(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaDropdownRemove": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_enum_add(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaEnumAdd": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture async def mock_query_mutation_schema_enum_remove(httpx_mock: HTTPXMock) -> None: response = {"data": {"SchemaEnumRemove": {"ok": True}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) @pytest.fixture @@ -2134,21 +2153,21 @@ async def mock_query_mutation_location_create_failed(httpx_mock: HTTPXMock) -> H ], } url_regex = re.compile(r"http://mock/graphql/main") - httpx_mock.add_response(method="POST", url=url_regex, json=response1) - httpx_mock.add_response(method="POST", url=url_regex, json=response2) + httpx_mock.add_response(method="POST", url=url_regex, json=response1, is_reusable=True) + httpx_mock.add_response(method="POST", url=url_regex, json=response2, is_reusable=True) return httpx_mock @pytest.fixture async def mock_query_infrahub_version(httpx_mock: HTTPXMock) -> HTTPXMock: - httpx_mock.add_response(method="POST", json={"data": {"InfrahubInfo": {"version": "1.1.0"}}}) + httpx_mock.add_response(method="POST", json={"data": {"InfrahubInfo": {"version": "1.1.0"}}}, is_reusable=True) return httpx_mock @pytest.fixture async def mock_query_infrahub_user(httpx_mock: HTTPXMock) -> HTTPXMock: response_text = (get_fixtures_dir() / "account_profile.json").read_text(encoding="UTF-8") - httpx_mock.add_response(method="POST", json=ujson.loads(response_text)) + httpx_mock.add_response(method="POST", json=ujson.loads(response_text), is_reusable=True) return httpx_mock @@ -2483,7 +2502,9 @@ def query_introspection() -> str: async def mock_schema_query_ipam(httpx_mock: HTTPXMock) -> HTTPXMock: response_text = (get_fixtures_dir() / "schema_ipam.json").read_text(encoding="UTF-8") - httpx_mock.add_response(method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(response_text)) + httpx_mock.add_response( + method="GET", url="http://mock/api/schema?branch=main", json=ujson.loads(response_text), is_reusable=True + ) return httpx_mock @@ -2492,7 +2513,7 @@ async def mock_query_location_batch_count( httpx_mock: HTTPXMock, client: InfrahubClient, mock_schema_query_01 ) -> HTTPXMock: response = {"data": {"BuiltinLocation": {"count": 30}}} - httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response) + httpx_mock.add_response(method="POST", url="http://mock/graphql/main", json=response, is_reusable=True) return httpx_mock @@ -2505,6 +2526,7 @@ async def mock_query_location_batch(httpx_mock: HTTPXMock, client: InfrahubClien method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": f"query-builtinlocation-page{i}"}, + is_reusable=True, ) return httpx_mock @@ -2518,6 +2540,7 @@ async def mock_query_tasks_01(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": f"query-tasks-page{i}"}, + is_reusable=True, ) return httpx_mock @@ -2530,6 +2553,7 @@ async def mock_query_tasks_02_main(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock @@ -2542,6 +2566,7 @@ async def mock_query_tasks_empty(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock @@ -2554,6 +2579,7 @@ async def mock_query_tasks_03(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock @@ -2567,6 +2593,7 @@ async def mock_query_tasks_04_full(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": f"query-tasks-page{i}"}, + is_reusable=True, ) return httpx_mock @@ -2579,5 +2606,6 @@ async def mock_query_tasks_05(httpx_mock: HTTPXMock) -> HTTPXMock: method="POST", json=ujson.loads(response_text), match_headers={"X-Infrahub-Tracker": "query-tasks-page1"}, + is_reusable=True, ) return httpx_mock diff --git a/tests/unit/sdk/test_client.py b/tests/unit/sdk/test_client.py index f7bad76f..ab3f1bf3 100644 --- a/tests/unit/sdk/test_client.py +++ b/tests/unit/sdk/test_client.py @@ -243,6 +243,7 @@ async def test_method_get_by_id(httpx_mock: HTTPXMock, clients, mock_schema_quer method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -291,6 +292,7 @@ async def test_method_get_by_hfid(httpx_mock: HTTPXMock, clients, mock_schema_qu method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -338,6 +340,7 @@ async def test_method_get_by_default_filter(httpx_mock: HTTPXMock, clients, mock method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -382,6 +385,7 @@ async def test_method_get_by_name(httpx_mock: HTTPXMock, clients, mock_schema_qu method="POST", json=response, match_headers={"X-Infrahub-Tracker": "query-corerepository-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -524,6 +528,7 @@ async def test_allocate_next_ip_address( } }, match_headers={"X-Infrahub-Tracker": "allocate-ip-loopback"}, + is_reusable=True, ) httpx_mock.add_response( method="POST", @@ -545,6 +550,7 @@ async def test_allocate_next_ip_address( } }, match_headers={"X-Infrahub-Tracker": "query-ipamipaddress-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -623,6 +629,7 @@ async def test_allocate_next_ip_prefix( } }, match_headers={"X-Infrahub-Tracker": "allocate-ip-interco"}, + is_reusable=True, ) httpx_mock.add_response( method="POST", @@ -644,6 +651,7 @@ async def test_allocate_next_ip_prefix( } }, match_headers={"X-Infrahub-Tracker": "query-ipamipprefix-page1"}, + is_reusable=True, ) if client_type == "standard": @@ -723,6 +731,7 @@ async def test_query_echo(httpx_mock: HTTPXMock, echo_clients, client_type): httpx_mock.add_response( method="POST", json={"data": {"BuiltinTag": {"edges": []}}}, + is_reusable=True, ) query = """