from time import time from unittest import mock from aiodynamo.errors import ItemNotFound from anydi.testing import TestContainer from dirty_equals import IsDatetime, IsStr from starlette.testclient import TestClient from url_shortener.config import Settings from url_shortener.services import ShortUrlRedirectService def test_url_shorten_success( client: TestClient, dynamodb_client_mock: mock.MagicMock, settings: Settings, ) -> None: dynamodb_client_mock.get_item.side_effect = ItemNotFound response = client.post( "/shorten", json={ "url": "https://example.com", "domain": settings.allowed_domains[0], "additional_attributes": {"key": "value"}, }, ) assert response.status_code == 200 response_data = response.json() assert response_data == { "url": "https://example.com", "short_url": IsStr(), "additional_attributes": {"key": "value"}, "created_at": IsDatetime(iso_string=True), } def test_url_bulk_shorten_success( client: TestClient, dynamodb_client_mock: mock.MagicMock, settings: Settings, ) -> None: dynamodb_client_mock.get_item.side_effect = ItemNotFound dynamodb_client_mock.batch_write.return_value = {} response = client.post( "/bulk-shorten", json={ "url": "https://example.com", "domain": settings.allowed_domains[0], "urls_count": 2, "additional_attributes": [ {"key_1": "value_1", "key_2": "value_2"}, {"key_1": "value_1", "key_2": "value_3"}, ], }, ) assert response.status_code == 200 response_data = response.json() assert response_data == { "url": "https://example.com", "created_at": IsDatetime(iso_string=True), "short_urls": [ { "short_url": IsStr(), "additional_attributes": {"key_1": "value_1", "key_2": "value_2"}, }, { "short_url": IsStr(), "additional_attributes": {"key_1": "value_1", "key_2": "value_3"}, }, ], } def test_url_check_path_not_available( client: TestClient, dynamodb_client_mock: mock.MagicMock ) -> None: dynamodb_client_mock.get_item.return_value = { "path": "test-path", "url": "https://example.com", } response = client.get("/check-path", params={"path": "test-path"}) assert response.status_code == 200 response_data = response.json() assert response_data == {"is_available": False} def test_url_check_path_available( client: TestClient, dynamodb_client_mock: mock.MagicMock ) -> None: dynamodb_client_mock.get_item.return_value = None response = client.get("/check-path", params={"path": "test-path"}) assert response.status_code == 200 response_data = response.json() assert response_data == {"is_available": True} def test_short_url_not_found( client: TestClient, dynamodb_client_mock: mock.AsyncMock ) -> None: dynamodb_client_mock.get_item.side_effect = ItemNotFound response = client.get("/redirect/test") assert response.status_code == 404 def test_short_url_not_found_analytics_not_collected( container: TestContainer, client: TestClient, dynamodb_client_mock: mock.AsyncMock ) -> None: dynamodb_client_mock.get_item.return_value = { "path": "test-path", "url": "https://example.com", "additional_attributes": None, } service_mock = mock.MagicMock(spec=ShortUrlRedirectService) service_mock.get_redirect_url.return_value = None with container.override(ShortUrlRedirectService, service_mock): response = client.get("/redirect/test") assert response.status_code == 404 service_mock.collect_analytics.assert_not_called() def test_short_url_analytics_collected( container: TestContainer, client: TestClient ) -> None: service_mock = mock.MagicMock(spec=ShortUrlRedirectService) service_mock.get_redirect_url.return_value = { "path": "test-path", "url": "https://example.com", "created_at": 1, "additional_attributes": {"test_key": "test_value"}, "is_personalized": True, } with container.override(ShortUrlRedirectService, service_mock): response = client.get("/redirect/test-path", follow_redirects=False) assert response.status_code == 301 service_mock.collect_analytics.assert_called_once_with( client_ip=None, country_code=None, created_at=1, domain="http://testserver/", path="test-path", additional_attributes={"test_key": "test_value"}, redirect_url="https://example.com", user_agent="testclient", is_bot=False, is_personalized=True, ) def test_short_url_resolve( client: TestClient, dynamodb_client_mock: mock.AsyncMock ) -> None: dynamodb_client_mock.get_item.return_value = { "path": "test", "url": "test", "additional_attributes": None, "created_at": time(), "is_personalized": True, } response = client.get("/redirect/test", follow_redirects=False) assert response.status_code == 301 def test_allowed_domains(client: TestClient, settings: Settings) -> None: response = client.get("/allowed-domains") assert response.status_code == 200 assert response.json() == settings.allowed_domains def test_delete_paths(client: TestClient, dynamodb_client_mock: mock.AsyncMock) -> None: dynamodb_client_mock.batch_write.return_value = {} response = client.post( "/delete-paths", json={"paths": ["test-path-1", "test-path-2"]} ) assert response.status_code == 200 def test_update_path_success( client: TestClient, dynamodb_client_mock: mock.AsyncMock ) -> None: dynamodb_client_mock.get_item.return_value = {"path": "test"} dynamodb_client_mock.update_item.return_value = None response = client.patch("/paths/test-path", json={"url": "https://example.com"}) assert response.status_code == 200 def test_update_path_not_found( client: TestClient, dynamodb_client_mock: mock.AsyncMock ) -> None: dynamodb_client_mock.get_item.return_value = None dynamodb_client_mock.update_item.return_value = None response = client.patch("/paths/test-path", json={"url": "https://example.com"}) assert response.status_code == 404 def test_update_path_not_allowed_domain( client: TestClient, dynamodb_client_mock: mock.AsyncMock ) -> None: dynamodb_client_mock.get_item.return_value = None dynamodb_client_mock.update_item.return_value = None response = client.patch( "/paths/test-path", json={"domain": "not-allowed-domain.com"} ) assert response.status_code == 400