From e83fed4b463d7795fe0ca4973532ce5e135ba818 Mon Sep 17 00:00:00 2001 From: Ferenc Bakonyi Date: Sat, 20 Dec 2025 17:27:28 +0000 Subject: [PATCH 1/2] feature: add test infrastructure and developer testing guide (PR 1) - Add developer testing guide in docs/testing.md - Implement pytest fixtures in tests/conftest.py: - mock_session_factory for sequential response mocking - Response fixtures for login, errors, device info, hosts, xpath - Pre-configured client fixtures for all encryption methods - Add API response fixtures (6 JSON files) with realistic router responses - Add 4 example tests demonstrating key patterns: - Successful login with session state validation - Authentication error handling - XPath operations with sequential responses - Pre-configured fixture usage - Configure pytest in pyproject.toml: - Add pytest-asyncio, pytest-aiohttp, pytest-cov dependencies - Set asyncio_mode, testpaths, and markers Part of #447: Add unit and integration tests to have test coverage --- docs/testing.md | 161 ++++ poetry.lock | 310 ++++++- pyproject.toml | 19 + tests/conftest.py | 175 ++++ tests/fixtures/device_info.json | 993 ++++++++++++++++++++++ tests/fixtures/hosts.json | 124 +++ tests/fixtures/login_auth_error.json | 32 + tests/fixtures/login_invalid_session.json | 9 + tests/fixtures/login_success.json | 35 + tests/fixtures/xpath_value.json | 76 ++ tests/unit/test_client_basic.py | 133 +++ 11 files changed, 2055 insertions(+), 12 deletions(-) create mode 100644 docs/testing.md create mode 100644 tests/conftest.py create mode 100644 tests/fixtures/device_info.json create mode 100644 tests/fixtures/hosts.json create mode 100644 tests/fixtures/login_auth_error.json create mode 100644 tests/fixtures/login_invalid_session.json create mode 100644 tests/fixtures/login_success.json create mode 100644 tests/fixtures/xpath_value.json create mode 100644 tests/unit/test_client_basic.py diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..2682bbe --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,161 @@ +# Testing Guide for Developers + +This guide covers testing strategies, patterns, and workflows for the Sagemcom API. + +## Overview + +The test suite uses `pytest` with async support to validate the client's behavior against mocked Sagemcom router API responses. Tests are split into: + +- **Unit tests** (`tests/unit/`) - Mock-based tests for individual methods, error handling, and encryption +- **Integration tests** (`tests/integration/`) - Tests against real or comprehensive simulated router APIs (requires device access) +- **Fixtures** (`tests/fixtures/`) - Sample API response payloads from different router models + +## Running Tests (from Dev Container) + +```bash +# Run all tests +poetry run pytest + +# Run only unit tests +poetry run pytest tests/unit/ + +# Run with coverage report +poetry run pytest --cov=sagemcom_api + +# Run with coverage HTML report +poetry run pytest --cov=sagemcom_api --cov-report=html + +# Run specific test file +poetry run pytest tests/unit/test_client_basic.py + +# Run specific test +poetry run pytest tests/unit/test_client_basic.py::test_login_success +``` + +## Test Structure + +### Mocking Strategy + +We mock at the **`aiohttp.ClientSession.post`** level to: +- Simulate realistic HTTP interactions +- Test full request/response cycle including JSON encoding/decoding +- Validate request payload structure +- Control response status codes and payloads + +### Fixture Patterns + +All fixtures are defined in `tests/conftest.py` with **function scope** for test isolation: + +- **`mock_session_factory`** - Factory for creating mock aiohttp sessions with custom responses +- **`login_success_response`** - Mock response for successful login +- **`login_auth_error_response`** - Mock response for authentication failure +- **`mock_client_...`** - Pre-configured SagemcomClient with mocked session + +Example usage: +```python +@pytest.mark.asyncio +async def test_example(mock_session_factory, login_success_response): + mock_session = mock_session_factory([login_success_response]) + client = SagemcomClient("192.168.1.1", "admin", "password", + EncryptionMethod.MD5, session=mock_session) + # Test implementation... +``` + +### API Response Fixtures + +Realistic API responses are stored in `tests/fixtures/` as JSON files mirroring actual router responses: + +- `login_success.json` - Successful login with session_id and nonce +- `login_auth_error.json` - Authentication failure (XMO_AUTHENTICATION_ERR) +- `device_info.json` - Device information response +- `hosts.json` - Connected devices list +- `xpath_value.json` - Generic XPath query response + +These fixtures preserve the actual JSON-RPC structure from Sagemcom routers: +```json +{ + "reply": { + "error": {"description": "XMO_REQUEST_NO_ERR"}, + "actions": [{ + "callbacks": [{ + "parameters": {"id": 12345, "nonce": "abcdef123456"} + }] + }] + } +} +``` + +## Testing Patterns + +### 1. Testing Authentication + +All three encryption methods (MD5, SHA512, MD5_NONCE) must be tested. See `test_client_basic.py` for examples: + +```python +@pytest.mark.asyncio +async def test_login_success(mock_session_factory, login_success_response): + """Test successful login flow.""" + # Demonstrates mocking login with session_id/nonce exchange +``` + +### 2. Testing Error Handling + +Each `XMO_*_ERR` constant should have corresponding test cases: + +```python +@pytest.mark.asyncio +async def test_authentication_error(mock_session_factory, login_auth_error_response): + """Test AuthenticationException is raised on XMO_AUTHENTICATION_ERR.""" + # Demonstrates error response mocking +``` + +### 3. Testing XPath Operations + +Validate URL encoding with safe characters preserved: + +```python +@pytest.mark.asyncio +async def test_xpath_url_encoding(mock_session_factory): + """Test XPath values are URL-encoded with /=[]' preserved.""" + # Demonstrates XPath encoding validation +``` + +### 4. Testing Sequential Responses + +Most API operations require multiple HTTP requests (login → operation). Use `mock_session_factory` with response lists: + +```python +# Two sequential responses +mock_session = mock_session_factory([login_success_response, xpath_value_response]) + +await client.login() # Consumes login_success_response (1st call) +await client.get_value_by_xpath() # Consumes xpath_value_response (2nd call) +await client.logout() # Would raise StopIteration (no 3rd response) +``` + +## Adding New Tests + +### For a new unit test: + +1. Determine what you're testing (method, error case, encryption variant) +2. Create or reuse fixture for API response in `tests/fixtures/` +3. Create test file in `tests/unit/test_.py` +4. Use `mock_session_factory` to inject responses +5. Write assertions for both success and error paths +6. Run test with coverage to verify new lines are covered + +### For a new integration test: + +1. Document router model and firmware version in test docstring +2. Create test in `tests/integration/test_.py` +3. Add conditional skip if router not available: `@pytest.mark.skipif(...)` +4. Use real credentials from environment variables, not hardcoded + +## Test Coverage + +Run coverage reports regularly: +```bash +poetry run pytest --cov=sagemcom_api --cov-report=term-missing +``` + +The `--cov-report=term-missing` shows which lines lack coverage. diff --git a/poetry.lock b/poetry.lock index 284e2d4..d75228e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6,7 +6,7 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -18,7 +18,7 @@ version = "3.13.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155"}, {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c"}, @@ -161,7 +161,7 @@ version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, @@ -192,7 +192,7 @@ version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] markers = "python_version < \"3.11\"" files = [ {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, @@ -205,7 +205,7 @@ version = "24.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, @@ -330,12 +330,239 @@ description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.10.7" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a"}, + {file = "coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87"}, + {file = "coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0"}, + {file = "coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13"}, + {file = "coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b"}, + {file = "coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59"}, + {file = "coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e"}, + {file = "coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2"}, + {file = "coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61"}, + {file = "coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14"}, + {file = "coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2"}, + {file = "coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417"}, + {file = "coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6"}, + {file = "coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb"}, + {file = "coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1"}, + {file = "coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256"}, + {file = "coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba"}, + {file = "coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d"}, + {file = "coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49"}, + {file = "coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c"}, + {file = "coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f"}, + {file = "coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698"}, + {file = "coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843"}, + {file = "coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c"}, + {file = "coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0"}, + {file = "coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999"}, + {file = "coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2"}, + {file = "coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a"}, + {file = "coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb"}, + {file = "coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520"}, + {file = "coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360"}, + {file = "coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e"}, + {file = "coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd"}, + {file = "coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2"}, + {file = "coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681"}, + {file = "coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63"}, + {file = "coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699"}, + {file = "coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0"}, + {file = "coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399"}, + {file = "coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235"}, + {file = "coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d"}, + {file = "coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3"}, + {file = "coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594"}, + {file = "coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0"}, + {file = "coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f"}, + {file = "coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431"}, + {file = "coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07"}, + {file = "coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260"}, + {file = "coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "coverage" +version = "7.13.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"}, + {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"}, + {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"}, + {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"}, + {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"}, + {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"}, + {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"}, + {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"}, + {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"}, + {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"}, + {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"}, + {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"}, + {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"}, + {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"}, + {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"}, + {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"}, + {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"}, + {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"}, + {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"}, + {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"}, + {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"}, + {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"}, + {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"}, + {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"}, + {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"}, + {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"}, + {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"}, + {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"}, + {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"}, + {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"}, + {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"}, + {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"}, + {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"}, + {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"}, + {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"}, + {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"}, + {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"}, + {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"}, + {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"}, + {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"}, + {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"}, + {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"}, + {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"}, + {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + [[package]] name = "dill" version = "0.3.8" @@ -420,7 +647,7 @@ version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, @@ -522,7 +749,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -660,7 +887,7 @@ version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, @@ -922,7 +1149,7 @@ version = "0.2.0" description = "Accelerated property cache" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, @@ -1130,6 +1357,65 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-aiohttp" +version = "1.1.0" +description = "Pytest plugin for aiohttp support" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_aiohttp-1.1.0-py3-none-any.whl", hash = "sha256:f39a11693a0dce08dd6c542d241e199dd8047a6e6596b2bcfa60d373f143456d"}, + {file = "pytest_aiohttp-1.1.0.tar.gz", hash = "sha256:147de8cb164f3fc9d7196967f109ab3c0b93ea3463ab50631e56438eab7b5adc"}, +] + +[package.dependencies] +aiohttp = ">=3.11.0b0" +pytest = ">=6.1.0" +pytest-asyncio = ">=0.17.2" + +[package.extras] +testing = ["coverage (==6.2)", "mypy (==1.12.1)"] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-cov" +version = "6.3.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749"}, + {file = "pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=6.2.5" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "pytokens" version = "0.3.0" @@ -1300,7 +1586,7 @@ version = "1.18.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "yarl-1.18.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:074fee89caab89a97e18ef5f29060ef61ba3cae6cd77673acc54bfdd3214b7b7"}, {file = "yarl-1.18.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b026cf2c32daf48d90c0c4e406815c3f8f4cfe0c6dfccb094a9add1ff6a0e41a"}, @@ -1394,4 +1680,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "71b9ab5635e3b6360df0546ddab518b09657b05d1ec0bec1ff5872247559603c" +content-hash = "17632ab51819bb50524338940ad60be9f6a1d39c54011320ef3250ee5427f42e" diff --git a/pyproject.toml b/pyproject.toml index 36ffd6c..f959ce7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,9 @@ backports-strenum = { version = "^1.3.1", python = "<3.11" } [tool.poetry.dev-dependencies] pytest = "^8.4" +pytest-asyncio = "^0.24.0" +pytest-aiohttp = "^1.0.5" +pytest-cov = "^6.0.0" pre-commit = "^4.3.0" black = "^25.11" pylint = "^3.2.7" @@ -42,3 +45,19 @@ build-backend = "poetry.core.masonry.api" profile = "black" force_sort_within_sections = true combine_as_imports = true + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "--strict-markers", + "--strict-config", + "-ra", +] +markers = [ + "asyncio: mark test as an asyncio test", + "integration: mark test as an integration test (requires router access)", +] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..143bc4f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,175 @@ +"""Shared pytest fixtures for Sagemcom API client tests.""" + +# pylint: disable=redefined-outer-name,duplicate-code + +import json +from pathlib import Path +from typing import Any, Dict, List +from unittest.mock import AsyncMock, MagicMock + +from aiohttp import ClientSession +import pytest + +from sagemcom_api.client import SagemcomClient +from sagemcom_api.enums import EncryptionMethod + +# Fixture directory path +FIXTURES_DIR = Path(__file__).parent / "fixtures" + + +def load_fixture(filename: str) -> Dict[str, Any]: + """ + Load a JSON fixture file. + + :param filename: Name of the fixture file (e.g., 'login_success.json') + :return: Parsed JSON data as dict + """ + fixture_path = FIXTURES_DIR / filename + with open(fixture_path, encoding="utf-8") as f: + return json.load(f) + + +@pytest.fixture +def login_success_response() -> Dict[str, Any]: + """Mock response for successful login.""" + return load_fixture("login_success.json") + + +@pytest.fixture +def login_auth_error_response() -> Dict[str, Any]: + """Mock response for authentication error.""" + return load_fixture("login_auth_error.json") + + +@pytest.fixture +def login_invalid_session_response() -> Dict[str, Any]: + """Mock response for invalid session error.""" + return load_fixture("login_invalid_session.json") + + +@pytest.fixture +def device_info_response() -> Dict[str, Any]: + """Mock response for device info query.""" + return load_fixture("device_info.json") + + +@pytest.fixture +def hosts_response() -> Dict[str, Any]: + """Mock response for hosts query.""" + return load_fixture("hosts.json") + + +@pytest.fixture +def xpath_value_response() -> Dict[str, Any]: + """Mock response for generic XPath getValue.""" + return load_fixture("xpath_value.json") + + +@pytest.fixture +def mock_session_factory(): + """ + Factory fixture for creating mock aiohttp ClientSession. + + Returns a factory function that creates a mock session with configurable responses. + Mock responses are consumed in sequence (first call gets first response, etc.). + + Usage: + mock_session = mock_session_factory([response1, response2]) + # First POST call returns response1, second returns response2 + + :return: Factory function that takes list of response dicts + """ + + def _create_mock_session(responses: List[Dict[str, Any]]) -> ClientSession: + """ + Create a mock ClientSession with specified responses. + + :param responses: List of response dictionaries to return in sequence + :return: Mock ClientSession + """ + mock_session = MagicMock(spec=ClientSession) + + # Create async context manager mock for session.post() + mock_post = MagicMock() + mock_session.post = mock_post + + # Create response mocks for each configured response + mock_responses = [] + for response_data in responses: + mock_response = AsyncMock() + mock_response.status = 200 + mock_response.json = AsyncMock(return_value=response_data) + mock_response.text = AsyncMock(return_value=json.dumps(response_data)) + mock_response.__aenter__ = AsyncMock(return_value=mock_response) + mock_response.__aexit__ = AsyncMock(return_value=None) + mock_responses.append(mock_response) + + # Configure post() to return responses in sequence + if len(mock_responses) == 1: + mock_post.return_value = mock_responses[0] + else: + mock_post.side_effect = mock_responses + + # Mock close method + mock_session.close = AsyncMock() + + return mock_session + + return _create_mock_session + + +@pytest.fixture +def mock_client_md5(mock_session_factory, login_success_response): + """ + Create a SagemcomClient with MD5 encryption and mocked session. + + Pre-configured with successful login response. + + :return: SagemcomClient instance + """ + mock_session = mock_session_factory([login_success_response]) + return SagemcomClient( + host="192.168.1.1", + username="admin", + password="admin", + authentication_method=EncryptionMethod.MD5, + session=mock_session, + ) + + +@pytest.fixture +def mock_client_sha512(mock_session_factory, login_success_response): + """ + Create a SagemcomClient with SHA512 encryption and mocked session. + + Pre-configured with successful login response. + + :return: SagemcomClient instance + """ + mock_session = mock_session_factory([login_success_response]) + return SagemcomClient( + host="192.168.1.1", + username="admin", + password="admin", + authentication_method=EncryptionMethod.SHA512, + session=mock_session, + ) + + +@pytest.fixture +def mock_client_md5_nonce(mock_session_factory, login_success_response): + """ + Create a SagemcomClient with MD5_NONCE encryption and mocked session. + + Pre-configured with successful login response. + + :return: SagemcomClient instance + """ + mock_session = mock_session_factory([login_success_response]) + return SagemcomClient( + host="192.168.1.1", + username="admin", + password="admin", + authentication_method=EncryptionMethod.MD5_NONCE, + session=mock_session, + ) diff --git a/tests/fixtures/device_info.json b/tests/fixtures/device_info.json new file mode 100644 index 0000000..d167f1a --- /dev/null +++ b/tests/fixtures/device_info.json @@ -0,0 +1,993 @@ +{ + "reply": { + "uid": 0, + "id": 1, + "error": { + "code": 16777216, + "description": "XMO_REQUEST_NO_ERR" + }, + "actions": [ + { + "uid": 1, + "id": 0, + "error": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "callbacks": [ + { + "uid": 1, + "result": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "xpath": "Device\/DeviceInfo", + "parameters": { + "value": { + "DeviceInfo": { + "DeviceCategory": "", + "Manufacturer": "Sagemcom", + "ManufacturerOUI": "3C585D", + "ModelName": "FAST3896_CUSTOM", + "ModelNumber": "FAST3896", + "Description": "Wireless Voice Gateway <>", + "ProductClass": "FAST3896", + "SerialNumber": "DM2229121000037", + "HardwareVersion": "4.0", + "SoftwareVersion": "FAST3896_CUSTOM_sw12.34.56.78i-9", + "AdditionalHardwareVersion": "", + "AdditionalSoftwareVersion": "", + "ExternalFirmwareVersion": "FAST3896_CUSTOM_sw12.34.56.78i-9", + "InternalFirmwareVersion": "sw12.34.56.78i-9", + "GUIFirmwareVersion": "", + "GUIAPIVersion": "1.23", + "ProvisioningCode": "", + "UpTime": 189629, + "FirstUseDate": "2024-03-03T14: 16: 22+0100", + "MACAddress": "01:23:45:67:89:AB", + "Mode": "GW", + "Country": "fr", + "RebootCount": 59, + "NodesToRestore": "", + "VendorConfigFiles": [ + { + "uid": 1, + "Alias": "DEVICE_CONFIG", + "Name": "device.cfg", + "Version": "", + "Date": "1-01-01T01: 00: 00+0100", + "Description": "Gateway device configuration", + "UseForBackupRestore": false + } + ], + "MemoryStatus": { + "Total": 231316, + "Free": 59768, + "FreeMemoryPercentage": 25 + }, + "ProcessStatus": { + "CPUUsage": 0, + "Processes": [], + "LoadAverage": { + "Load1": 0.072754, + "Load5": 0.060059, + "Load15": 0.068848 + } + }, + "TemperatureStatus": { + "TemperatureSensors": [ + { + "uid": 1, + "Alias": "mainSensor", + "Enable": false, + "Status": "DISABLED", + "Reset": false, + "ResetTime": "1-01-01T01: 00: 00+0100", + "Name": "", + "Value": 67, + "LastUpdate": "1-01-01T01: 00: 00+0100", + "MinValue": 0, + "MinTime": "1-01-01T01: 00: 00+0100", + "MaxValue": 0, + "MaxTime": "1-01-01T01: 00: 00+0100", + "LowAlarmValue": 0, + "LowAlarmTime": "1-01-01T01: 00: 00+0100", + "HighAlarmValue": 0, + "PollingInterval": 0, + "HighAlarmTime": "1-01-01T01: 00: 00+0100" + }, + { + "uid": 2, + "Alias": "blvSensor1", + "Enable": false, + "Status": "DISABLED", + "Reset": false, + "ResetTime": "1-01-01T01: 00: 00+0100", + "Name": "", + "Value": 25, + "LastUpdate": "1-01-01T01: 00: 00+0100", + "MinValue": 0, + "MinTime": "1-01-01T01: 00: 00+0100", + "MaxValue": 0, + "MaxTime": "1-01-01T01: 00: 00+0100", + "LowAlarmValue": 0, + "LowAlarmTime": "1-01-01T01: 00: 00+0100", + "HighAlarmValue": 0, + "PollingInterval": 0, + "HighAlarmTime": "1-01-01T01: 00: 00+0100" + }, + { + "uid": 3, + "Alias": "blvSensor2", + "Enable": false, + "Status": "DISABLED", + "Reset": false, + "ResetTime": "1-01-01T01: 00: 00+0100", + "Name": "", + "Value": 25, + "LastUpdate": "1-01-01T01: 00: 00+0100", + "MinValue": 0, + "MinTime": "1-01-01T01: 00: 00+0100", + "MaxValue": 0, + "MaxTime": "1-01-01T01: 00: 00+0100", + "LowAlarmValue": 0, + "LowAlarmTime": "1-01-01T01: 00: 00+0100", + "HighAlarmValue": 0, + "PollingInterval": 0, + "HighAlarmTime": "1-01-01T01: 00: 00+0100" + }, + { + "uid": 4, + "Alias": "batSensor", + "Enable": false, + "Status": "DISABLED", + "Reset": false, + "ResetTime": "1-01-01T01: 00: 00+0100", + "Name": "", + "Value": 25, + "LastUpdate": "1-01-01T01: 00: 00+0100", + "MinValue": 0, + "MinTime": "1-01-01T01: 00: 00+0100", + "MaxValue": 0, + "MaxTime": "1-01-01T01: 00: 00+0100", + "LowAlarmValue": 0, + "LowAlarmTime": "1-01-01T01: 00: 00+0100", + "HighAlarmValue": 0, + "PollingInterval": 0, + "HighAlarmTime": "1-01-01T01: 00: 00+0100" + }, + { + "uid": 5, + "Alias": "avgSensor", + "Enable": false, + "Status": "DISABLED", + "Reset": false, + "ResetTime": "1-01-01T01: 00: 00+0100", + "Name": "", + "Value": 25, + "LastUpdate": "1-01-01T01: 00: 00+0100", + "MinValue": 0, + "MinTime": "1-01-01T01: 00: 00+0100", + "MaxValue": 0, + "MaxTime": "1-01-01T01: 00: 00+0100", + "LowAlarmValue": 0, + "LowAlarmTime": "1-01-01T01: 00: 00+0100", + "HighAlarmValue": 0, + "PollingInterval": 0, + "HighAlarmTime": "1-01-01T01: 00: 00+0100" + } + ] + }, + "NetworkProperties": { + "MaxTCPWindowSize": 0, + "TCPImplementation": "" + }, + "SupportedDataModels": [ + { + "uid": 1, + "URL": "http:\/\/192.168.1.1\/datamodels\/cpe-dti-tr104.xml", + "UUID": "", + "URN": "urn:broadband-forum-org:tr-104-1-0-0", + "Features": "X_VoIP", + "Alias": "cpe-1" + }, + { + "uid": 2, + "URL": "http:\/\/192.168.1.1\/datamodels\/cpe-dti-tr140.xml", + "UUID": "", + "URN": "urn:broadband-forum-org:tr-140-1-1-0", + "Features": "X_NAS", + "Alias": "cpe-2" + }, + { + "uid": 3, + "URL": "http:\/\/192.168.1.1\/datamodels\/cpe-dti-tr181.xml", + "UUID": "", + "URN": "urn:broadband-forum-org:tr-181-2-2-0", + "Features": "DNSClient,DNSServer,Firewall,IPv6,NAT,Router", + "Alias": "cpe-3" + } + ], + "RouterName": "Telekom-123456-2.4GHz", + "RebootStatus": 0.000000, + "ResetStatus": 0.000000, + "UpdateStatus": 0.000000, + "SNMP": false, + "FirstConnection": false, + "SimpleLogs": { + "SystemLog": "", + "FirewallLog": "", + "CallLog": "" + }, + "BuildDate": "2020-01-01T01: 01: 13+0100", + "SpecVersion": "1.0", + "CLID": "", + "FlushDeviceLog": false, + "Processors": [ + { + "uid": 1, + "Alias": "CPU1", + "Architecture": "ARM" + }, + { + "uid": 2, + "Alias": "CPU2", + "Architecture": "ARM" + } + ], + "VendorLogFiles": [ + { + "uid": 1, + "Alias": "OPERATOR_LOG", + "Name": "", + "MaximumSize": 0, + "Persistent": false, + "LogData": "", + "DiagnosticState": "NONE" + } + ], + "ProxierInfo": { + "ManufacturerOUI": "", + "ProductClass": "", + "SerialNumber": "", + "ProxyProtocol": "" + }, + "Locations": [], + "APIVersion": "", + "Logging": { + "LogLevel": "Informational", + "Syslog": { + "Enable": true, + "Server": { + "Enable": false, + "IPv4Address": "192.168.1.2", + "Port": 514, + "SyslogConfig": "", + "LoggerCategories": "", + "LoggerExcludedCategories": "", + "SourceIndex": 1 + }, + "Destinations": [ + { + "uid": 1, + "Alias": "RAM", + "Enable": true, + "Status": "RUNNING", + "LogSize": 1048576, + "SyslogConfig": "", + "LoggerCategories": "", + "LoggerExcludedCategories": "", + "FileStorageLocation": "\/var\/log\/messages", + "SourceIndex": 1 + } + ], + "Sources": [ + { + "uid": 1, + "Alias": "Log_source", + "Enable": true, + "KernelSource": true, + "InternalSource": true, + "UnixStream": true, + "FileSourceLocation": "", + "Network": { + "Enable": false, + "Protocol": "", + "Port": 0 + } + } + ] + }, + "ResetLogOper": false + }, + "BackupTimeStamp": "1-01-01T01: 00: 00+0100", + "ConfigBackupRestoreEnable": false, + "LastBackupDate": "1-01-01T01: 00: 00+0100", + "LastRestoreDate": "1-01-01T01: 00: 00+0100", + "EventLog": "", + "BackupSoftwareVersion": "", + "BootloaderVersion": "v1.23_B1_v4.56", + "FlashMemoryStatus": { + "Total": 0, + "Free": 0 + }, + "CustomerModelName": "", + "UserConfigFiles": [] + } + }, + "capability": { + "name": "DeviceInfo", + "type": "devinfo:DeviceInfo xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "config": true, + "capability": true, + "xml-child": true, + "has-dynamic-extension": true + }, + "interfaces": [ + { + "name": "UserBackupRestoreInterface", + "major-version": "1", + "minor-version": "0", + "commands": [ + { + "name": "CreateUserBackup" + }, + { + "name": "RestoreUserBackup", + "input-parameters": [ + { + "name": "Index", + "type": "RestoreUserBackup:Index xmo:uint32 xmo:number xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + } + ] + } + ] + } + ], + "attributes": [ + { + "name": "DeviceCategory", + "type": "devinfo:DeviceInfo:DeviceCategory xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + } + }, + { + "name": "Manufacturer", + "type": "devinfo:DeviceInfo:Manufacturer xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "ManufacturerOUI", + "type": "devinfo:DeviceInfo:ManufacturerOUI xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": {} + }, + { + "name": "ModelName", + "type": "devinfo:DeviceInfo:ModelName xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "ModelNumber", + "type": "devinfo:DeviceInfo:ModelNumber xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "Description", + "type": "devinfo:DeviceInfo:Description xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 256 + } + }, + { + "name": "ProductClass", + "type": "devinfo:DeviceInfo:ProductClass xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "SerialNumber", + "type": "devinfo:DeviceInfo:SerialNumber xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "HardwareVersion", + "type": "devinfo:DeviceInfo:HardwareVersion xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "SoftwareVersion", + "type": "devinfo:DeviceInfo:SoftwareVersion xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "AdditionalHardwareVersion", + "type": "devinfo:DeviceInfo:AdditionalHardwareVersion xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "AdditionalSoftwareVersion", + "type": "devinfo:DeviceInfo:AdditionalSoftwareVersion xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "ExternalFirmwareVersion", + "type": "devinfo:DeviceInfo:ExternalFirmwareVersion xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "InternalFirmwareVersion", + "type": "devinfo:DeviceInfo:InternalFirmwareVersion xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "GUIFirmwareVersion", + "type": "devinfo:DeviceInfo:GUIFirmwareVersion xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "GUIAPIVersion", + "type": "devinfo:DeviceInfo:GUIAPIVersion xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "default-value": "2.92", + "restrictions": { + "max-length": 64 + } + }, + { + "name": "ProvisioningCode", + "type": "devinfo:DeviceInfo:ProvisioningCode xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "UpTime", + "type": "xmo:uint32 xmo:number xmo:value", + "flags": { + "value": true, + "statistic": true, + "xml-child": true + } + }, + { + "name": "FirstUseDate", + "type": "xmo:time xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + }, + { + "name": "MACAddress", + "type": "devinfo:DeviceInfo:MACAddress xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "Mode", + "type": "devinfo:DeviceInfo:Mode xmo:int32 xmo:number xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "enum-values": [ + { + "name": "GW", + "value": "0" + }, + { + "name": "GWC", + "value": "1" + }, + { + "name": "AP", + "value": "2" + } + ] + } + }, + { + "name": "Country", + "type": "devinfo:DeviceInfo:Country xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "default-value": "fr", + "restrictions": { + "min-length": 2, + "max-length": 64 + } + }, + { + "name": "RebootCount", + "type": "xmo:uint32 xmo:number xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + }, + { + "name": "NodesToRestore", + "type": "xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + }, + { + "name": "VendorConfigFiles", + "type": "devinfo:DeviceInfo:VendorConfigFiles xmo:list xmo:container", + "flags": { + "list": true, + "config": true, + "xml-child": true, + "has-dynamic-extension": true + }, + "children": [] + }, + { + "name": "MemoryStatus", + "type": "devinfo:DeviceInfo:MemoryStatus xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "status": true, + "capability": true, + "xml-child": true + } + }, + { + "name": "ProcessStatus", + "type": "devinfo:DeviceInfo:ProcessStatus xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "status": true, + "capability": true, + "xml-child": true + } + }, + { + "name": "TemperatureStatus", + "type": "devinfo:DeviceInfo:TemperatureStatus xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "config": true, + "capability": true, + "xml-child": true, + "has-dynamic-extension": true + } + }, + { + "name": "NetworkProperties", + "type": "devinfo:NetworkProperties xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "config": true, + "capability": true, + "xml-child": true + } + }, + { + "name": "SupportedDataModels", + "type": "devinfo:DeviceInfo:SupportedDataModels xmo:list xmo:container", + "flags": { + "list": true, + "status": true, + "xml-child": true + }, + "children": [] + }, + { + "name": "RouterName", + "type": "devinfo:DeviceInfo:RouterName xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 32 + } + }, + { + "name": "RebootStatus", + "type": "devinfo:DeviceInfo:RebootStatus xmo:float xmo:number xmo:value", + "flags": { + "value": true, + "statistic": true, + "xml-child": true + }, + "restrictions": { + "min-value-inclusive": 0, + "max-value-inclusive": 1 + } + }, + { + "name": "ResetStatus", + "type": "devinfo:DeviceInfo:ResetStatus xmo:float xmo:number xmo:value", + "flags": { + "value": true, + "statistic": true, + "xml-child": true + }, + "restrictions": { + "min-value-inclusive": 0, + "max-value-inclusive": 1 + } + }, + { + "name": "UpdateStatus", + "type": "devinfo:DeviceInfo:UpdateStatus xmo:float xmo:number xmo:value", + "flags": { + "value": true, + "statistic": true, + "xml-child": true + }, + "restrictions": { + "min-value-inclusive": 0, + "max-value-inclusive": 1 + } + }, + { + "name": "SNMP", + "type": "xmo:boolean xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "default-value": "false" + }, + { + "name": "FirstConnection", + "type": "xmo:boolean xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "default-value": "true" + }, + { + "name": "SimpleLogs", + "type": "devinfo:SimpleLogs xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "config": true, + "capability": true, + "xml-child": true + } + }, + { + "name": "BuildDate", + "type": "xmo:time xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + } + }, + { + "name": "SpecVersion", + "type": "devinfo:DeviceInfo:SpecVersion xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "default-value": "1.0", + "restrictions": { + "max-length": 32 + } + }, + { + "name": "CLID", + "type": "devinfo:DeviceInfo:CLID xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + }, + { + "name": "FlushDeviceLog", + "type": "devinfo:DeviceInfo:FlushDeviceLog xmo:boolean xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "default-value": "false" + }, + { + "name": "Processors", + "type": "devinfo:DeviceInfo:Processors xmo:list xmo:container", + "flags": { + "list": true, + "config": true, + "xml-child": true + }, + "children": [] + }, + { + "name": "VendorLogFiles", + "type": "devinfo:DeviceInfo:VendorLogFiles xmo:list xmo:container", + "flags": { + "list": true, + "config": true, + "xml-child": true + }, + "children": [] + }, + { + "name": "ProxierInfo", + "type": "devinfo:DeviceInfo:ProxierInfo xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "config": true, + "capability": true, + "xml-child": true + } + }, + { + "name": "Locations", + "type": "devinfo:DeviceInfo:Locations xmo:list xmo:container", + "flags": { + "list": true, + "config": true, + "xml-child": true + }, + "children": [] + }, + { + "name": "APIVersion", + "type": "devinfo:DeviceInfo:APIVersion xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 5 + } + }, + { + "name": "Logging", + "type": "devinfo:Logging xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "config": true, + "capability": true, + "xml-child": true + } + }, + { + "name": "BackupTimeStamp", + "type": "xmo:time xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + }, + { + "name": "ConfigBackupRestoreEnable", + "type": "xmo:boolean xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "default-value": "false" + }, + { + "name": "LastBackupDate", + "type": "xmo:time xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + }, + { + "name": "LastRestoreDate", + "type": "xmo:time xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + } + }, + { + "name": "EventLog", + "type": "xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + } + }, + { + "name": "BackupSoftwareVersion", + "type": "xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + } + }, + { + "name": "BootloaderVersion", + "type": "xmo:str xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + } + }, + { + "name": "FlashMemoryStatus", + "type": "devinfo:DeviceInfo:FlashMemoryStatus xmo:extended-node xmo:child-node xmo:node", + "flags": { + "node": true, + "status": true, + "capability": true, + "xml-child": true + } + }, + { + "name": "CustomerModelName", + "type": "devinfo:DeviceInfo:CustomerModelName xmo:str xmo:value", + "flags": { + "value": true, + "config": true, + "xml-child": true + }, + "restrictions": { + "max-length": 64 + } + }, + { + "name": "UserConfigFiles", + "type": "devinfo:DeviceInfo:UserConfigFiles xmo:list xmo:container", + "flags": { + "list": true, + "config": true, + "xml-child": true + }, + "children": [] + } + ] + } + } + } + ] + } + ], + "events": [] + } +} diff --git a/tests/fixtures/hosts.json b/tests/fixtures/hosts.json new file mode 100644 index 0000000..b84a8b7 --- /dev/null +++ b/tests/fixtures/hosts.json @@ -0,0 +1,124 @@ +{ + "reply": { + "uid": 0, + "id": 1, + "error": { + "code": 16777216, + "description": "XMO_REQUEST_NO_ERR" + }, + "actions": [ + { + "uid": 1, + "id": 0, + "error": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "callbacks": [ + { + "uid": 1, + "result": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "xpath": "Device/Hosts/Hosts", + "parameters": { + "value": [ + { + "uid": 1, + "Alias": "cpe-1", + "PhysAddress": "11:22:33:AA:BB:CC", + "IPAddress": "", + "AddressSource": "NONE", + "DHCPClient": "", + "LeaseTimeRemaining": 0, + "AssociatedDevice": "", + "RemoteAssociatedDevice": "", + "Layer1Interface": "", + "Layer3Interface": "", + "VendorClassID": "", + "ClientID": "", + "UserClassID": "", + "HostName": "", + "Active": false, + "LeaseStart": 0, + "LeaseDuration": 0, + "InterfaceType": "Ethernet", + "DetectedDeviceType": "Smartphone", + "ActiveLastChange": "2024-01-18T12:38:30+0100", + "UserFriendlyName": "MyPhone", + "UserHostName": "MyPhone", + "UserDeviceType": "MOBILE_PHONE", + "Icon": "", + "Room": "", + "BlacklistEnable": true, + "Blacklisted": false, + "UnblockHoursCount": 0, + "BlacklistStatus": false, + "BlacklistedAccordingToSchedule": true, + "BlacklistedSchedule": [], + "Hidden": false, + "Options": [], + "VendorClassIDv6": "", + "SysfsId": "", + "IPv4Addresses": [], + "IPv6Addresses": [], + "DeviceTypeAssociation": "Device/DeviceDiscovery/DeviceIdentification/DHCPFingerprintDatabase/Entries/Entry[@uid='5']", + "IPv6LeaseStart": 0, + "IPv6LeaseDuration": 0, + "IPv6LeaseTimeRemaining": 0, + "IPv6ClientID": "" + }, + { + "uid": 2, + "Alias": "cpe-2", + "PhysAddress": "12:34:56:78:90:AB", + "IPAddress": "", + "AddressSource": "NONE", + "DHCPClient": "", + "LeaseTimeRemaining": 0, + "AssociatedDevice": "", + "RemoteAssociatedDevice": "", + "Layer1Interface": "", + "Layer3Interface": "", + "VendorClassID": "", + "ClientID": "", + "UserClassID": "", + "HostName": "", + "Active": false, + "LeaseStart": 0, + "LeaseDuration": 0, + "InterfaceType": "Ethernet", + "DetectedDeviceType": "Smartphone", + "ActiveLastChange": "2024-04-22T17:31:12+0100", + "UserFriendlyName": "MyPhone", + "UserHostName": "MyPhone", + "UserDeviceType": "MOBILE_PHONE", + "Icon": "", + "Room": "", + "BlacklistEnable": true, + "Blacklisted": false, + "UnblockHoursCount": 0, + "BlacklistStatus": false, + "BlacklistedAccordingToSchedule": true, + "BlacklistedSchedule": [], + "Hidden": false, + "Options": [], + "VendorClassIDv6": "", + "SysfsId": "", + "IPv4Addresses": [], + "IPv6Addresses": [], + "DeviceTypeAssociation": "Device/DeviceDiscovery/DeviceIdentification/DHCPFingerprintDatabase/Entries/Entry[@uid='4']", + "IPv6LeaseStart": 0, + "IPv6LeaseDuration": 0, + "IPv6LeaseTimeRemaining": 0, + "IPv6ClientID": "" + } + ] + } + } + ] + } + ] + } +} diff --git a/tests/fixtures/login_auth_error.json b/tests/fixtures/login_auth_error.json new file mode 100644 index 0000000..96464a7 --- /dev/null +++ b/tests/fixtures/login_auth_error.json @@ -0,0 +1,32 @@ +{ + "reply": { + "uid": 0, + "id": 0, + "error": { + "code": 16777236, + "description": "XMO_REQUEST_ACTION_ERR" + }, + "actions": [ + { + "uid": 1, + "id": 0, + "error": { + "code": 16777223, + "description": "XMO_AUTHENTICATION_ERR" + }, + "callbacks": [ + { + "uid": 1, + "result": { + "code": 16777223, + "description": "XMO_AUTHENTICATION_ERR" + }, + "xpath": "Device", + "parameters": {} + } + ] + } + ], + "events": [] + } +} diff --git a/tests/fixtures/login_invalid_session.json b/tests/fixtures/login_invalid_session.json new file mode 100644 index 0000000..66b9a4e --- /dev/null +++ b/tests/fixtures/login_invalid_session.json @@ -0,0 +1,9 @@ +{ + "reply": { + "error": { + "description": "XMO_INVALID_SESSION_ERR", + "info": "Invalid session" + }, + "uid": 1 + } +} diff --git a/tests/fixtures/login_success.json b/tests/fixtures/login_success.json new file mode 100644 index 0000000..687ddc6 --- /dev/null +++ b/tests/fixtures/login_success.json @@ -0,0 +1,35 @@ +{ + "reply": { + "uid": 0, + "id": 0, + "error": { + "code": 16777216, + "description": "XMO_REQUEST_NO_ERR" + }, + "actions": [ + { + "uid": 1, + "id": 0, + "error": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "callbacks": [ + { + "uid": 1, + "result": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "xpath": "Device", + "parameters": { + "id": 12345, + "nonce": "abcdef1234567890" + } + } + ] + } + ], + "events": [] + } +} diff --git a/tests/fixtures/xpath_value.json b/tests/fixtures/xpath_value.json new file mode 100644 index 0000000..8555415 --- /dev/null +++ b/tests/fixtures/xpath_value.json @@ -0,0 +1,76 @@ +{ + "reply": { + "uid": 0, + "id": 1, + "error": { + "code": 16777216, + "description": "XMO_REQUEST_NO_ERR" + }, + "actions": [ + { + "uid": 1, + "id": 0, + "error": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "callbacks": [ + { + "uid": 1, + "result": { + "code": 16777238, + "description": "XMO_NO_ERR" + }, + "xpath": "Device\/WiFi\/Radios\/Radio[@uid='1']\/Status", + "parameters": { + "value": "UP", + "capability": { + "name": "Status", + "type": "interface:Interface:Status xmo:int32 xmo:number xmo:value", + "flags": { + "value": true, + "status": true, + "xml-child": true + }, + "default-value": "DOWN", + "restrictions": { + "enum-values": [ + { + "name": "UP", + "value": "0" + }, + { + "name": "DOWN", + "value": "1" + }, + { + "name": "UNKNOWN", + "value": "2" + }, + { + "name": "DORMANT", + "value": "3" + }, + { + "name": "NOTPRESENT", + "value": "4" + }, + { + "name": "LOWERLAYERDOWN", + "value": "5" + }, + { + "name": "ERROR", + "value": "6" + } + ] + } + } + } + } + ] + } + ], + "events": [] + } +} diff --git a/tests/unit/test_client_basic.py b/tests/unit/test_client_basic.py new file mode 100644 index 0000000..ba98fda --- /dev/null +++ b/tests/unit/test_client_basic.py @@ -0,0 +1,133 @@ +"""Basic client tests demonstrating mocking patterns for the Sagemcom API client.""" + +# pylint: disable=protected-access + +import pytest + +from sagemcom_api.client import SagemcomClient +from sagemcom_api.enums import EncryptionMethod +from sagemcom_api.exceptions import AuthenticationException + + +@pytest.mark.asyncio +async def test_login_success(mock_session_factory, login_success_response): + """ + Test successful login with mocked session. + + Demonstrates: + - Mocking aiohttp session at session.post level + - Successful login flow with session_id and nonce exchange + - Using fixture-based API responses + """ + # Arrange: Create mock session with successful login response + mock_session = mock_session_factory([login_success_response]) + client = SagemcomClient( + host="192.168.1.1", + username="admin", + password="admin", + authentication_method=EncryptionMethod.MD5, + session=mock_session, + ) + + # Act: Perform login + result = await client.login() + + # Assert: Verify login succeeded and session state was updated + assert result is True + assert client._session_id == 12345 + assert client._server_nonce == "abcdef1234567890" + assert client._request_id == 0 # Login is first request (id=0) + + +@pytest.mark.asyncio +async def test_login_authentication_error( + mock_session_factory, login_auth_error_response +): + """ + Test login raises AuthenticationException on XMO_AUTHENTICATION_ERR. + + Demonstrates: + - Mocking error responses from the API + - Exception handling for authentication failures + - Action-level error handling (XMO_REQUEST_ACTION_ERR + XMO_AUTHENTICATION_ERR) + """ + # Arrange: Create mock session with authentication error response + mock_session = mock_session_factory([login_auth_error_response]) + client = SagemcomClient( + host="192.168.1.1", + username="admin", + password="wrong_password", + authentication_method=EncryptionMethod.MD5, + session=mock_session, + ) + + # Act & Assert: Verify AuthenticationException is raised + with pytest.raises(AuthenticationException) as exc_info: + await client.login() + + # Verify exception contains error details + assert "XMO_AUTHENTICATION_ERR" in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_get_value_by_xpath_url_encoding( + mock_session_factory, login_success_response, xpath_value_response +): + """ + Test XPath values are URL-encoded with safe characters preserved. + + Demonstrates: + - XPath URL encoding behavior + - Preservation of safe characters: /=[]' + - Multiple mock responses in sequence (login, then XPath query) + - Accessing request payload to validate encoding + """ + # Arrange: Create mock session with login and XPath responses + mock_session = mock_session_factory([login_success_response, xpath_value_response]) + client = SagemcomClient( + host="192.168.1.1", + username="admin", + password="admin", + authentication_method=EncryptionMethod.MD5, + session=mock_session, + ) + + # Login first + await client.login() + + # Act: Query XPath with characters that need encoding + xpath = "Device/WiFi/Radios/Radio[Alias='RADIO2G4']/Status" + result = await client.get_value_by_xpath(xpath) + + # Assert: Verify result contains expected data + assert result == "UP" + + # Verify two POST requests were made (login + XPath query) + assert mock_session.post.call_count == 2 + + # Verify XPath was properly URL-encoded in the request + # Note: Detailed XPath encoding validation (safe characters /=[]') + # will be covered later + + +@pytest.mark.asyncio +async def test_login_with_preconfigured_fixture(mock_client_sha512): + """ + Test login using pre-configured client fixture. + + Demonstrates: + - Using pre-configured client fixtures for concise tests + - SHA512 encryption method configuration + - Reduced boilerplate for common test scenarios + """ + # Arrange: Use pre-configured mock client fixture + client = mock_client_sha512 + + # Act: Login with pre-configured client (no setup needed) + result = await client.login() + + # Assert: Verify login succeeded and encryption method is SHA512 + assert result is True + assert client.authentication_method == EncryptionMethod.SHA512 + assert client._session_id == 12345 + assert client._server_nonce == "abcdef1234567890" From 25f18a013142c6c280ea3f467a434d39e2ef1210 Mon Sep 17 00:00:00 2001 From: bakonyiferenc <54292419+bakonyiferenc@users.noreply.github.com> Date: Sun, 21 Dec 2025 08:47:34 +0100 Subject: [PATCH 2/2] Update tests/fixtures/hosts.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/fixtures/hosts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/hosts.json b/tests/fixtures/hosts.json index b84a8b7..a996a0d 100644 --- a/tests/fixtures/hosts.json +++ b/tests/fixtures/hosts.json @@ -92,7 +92,7 @@ "DetectedDeviceType": "Smartphone", "ActiveLastChange": "2024-04-22T17:31:12+0100", "UserFriendlyName": "MyPhone", - "UserHostName": "MyPhone", + "UserHostName": "MyPhone", "UserDeviceType": "MOBILE_PHONE", "Icon": "", "Room": "",