"""Unit tests for vk_sync task logic (price calc, name sanitization, orphan deletion).""" from datetime import datetime from decimal import Decimal from unittest.mock import MagicMock, call, patch import pytest from web.tasks.vk_sync import ( _build_description, _calc_price, _delete_orphans, _is_weight, _name_for_vk, ) # ── _is_weight ──────────────────────────────────────────────────────────────── @pytest.mark.parametrize("measure,expected", [ ("г", True), ("г.", True), ("гр", True), ("гр.", True), ("грамм", True), ("граммов", True), (" Г ", True), # case-insensitive, stripped ("кг", False), ("шт", False), ("л", False), (None, False), ("", False), ]) def test_is_weight(measure, expected): assert _is_weight(measure) == expected # ── _calc_price ─────────────────────────────────────────────────────────────── def test_calc_price_normal(monkeypatch): monkeypatch.setattr("web.tasks.vk_sync.settings.VK_WEIGHT_PRICE_MULTIPLIER", 10) assert _calc_price(Decimal("150"), "шт") == 15000 # 150 руб * 100 копеек def test_calc_price_weight_multiplier(monkeypatch): monkeypatch.setattr("web.tasks.vk_sync.settings.VK_WEIGHT_PRICE_MULTIPLIER", 10) # 50 руб/г → 50 * 10 (multiplier) * 100 (kopecks) = 50000 assert _calc_price(Decimal("50"), "г") == 50000 def test_calc_price_none(): assert _calc_price(None, "шт") == 0 def test_calc_price_zero(): assert _calc_price(Decimal("0"), "шт") == 0 # ── _name_for_vk ────────────────────────────────────────────────────────────── def test_name_replaces_semicolons(): assert _name_for_vk("Чай; зелёный; Китай") == "Чай, зелёный, Китай" def test_name_no_semicolons(): assert _name_for_vk("Пуэр (выдержанный)") == "Пуэр (выдержанный)" # ── _build_description ──────────────────────────────────────────────────────── def test_build_description_weight(monkeypatch): monkeypatch.setattr("web.tasks.vk_sync.settings.VK_WEIGHT_PRICE_MULTIPLIER", 10) desc = _build_description("Чай", "г", None) assert "10г" in desc assert "Чай" in desc def test_build_description_with_evo_desc(monkeypatch): monkeypatch.setattr("web.tasks.vk_sync.settings.VK_WEIGHT_PRICE_MULTIPLIER", 10) desc = _build_description("Чай", "шт", "Вкусный чай из Китая") assert "Вкусный чай из Китая" in desc def test_build_description_no_evo_desc(monkeypatch): monkeypatch.setattr("web.tasks.vk_sync.settings.VK_WEIGHT_PRICE_MULTIPLIER", 10) desc = _build_description("Чай", "шт", None) assert "Чай" in desc # ── _delete_orphans ─────────────────────────────────────────────────────────── def test_delete_orphans_removes_stale_vk_products(): from web.models.connections import VkCachedProduct, CachedProduct # Build fake VK cached products vk1 = MagicMock(spec=VkCachedProduct) vk1.vk_product_id = "111" vk1.name = "Существующий" vk2 = MagicMock(spec=VkCachedProduct) vk2.vk_product_id = "222" vk2.name = "Удалённый из Эвотор" db = MagicMock() # owned_ids contains only "111" — "222" is orphan owned_ids = {"111"} # query().filter_by().filter().all() chain query_mock = MagicMock() query_mock.filter_by.return_value.filter.return_value.all.return_value = [vk2] # second query for stale cached_products query_mock.filter.return_value.all.return_value = [] db.query.return_value = query_mock results = {"deleted": 0, "errors": 0} mock_post_resp = {"response": 1} with patch("web.tasks.vk_sync._vk_post", return_value=mock_post_resp): _delete_orphans(db, user_id=1, vk_group_id="99", owned_ids=owned_ids, token="tok", results=results) assert results["deleted"] == 1 db.delete.assert_called_once_with(vk2) def test_delete_orphans_vk_api_error_counted(): from web.models.connections import VkCachedProduct vk1 = MagicMock(spec=VkCachedProduct) vk1.vk_product_id = "999" vk1.name = "Сломанный" db = MagicMock() query_mock = MagicMock() query_mock.filter_by.return_value.filter.return_value.all.return_value = [vk1] query_mock.filter.return_value.all.return_value = [] db.query.return_value = query_mock results = {"deleted": 0, "errors": 0} with patch("web.tasks.vk_sync._vk_post", return_value={"error": {"error_code": 15}}): _delete_orphans(db, user_id=1, vk_group_id="99", owned_ids={"other"}, token="tok", results=results) assert results["deleted"] == 0 assert results["errors"] == 1 def test_delete_orphans_empty_owned_ids_deletes_all(): """If no Evotor products exist (owned_ids empty), all VK products are orphans.""" from web.models.connections import VkCachedProduct vk1 = MagicMock(spec=VkCachedProduct) vk1.vk_product_id = "1" vk1.name = "Лишний" db = MagicMock() query_mock = MagicMock() # With empty owned_ids, query without .filter() is used query_mock.filter_by.return_value.all.return_value = [vk1] query_mock.filter.return_value.all.return_value = [] db.query.return_value = query_mock results = {"deleted": 0, "errors": 0} with patch("web.tasks.vk_sync._vk_post", return_value={"response": 1}): _delete_orphans(db, user_id=1, vk_group_id="99", owned_ids=set(), token="tok", results=results) assert results["deleted"] == 1