From 02c440527bef17828e7646dce52771dfec2089b4 Mon Sep 17 00:00:00 2001 From: 0ln <8427756+0ln@users.noreply.github.com> Date: Wed, 7 Jan 2026 10:43:48 +0100 Subject: [PATCH 1/7] feat: add missing API endpoints for package management --- pyseventeentrack/profile.py | 183 ++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/pyseventeentrack/profile.py b/pyseventeentrack/profile.py index 1a4309e..38a3e29 100644 --- a/pyseventeentrack/profile.py +++ b/pyseventeentrack/profile.py @@ -205,3 +205,186 @@ async def archive_package(self, tracking_number: str): code = archive_resp.get("Code") if code != 0: raise RequestError(f"Non-zero status code in response: {code}") + + async def activate_package(self, tracking_number: str): + """Activate (unarchive) a package by tracking number.""" + packages = await self.packages() + + try: + package = next(p for p in packages if p.tracking_number == tracking_number) + except StopIteration as err: + raise InvalidTrackingNumberError( + f"Package not found by tracking number: {tracking_number}" + ) from err + + internal_id = package.id + + _LOGGER.debug("Found internal ID of package: %s", internal_id) + + activate_resp: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": "SetTrackActivate", + "param": {"TrackInfoIds": [internal_id]}, + }, + ) + + _LOGGER.debug("Activate package response: %s", activate_resp) + + code = activate_resp.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") + + async def delete_package(self, tracking_number: str): + """Delete a package by tracking number.""" + packages = await self.packages() + + try: + package = next(p for p in packages if p.tracking_number == tracking_number) + except StopIteration as err: + raise InvalidTrackingNumberError( + f"Package not found by tracking number: {tracking_number}" + ) from err + + internal_id = package.id + + _LOGGER.debug("Found internal ID of package: %s", internal_id) + + delete_resp: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": "DelTrackNo", + "param": {"TrackInfoIds": [internal_id]}, + }, + ) + + _LOGGER.debug("Delete package response: %s", delete_resp) + + code = delete_resp.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") + + async def set_tag_type(self, internal_id: str, tag: str): + """Set the tag type for an existing package.""" + tag_resp: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": "SetTrackTagType", + "param": {"tid": internal_id, "tag": tag}, + }, + ) + + _LOGGER.debug("Set tag type response: %s", tag_resp) + + code = tag_resp.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") + + async def set_carrier( + self, internal_id: str, first_carrier: str, second_carrier: str = "0" + ): + """Set the carrier(s) for an existing package.""" + carrier_resp: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": "SetTrackCarrier", + "param": { + "TrackInfoId": internal_id, + "FirstCarrier": first_carrier, + "SecondCarrier": second_carrier, + }, + }, + ) + + _LOGGER.debug("Set carrier response: %s", carrier_resp) + + code = carrier_resp.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") + + async def track_info_by_id(self, *track_info_ids: str, isa: bool = False) -> list: + """Get tracking info by internal tracking IDs.""" + if not track_info_ids: + return [] + + track_resp: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": "GetTrackInfoById", + "param": {"isa": isa, "tids": list(track_info_ids)}, + }, + ) + + _LOGGER.debug("Track info by ID response: %s", track_resp) + + code = track_resp.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") + + return track_resp.get("Json", []) + + async def order_info_by_id(self, internal_id: str) -> dict: + """Get order info by internal tracking ID.""" + order_resp: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": "GetOrderInfoById", + "param": {"tid": internal_id}, + }, + ) + + _LOGGER.debug("Order info response: %s", order_resp) + + code = order_resp.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") + + return order_resp.get("Json", {}).get("order", {}) + + async def save_order_info( + self, + internal_id: str, + *, + opn: Optional[str] = None, + ptoid: Optional[str] = None, + pt: Optional[str] = None, + otime: Optional[str] = None, + ): + """Save order info for an existing package.""" + param: dict = {"tid": internal_id} + if opn is not None: + param["opn"] = opn + if ptoid is not None: + param["ptoid"] = ptoid + if pt is not None: + param["pt"] = pt + if otime is not None: + param["otime"] = otime + + order_resp: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": "SaveOrderInfo", + "param": param, + }, + ) + + _LOGGER.debug("Save order info response: %s", order_resp) + + code = order_resp.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") From 7b4f991735a656a2c202d2820b2762a249b11492 Mon Sep 17 00:00:00 2001 From: 0ln <8427756+0ln@users.noreply.github.com> Date: Wed, 7 Jan 2026 10:44:50 +0100 Subject: [PATCH 2/7] test: add tests and response fixtures for package activation and deletion --- tests/fixtures/activate_package_response.json | 1 + ...ate_package_response_failure_response.json | 1 + tests/fixtures/delete_package_response.json | 1 + ...ete_package_response_failure_response.json | 1 + .../order_info_by_id_failure_response.json | 1 + tests/fixtures/order_info_by_id_response.json | 1 + .../save_order_info_failure_response.json | 1 + tests/fixtures/save_order_info_response.json | 1 + tests/fixtures/set_carrier_response.json | 1 + ...set_carrier_response_failure_response.json | 1 + tests/fixtures/set_tag_type_response.json | 1 + ...et_tag_type_response_failure_response.json | 1 + .../track_info_by_id_failure_response.json | 1 + tests/fixtures/track_info_by_id_response.json | 1 + tests/test_profile.py | 479 ++++++++++++++++++ 15 files changed, 493 insertions(+) create mode 100644 tests/fixtures/activate_package_response.json create mode 100644 tests/fixtures/activate_package_response_failure_response.json create mode 100644 tests/fixtures/delete_package_response.json create mode 100644 tests/fixtures/delete_package_response_failure_response.json create mode 100644 tests/fixtures/order_info_by_id_failure_response.json create mode 100644 tests/fixtures/order_info_by_id_response.json create mode 100644 tests/fixtures/save_order_info_failure_response.json create mode 100644 tests/fixtures/save_order_info_response.json create mode 100644 tests/fixtures/set_carrier_response.json create mode 100644 tests/fixtures/set_carrier_response_failure_response.json create mode 100644 tests/fixtures/set_tag_type_response.json create mode 100644 tests/fixtures/set_tag_type_response_failure_response.json create mode 100644 tests/fixtures/track_info_by_id_failure_response.json create mode 100644 tests/fixtures/track_info_by_id_response.json diff --git a/tests/fixtures/activate_package_response.json b/tests/fixtures/activate_package_response.json new file mode 100644 index 0000000..b3ca7b3 --- /dev/null +++ b/tests/fixtures/activate_package_response.json @@ -0,0 +1 @@ +{"Code": 0, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/activate_package_response_failure_response.json b/tests/fixtures/activate_package_response_failure_response.json new file mode 100644 index 0000000..227234b --- /dev/null +++ b/tests/fixtures/activate_package_response_failure_response.json @@ -0,0 +1 @@ +{"Code": 1, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/delete_package_response.json b/tests/fixtures/delete_package_response.json new file mode 100644 index 0000000..b3ca7b3 --- /dev/null +++ b/tests/fixtures/delete_package_response.json @@ -0,0 +1 @@ +{"Code": 0, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/delete_package_response_failure_response.json b/tests/fixtures/delete_package_response_failure_response.json new file mode 100644 index 0000000..227234b --- /dev/null +++ b/tests/fixtures/delete_package_response_failure_response.json @@ -0,0 +1 @@ +{"Code": 1, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/order_info_by_id_failure_response.json b/tests/fixtures/order_info_by_id_failure_response.json new file mode 100644 index 0000000..227234b --- /dev/null +++ b/tests/fixtures/order_info_by_id_failure_response.json @@ -0,0 +1 @@ +{"Code": 1, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/order_info_by_id_response.json b/tests/fixtures/order_info_by_id_response.json new file mode 100644 index 0000000..70f08b5 --- /dev/null +++ b/tests/fixtures/order_info_by_id_response.json @@ -0,0 +1 @@ +{"Code": 0, "Json": {"order": {"opn": "Acme", "ptoid": "123", "pt": "01", "otime": "2024-12-01"}}} \ No newline at end of file diff --git a/tests/fixtures/save_order_info_failure_response.json b/tests/fixtures/save_order_info_failure_response.json new file mode 100644 index 0000000..227234b --- /dev/null +++ b/tests/fixtures/save_order_info_failure_response.json @@ -0,0 +1 @@ +{"Code": 1, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/save_order_info_response.json b/tests/fixtures/save_order_info_response.json new file mode 100644 index 0000000..b3ca7b3 --- /dev/null +++ b/tests/fixtures/save_order_info_response.json @@ -0,0 +1 @@ +{"Code": 0, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/set_carrier_response.json b/tests/fixtures/set_carrier_response.json new file mode 100644 index 0000000..b3ca7b3 --- /dev/null +++ b/tests/fixtures/set_carrier_response.json @@ -0,0 +1 @@ +{"Code": 0, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/set_carrier_response_failure_response.json b/tests/fixtures/set_carrier_response_failure_response.json new file mode 100644 index 0000000..227234b --- /dev/null +++ b/tests/fixtures/set_carrier_response_failure_response.json @@ -0,0 +1 @@ +{"Code": 1, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/set_tag_type_response.json b/tests/fixtures/set_tag_type_response.json new file mode 100644 index 0000000..b3ca7b3 --- /dev/null +++ b/tests/fixtures/set_tag_type_response.json @@ -0,0 +1 @@ +{"Code": 0, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/set_tag_type_response_failure_response.json b/tests/fixtures/set_tag_type_response_failure_response.json new file mode 100644 index 0000000..227234b --- /dev/null +++ b/tests/fixtures/set_tag_type_response_failure_response.json @@ -0,0 +1 @@ +{"Code": 1, "Json": {}} \ No newline at end of file diff --git a/tests/fixtures/track_info_by_id_failure_response.json b/tests/fixtures/track_info_by_id_failure_response.json new file mode 100644 index 0000000..554b635 --- /dev/null +++ b/tests/fixtures/track_info_by_id_failure_response.json @@ -0,0 +1 @@ +{"Code": 1, "Json": []} \ No newline at end of file diff --git a/tests/fixtures/track_info_by_id_response.json b/tests/fixtures/track_info_by_id_response.json new file mode 100644 index 0000000..8a099a4 --- /dev/null +++ b/tests/fixtures/track_info_by_id_response.json @@ -0,0 +1 @@ +{"Code": 0, "Json": [{"FTrackInfoId": "1234567890987654321", "FPackageState": "10", "FTrackStateType": 2}]} \ No newline at end of file diff --git a/tests/test_profile.py b/tests/test_profile.py index f3d0400..1c58101 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -473,3 +473,482 @@ async def test_archive_package_error_response(aresponses): client = Client(session=session) await client.profile.login(TEST_EMAIL, TEST_PASSWORD) await client.profile.archive_package("1234567890987654321") + + +@pytest.mark.asyncio +async def test_activate_package(aresponses): + """Test activating a package.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("packages_response.json"), status=200), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("activate_package_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + res = await client.profile.activate_package("1234567890987654321") + assert res is None + + +@pytest.mark.asyncio +async def test_activate_package_non_existing(aresponses): + """Test activating a non existing package.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("packages_response.json"), status=200), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("activate_package_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(InvalidTrackingNumberError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.activate_package("1234567890987654321111") + + +@pytest.mark.asyncio +async def test_activate_package_error_response(aresponses): + """Test activating a package with failed response.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("packages_response.json"), status=200), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("activate_package_response_failure_response.json"), + status=200, + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(RequestError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.activate_package("1234567890987654321") + + +@pytest.mark.asyncio +async def test_delete_package(aresponses): + """Test deleting a package.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("packages_response.json"), status=200), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("delete_package_response.json"), status=200), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + res = await client.profile.delete_package("1234567890987654321") + assert res is None + + +@pytest.mark.asyncio +async def test_delete_package_non_existing(aresponses): + """Test deleting a non existing package.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("packages_response.json"), status=200), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("delete_package_response.json"), status=200), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(InvalidTrackingNumberError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.delete_package("1234567890987654321111") + + +@pytest.mark.asyncio +async def test_delete_package_error_response(aresponses): + """Test deleting a package with failed response.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("packages_response.json"), status=200), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("delete_package_response_failure_response.json"), + status=200, + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(RequestError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.delete_package("1234567890987654321") + + +@pytest.mark.asyncio +async def test_set_tag_type(aresponses): + """Test setting tag type.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("set_tag_type_response.json"), status=200), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + res = await client.profile.set_tag_type("1234567890987654321", "0") + assert res is None + + +@pytest.mark.asyncio +async def test_set_tag_type_error_response(aresponses): + """Test setting tag type with failed response.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("set_tag_type_response_failure_response.json"), + status=200, + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(RequestError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.set_tag_type("1234567890987654321", "0") + + +@pytest.mark.asyncio +async def test_set_carrier(aresponses): + """Test setting carrier.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("set_carrier_response.json"), status=200), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + res = await client.profile.set_carrier("1234567890987654321", "100001", "0") + assert res is None + + +@pytest.mark.asyncio +async def test_set_carrier_error_response(aresponses): + """Test setting carrier with failed response.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("set_carrier_response_failure_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(RequestError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.set_carrier("1234567890987654321", "100001", "0") + + +@pytest.mark.asyncio +async def test_track_info_by_id(aresponses): + """Test getting track info by internal ID.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("track_info_by_id_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + items = await client.profile.track_info_by_id("1234567890987654321") + assert len(items) == 1 + assert items[0]["FTrackInfoId"] == "1234567890987654321" + + +@pytest.mark.asyncio +async def test_track_info_by_id_empty(): + """Test getting track info with no IDs.""" + client = Client() + items = await client.profile.track_info_by_id() + assert items == [] + + +@pytest.mark.asyncio +async def test_track_info_by_id_error_response(aresponses): + """Test getting track info with failed response.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("track_info_by_id_failure_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(RequestError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.track_info_by_id("1234567890987654321") + + +@pytest.mark.asyncio +async def test_order_info_by_id(aresponses): + """Test getting order info by internal ID.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("order_info_by_id_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + order = await client.profile.order_info_by_id("1234567890987654321") + assert order["opn"] == "Acme" + assert order["ptoid"] == "123" + assert order["pt"] == "01" + assert order["otime"] == "2024-12-01" + + +@pytest.mark.asyncio +async def test_order_info_by_id_error_response(aresponses): + """Test getting order info with failed response.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("order_info_by_id_failure_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(RequestError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.order_info_by_id("1234567890987654321") + + +@pytest.mark.asyncio +async def test_save_order_info(aresponses): + """Test saving order info.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response(text=load_fixture("save_order_info_response.json"), status=200), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + res = await client.profile.save_order_info( + "1234567890987654321", + opn="Acme", + ptoid="123", + pt="01", + otime="2024-12-01", + ) + assert res is None + + +@pytest.mark.asyncio +async def test_save_order_info_error_response(aresponses): + """Test saving order info with failed response.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + aresponses.add( + "buyer.17track.net", + "/orderapi/call", + "post", + aresponses.Response( + text=load_fixture("save_order_info_failure_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + with pytest.raises(RequestError): + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + await client.profile.save_order_info("1234567890987654321", opn="Acme") From 9e6d3a83b30f40d47b2b769f105c59b7782bedc1 Mon Sep 17 00:00:00 2001 From: 0ln <8427756+0ln@users.noreply.github.com> Date: Wed, 7 Jan 2026 10:53:45 +0100 Subject: [PATCH 3/7] docs: add archive, activate, delete, and order info methods to README --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5802e6..33fa6ae 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,20 @@ async def main() -> None: # Add new packages by tracking number await client.profile.add_package('', '') + # Archive / activate / delete packages + await client.profile.archive_package('') + await client.profile.activate_package('') + await client.profile.delete_package('') + + # Set tag type or carriers (internal tracking ID required) + await client.profile.set_tag_type('', '0') + await client.profile.set_carrier('', '', '') + + # Fetch and update order info (internal tracking ID required) + order_info = await client.profile.order_info_by_id('') + # >>> {'opn': 'Acme', 'ptoid': '123', 'pt': '01', 'otime': '2024-12-01'} + await client.profile.save_order_info('', opn='Acme', ptoid='123', pt='01', otime='2024-12-01') + asyncio.run(main()) ``` @@ -116,4 +130,3 @@ Each `Package` object has the following info: 9. Update `README.md` with any new documentation. 10. Add yourself to `AUTHORS.md`. 11. Submit a pull request! - From 833603c0721f5fd39bdbd4ca56a96bda3635669e Mon Sep 17 00:00:00 2001 From: 0ln <8427756+0ln@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:07:57 +0100 Subject: [PATCH 4/7] feat: add package management methods to use a unified buyer API call --- pyseventeentrack/profile.py | 264 ++++++++++++------------------------ 1 file changed, 89 insertions(+), 175 deletions(-) diff --git a/pyseventeentrack/profile.py b/pyseventeentrack/profile.py index 38a3e29..eaa9544 100644 --- a/pyseventeentrack/profile.py +++ b/pyseventeentrack/profile.py @@ -22,6 +22,41 @@ def __init__(self, request: Callable[..., Coroutine]) -> None: self._request: Callable[..., Coroutine] = request self.account_id: Optional[str] = None + async def _buyer_api_call( + self, method: str, param: dict, log_message: str + ) -> dict: + """Call the buyer API and validate the response.""" + response: dict = await self._request( + "post", + API_URL_BUYER, + json={ + "version": "1.0", + "method": method, + "param": param, + }, + ) + + _LOGGER.debug(log_message, response) + + code = response.get("Code") + if code != 0: + raise RequestError(f"Non-zero status code in response: {code}") + + return response + + async def _get_package_by_tracking_number( + self, tracking_number: str + ) -> Package: + """Get a package by tracking number.""" + packages = await self.packages() + + try: + return next(p for p in packages if p.tracking_number == tracking_number) + except StopIteration as err: + raise InvalidTrackingNumberError( + f"Package not found by tracking number: {tracking_number}" + ) from err + async def login(self, email: str, password: str) -> bool: """Login to the profile.""" login_resp: dict = await self._request( @@ -121,22 +156,12 @@ async def add_package( self, tracking_number: str, friendly_name: Optional[str] = None ): """Add a package by tracking number to the tracking list.""" - add_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "AddTrackNo", - "param": {"TrackNos": [tracking_number]}, - }, + add_resp = await self._buyer_api_call( + "AddTrackNo", + {"TrackNos": [tracking_number]}, + "Add package response: %s", ) - _LOGGER.debug("Add package response: %s", add_resp) - - code = add_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - if not friendly_name: return @@ -159,198 +184,97 @@ async def set_friendly_name(self, internal_id: str, friendly_name: str): internal_id is not the tracking number, it's the ID of an existing package. """ - remark_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "SetTrackRemark", - "param": {"TrackInfoId": internal_id, "Remark": friendly_name}, - }, + await self._buyer_api_call( + "SetTrackRemark", + {"TrackInfoId": internal_id, "Remark": friendly_name}, + "Set friendly name response: %s", ) - _LOGGER.debug("Set friendly name response: %s", remark_resp) - - code = remark_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - async def archive_package(self, tracking_number: str): """Archive a package by tracking number.""" - packages = await self.packages() - - try: - package = next(p for p in packages if p.tracking_number == tracking_number) - except StopIteration as err: - raise InvalidTrackingNumberError( - f"Package not found by tracking number: {tracking_number}" - ) from err + package = await self._get_package_by_tracking_number(tracking_number) internal_id = package.id _LOGGER.debug("Found internal ID of package: %s", internal_id) - archive_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "SetTrackArchived", - "param": {"TrackInfoIds": [internal_id]}, - }, + await self._buyer_api_call( + "SetTrackArchived", + {"TrackInfoIds": [internal_id]}, + "Archive package response: %s", ) - _LOGGER.debug("Archive package response: %s", archive_resp) - - code = archive_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - async def activate_package(self, tracking_number: str): """Activate (unarchive) a package by tracking number.""" - packages = await self.packages() - - try: - package = next(p for p in packages if p.tracking_number == tracking_number) - except StopIteration as err: - raise InvalidTrackingNumberError( - f"Package not found by tracking number: {tracking_number}" - ) from err + package = await self._get_package_by_tracking_number(tracking_number) internal_id = package.id _LOGGER.debug("Found internal ID of package: %s", internal_id) - activate_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "SetTrackActivate", - "param": {"TrackInfoIds": [internal_id]}, - }, + await self._buyer_api_call( + "SetTrackActivate", + {"TrackInfoIds": [internal_id]}, + "Activate package response: %s", ) - _LOGGER.debug("Activate package response: %s", activate_resp) - - code = activate_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - async def delete_package(self, tracking_number: str): """Delete a package by tracking number.""" - packages = await self.packages() - - try: - package = next(p for p in packages if p.tracking_number == tracking_number) - except StopIteration as err: - raise InvalidTrackingNumberError( - f"Package not found by tracking number: {tracking_number}" - ) from err + package = await self._get_package_by_tracking_number(tracking_number) internal_id = package.id _LOGGER.debug("Found internal ID of package: %s", internal_id) - delete_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "DelTrackNo", - "param": {"TrackInfoIds": [internal_id]}, - }, + await self._buyer_api_call( + "DelTrackNo", + {"TrackInfoIds": [internal_id]}, + "Delete package response: %s", ) - _LOGGER.debug("Delete package response: %s", delete_resp) - - code = delete_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - async def set_tag_type(self, internal_id: str, tag: str): """Set the tag type for an existing package.""" - tag_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "SetTrackTagType", - "param": {"tid": internal_id, "tag": tag}, - }, + await self._buyer_api_call( + "SetTrackTagType", + {"tid": internal_id, "tag": tag}, + "Set tag type response: %s", ) - _LOGGER.debug("Set tag type response: %s", tag_resp) - - code = tag_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - async def set_carrier( self, internal_id: str, first_carrier: str, second_carrier: str = "0" ): """Set the carrier(s) for an existing package.""" - carrier_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "SetTrackCarrier", - "param": { - "TrackInfoId": internal_id, - "FirstCarrier": first_carrier, - "SecondCarrier": second_carrier, - }, + await self._buyer_api_call( + "SetTrackCarrier", + { + "TrackInfoId": internal_id, + "FirstCarrier": first_carrier, + "SecondCarrier": second_carrier, }, + "Set carrier response: %s", ) - _LOGGER.debug("Set carrier response: %s", carrier_resp) - - code = carrier_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - async def track_info_by_id(self, *track_info_ids: str, isa: bool = False) -> list: """Get tracking info by internal tracking IDs.""" if not track_info_ids: return [] - track_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "GetTrackInfoById", - "param": {"isa": isa, "tids": list(track_info_ids)}, - }, + track_resp = await self._buyer_api_call( + "GetTrackInfoById", + {"isa": isa, "tids": list(track_info_ids)}, + "Track info by ID response: %s", ) - _LOGGER.debug("Track info by ID response: %s", track_resp) - - code = track_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - return track_resp.get("Json", []) async def order_info_by_id(self, internal_id: str) -> dict: """Get order info by internal tracking ID.""" - order_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "GetOrderInfoById", - "param": {"tid": internal_id}, - }, + order_resp = await self._buyer_api_call( + "GetOrderInfoById", + {"tid": internal_id}, + "Order info response: %s", ) - _LOGGER.debug("Order info response: %s", order_resp) - - code = order_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") - return order_resp.get("Json", {}).get("order", {}) async def save_order_info( @@ -364,27 +288,17 @@ async def save_order_info( ): """Save order info for an existing package.""" param: dict = {"tid": internal_id} - if opn is not None: - param["opn"] = opn - if ptoid is not None: - param["ptoid"] = ptoid - if pt is not None: - param["pt"] = pt - if otime is not None: - param["otime"] = otime - - order_resp: dict = await self._request( - "post", - API_URL_BUYER, - json={ - "version": "1.0", - "method": "SaveOrderInfo", - "param": param, - }, + optional_params = {"opn": opn, "ptoid": ptoid, "pt": pt, "otime": otime} + param.update( + { + key: value + for key, value in optional_params.items() + if value is not None + } ) - _LOGGER.debug("Save order info response: %s", order_resp) - - code = order_resp.get("Code") - if code != 0: - raise RequestError(f"Non-zero status code in response: {code}") + await self._buyer_api_call( + "SaveOrderInfo", + param, + "Save order info response: %s", + ) From 19d493cdd3e8a0793eb8e2fa058879a847d455ab Mon Sep 17 00:00:00 2001 From: 0ln <8427756+0ln@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:08:52 +0100 Subject: [PATCH 5/7] test: enhance tests by adding authenticated client fixture for package operations --- tests/test_profile.py | 317 +++++++++++------------------------------- 1 file changed, 83 insertions(+), 234 deletions(-) diff --git a/tests/test_profile.py b/tests/test_profile.py index 1c58101..c30d45c 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -2,6 +2,7 @@ import aiohttp import pytest +import pytest_asyncio from pyseventeentrack import Client from pyseventeentrack.errors import InvalidTrackingNumberError, RequestError @@ -44,6 +45,24 @@ async def test_login_success(aresponses): assert login_result is True +@pytest_asyncio.fixture +async def authenticated_client(aresponses): + """Return an authenticated client.""" + aresponses.add( + "user.17track.net", + "/user-api/v1/sign-in-by-password", + "post", + aresponses.Response( + text=load_fixture("authentication_success_response.json"), status=200 + ), + ) + + async with aiohttp.ClientSession() as session: + client = Client(session=session) + await client.profile.login(TEST_EMAIL, TEST_PASSWORD) + yield client + + @pytest.mark.asyncio async def test_no_explicit_session(aresponses): """Test not providing an explicit aiohttp ClientSession.""" @@ -476,16 +495,8 @@ async def test_archive_package_error_response(aresponses): @pytest.mark.asyncio -async def test_activate_package(aresponses): +async def test_activate_package(aresponses, authenticated_client): """Test activating a package.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -501,24 +512,13 @@ async def test_activate_package(aresponses): ), ) - async with aiohttp.ClientSession() as session: - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - res = await client.profile.activate_package("1234567890987654321") - assert res is None + res = await authenticated_client.profile.activate_package("1234567890987654321") + assert res is None @pytest.mark.asyncio -async def test_activate_package_non_existing(aresponses): +async def test_activate_package_non_existing(aresponses, authenticated_client): """Test activating a non existing package.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -534,24 +534,13 @@ async def test_activate_package_non_existing(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(InvalidTrackingNumberError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.activate_package("1234567890987654321111") + with pytest.raises(InvalidTrackingNumberError): + await authenticated_client.profile.activate_package("1234567890987654321111") @pytest.mark.asyncio -async def test_activate_package_error_response(aresponses): +async def test_activate_package_error_response(aresponses, authenticated_client): """Test activating a package with failed response.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -568,24 +557,13 @@ async def test_activate_package_error_response(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(RequestError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.activate_package("1234567890987654321") + with pytest.raises(RequestError): + await authenticated_client.profile.activate_package("1234567890987654321") @pytest.mark.asyncio -async def test_delete_package(aresponses): +async def test_delete_package(aresponses, authenticated_client): """Test deleting a package.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -599,24 +577,13 @@ async def test_delete_package(aresponses): aresponses.Response(text=load_fixture("delete_package_response.json"), status=200), ) - async with aiohttp.ClientSession() as session: - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - res = await client.profile.delete_package("1234567890987654321") - assert res is None + res = await authenticated_client.profile.delete_package("1234567890987654321") + assert res is None @pytest.mark.asyncio -async def test_delete_package_non_existing(aresponses): +async def test_delete_package_non_existing(aresponses, authenticated_client): """Test deleting a non existing package.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -630,24 +597,13 @@ async def test_delete_package_non_existing(aresponses): aresponses.Response(text=load_fixture("delete_package_response.json"), status=200), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(InvalidTrackingNumberError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.delete_package("1234567890987654321111") + with pytest.raises(InvalidTrackingNumberError): + await authenticated_client.profile.delete_package("1234567890987654321111") @pytest.mark.asyncio -async def test_delete_package_error_response(aresponses): +async def test_delete_package_error_response(aresponses, authenticated_client): """Test deleting a package with failed response.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -664,24 +620,13 @@ async def test_delete_package_error_response(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(RequestError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.delete_package("1234567890987654321") + with pytest.raises(RequestError): + await authenticated_client.profile.delete_package("1234567890987654321") @pytest.mark.asyncio -async def test_set_tag_type(aresponses): +async def test_set_tag_type(aresponses, authenticated_client): """Test setting tag type.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -689,24 +634,13 @@ async def test_set_tag_type(aresponses): aresponses.Response(text=load_fixture("set_tag_type_response.json"), status=200), ) - async with aiohttp.ClientSession() as session: - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - res = await client.profile.set_tag_type("1234567890987654321", "0") - assert res is None + res = await authenticated_client.profile.set_tag_type("1234567890987654321", "0") + assert res is None @pytest.mark.asyncio -async def test_set_tag_type_error_response(aresponses): +async def test_set_tag_type_error_response(aresponses, authenticated_client): """Test setting tag type with failed response.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -717,24 +651,13 @@ async def test_set_tag_type_error_response(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(RequestError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.set_tag_type("1234567890987654321", "0") + with pytest.raises(RequestError): + await authenticated_client.profile.set_tag_type("1234567890987654321", "0") @pytest.mark.asyncio -async def test_set_carrier(aresponses): +async def test_set_carrier(aresponses, authenticated_client): """Test setting carrier.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -742,24 +665,15 @@ async def test_set_carrier(aresponses): aresponses.Response(text=load_fixture("set_carrier_response.json"), status=200), ) - async with aiohttp.ClientSession() as session: - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - res = await client.profile.set_carrier("1234567890987654321", "100001", "0") - assert res is None + res = await authenticated_client.profile.set_carrier( + "1234567890987654321", "100001", "0" + ) + assert res is None @pytest.mark.asyncio -async def test_set_carrier_error_response(aresponses): +async def test_set_carrier_error_response(aresponses, authenticated_client): """Test setting carrier with failed response.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -769,24 +683,15 @@ async def test_set_carrier_error_response(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(RequestError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.set_carrier("1234567890987654321", "100001", "0") + with pytest.raises(RequestError): + await authenticated_client.profile.set_carrier( + "1234567890987654321", "100001", "0" + ) @pytest.mark.asyncio -async def test_track_info_by_id(aresponses): +async def test_track_info_by_id(aresponses, authenticated_client): """Test getting track info by internal ID.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -796,12 +701,9 @@ async def test_track_info_by_id(aresponses): ), ) - async with aiohttp.ClientSession() as session: - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - items = await client.profile.track_info_by_id("1234567890987654321") - assert len(items) == 1 - assert items[0]["FTrackInfoId"] == "1234567890987654321" + items = await authenticated_client.profile.track_info_by_id("1234567890987654321") + assert len(items) == 1 + assert items[0]["FTrackInfoId"] == "1234567890987654321" @pytest.mark.asyncio @@ -813,16 +715,8 @@ async def test_track_info_by_id_empty(): @pytest.mark.asyncio -async def test_track_info_by_id_error_response(aresponses): +async def test_track_info_by_id_error_response(aresponses, authenticated_client): """Test getting track info with failed response.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -832,24 +726,13 @@ async def test_track_info_by_id_error_response(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(RequestError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.track_info_by_id("1234567890987654321") + with pytest.raises(RequestError): + await authenticated_client.profile.track_info_by_id("1234567890987654321") @pytest.mark.asyncio -async def test_order_info_by_id(aresponses): +async def test_order_info_by_id(aresponses, authenticated_client): """Test getting order info by internal ID.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -859,27 +742,16 @@ async def test_order_info_by_id(aresponses): ), ) - async with aiohttp.ClientSession() as session: - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - order = await client.profile.order_info_by_id("1234567890987654321") - assert order["opn"] == "Acme" - assert order["ptoid"] == "123" - assert order["pt"] == "01" - assert order["otime"] == "2024-12-01" + order = await authenticated_client.profile.order_info_by_id("1234567890987654321") + assert order["opn"] == "Acme" + assert order["ptoid"] == "123" + assert order["pt"] == "01" + assert order["otime"] == "2024-12-01" @pytest.mark.asyncio -async def test_order_info_by_id_error_response(aresponses): +async def test_order_info_by_id_error_response(aresponses, authenticated_client): """Test getting order info with failed response.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -889,24 +761,13 @@ async def test_order_info_by_id_error_response(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(RequestError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.order_info_by_id("1234567890987654321") + with pytest.raises(RequestError): + await authenticated_client.profile.order_info_by_id("1234567890987654321") @pytest.mark.asyncio -async def test_save_order_info(aresponses): +async def test_save_order_info(aresponses, authenticated_client): """Test saving order info.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -914,30 +775,19 @@ async def test_save_order_info(aresponses): aresponses.Response(text=load_fixture("save_order_info_response.json"), status=200), ) - async with aiohttp.ClientSession() as session: - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - res = await client.profile.save_order_info( - "1234567890987654321", - opn="Acme", - ptoid="123", - pt="01", - otime="2024-12-01", - ) - assert res is None + res = await authenticated_client.profile.save_order_info( + "1234567890987654321", + opn="Acme", + ptoid="123", + pt="01", + otime="2024-12-01", + ) + assert res is None @pytest.mark.asyncio -async def test_save_order_info_error_response(aresponses): +async def test_save_order_info_error_response(aresponses, authenticated_client): """Test saving order info with failed response.""" - aresponses.add( - "user.17track.net", - "/user-api/v1/sign-in-by-password", - "post", - aresponses.Response( - text=load_fixture("authentication_success_response.json"), status=200 - ), - ) aresponses.add( "buyer.17track.net", "/orderapi/call", @@ -947,8 +797,7 @@ async def test_save_order_info_error_response(aresponses): ), ) - async with aiohttp.ClientSession() as session: - with pytest.raises(RequestError): - client = Client(session=session) - await client.profile.login(TEST_EMAIL, TEST_PASSWORD) - await client.profile.save_order_info("1234567890987654321", opn="Acme") + with pytest.raises(RequestError): + await authenticated_client.profile.save_order_info( + "1234567890987654321", opn="Acme" + ) From 4c1570235b56c5291cf7d97a3d54f92dbc42b15a Mon Sep 17 00:00:00 2001 From: 0ln <8427756+0ln@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:35:27 +0100 Subject: [PATCH 6/7] test: update coverage configuration to omit tests and examples directories --- .coveragerc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 462857c..5cf38fb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,5 +1,6 @@ [run] source = pyseventeentrack - omit = + tests/* + examples/* pyseventeentrack/track.py From 819690123f5a33f251d1ba65dec3307d19c842e8 Mon Sep 17 00:00:00 2001 From: 0ln <8427756+0ln@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:35:42 +0100 Subject: [PATCH 7/7] test: add unit test for rsa_encrypt with invalid RSA key --- tests/test_encrypt.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/test_encrypt.py diff --git a/tests/test_encrypt.py b/tests/test_encrypt.py new file mode 100644 index 0000000..cd27bc1 --- /dev/null +++ b/tests/test_encrypt.py @@ -0,0 +1,22 @@ +"""Define tests for encryption utilities.""" + +import pytest + +from pyseventeentrack import encrypt + + +def test_rsa_encrypt_invalid_key(monkeypatch): + """Test rsa_encrypt raises when key is not RSA.""" + + class DummyKey: + """Non-RSA key placeholder.""" + + def fake_load_pem_public_key(*_args, **_kwargs): + return DummyKey() + + monkeypatch.setattr( + encrypt.serialization, "load_pem_public_key", fake_load_pem_public_key + ) + + with pytest.raises(TypeError): + encrypt.rsa_encrypt("password")