"""Integration tests for /catalog routes (stores, groups, products, toggles).""" from datetime import datetime import pytest from web.models.connections import ( CachedGroup, CachedProduct, CachedStore, SyncConfig, SyncFilter, ) def _now(): return datetime.utcnow() async def _login(client, user): await client.post("/login", data={"email": user.email, "password": "testpass123"}, follow_redirects=False) def _make_store(db, user_id, evotor_id="s1", name="Магазин 1"): s = CachedStore(user_id=user_id, evotor_id=evotor_id, name=name, fetched_at=_now()) db.add(s) db.flush() return s def _make_group(db, user_id, store_id, evotor_id="g1", name="Группа 1"): g = CachedGroup(user_id=user_id, store_evotor_id=store_id, evotor_id=evotor_id, name=name, fetched_at=_now()) db.add(g) db.flush() return g def _make_product(db, user_id, store_id, group_id=None, evotor_id="p1", name="Товар 1", price=100, allow_to_sell=True): p = CachedProduct( user_id=user_id, store_evotor_id=store_id, group_evotor_id=group_id, evotor_id=evotor_id, name=name, price=price, allow_to_sell=allow_to_sell, fetched_at=_now(), ) db.add(p) db.flush() return p # ── auth guards ─────────────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_catalog_stores_requires_login(client): resp = await client.get("/catalog/stores", follow_redirects=False) assert resp.status_code == 303 assert "/login" in resp.headers["location"] # ── GET /catalog/stores ─────────────────────────────────────────────────────── @pytest.mark.asyncio async def test_catalog_stores_empty(client, active_user): await _login(client, active_user) resp = await client.get("/catalog/stores") assert resp.status_code == 200 assert "не загружены" in resp.text.lower() @pytest.mark.asyncio async def test_catalog_stores_lists_stores(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1", "Главный магазин") _make_store(override_db, active_user.id, "s2", "Второй магазин") override_db.commit() resp = await client.get("/catalog/stores") assert resp.status_code == 200 assert "Главный магазин" in resp.text assert "Второй магазин" in resp.text @pytest.mark.asyncio async def test_catalog_stores_not_shows_other_user(client, active_user, user_factory, override_db): await _login(client, active_user) other = user_factory.create() _make_store(override_db, other.id, "s-other", "Чужой магазин") override_db.commit() resp = await client.get("/catalog/stores") assert "Чужой магазин" not in resp.text # ── GET /catalog/stores/{id}/groups ────────────────────────────────────────── @pytest.mark.asyncio async def test_catalog_groups_shows_product_count(client, active_user, override_db): await _login(client, active_user) store = _make_store(override_db, active_user.id, "s1") group = _make_group(override_db, active_user.id, "s1", "g1", "Чай") _make_product(override_db, active_user.id, "s1", "g1", "p1", "Пуэр") _make_product(override_db, active_user.id, "s1", "g1", "p2", "Улун") override_db.commit() resp = await client.get(f"/catalog/stores/s1/groups") assert resp.status_code == 200 assert "Чай" in resp.text assert "2" in resp.text # product count @pytest.mark.asyncio async def test_catalog_groups_zero_count_for_empty_group(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_group(override_db, active_user.id, "s1", "g1", "Пустая группа") override_db.commit() resp = await client.get("/catalog/stores/s1/groups") assert resp.status_code == 200 assert "Пустая группа" in resp.text assert "0" in resp.text @pytest.mark.asyncio async def test_catalog_groups_unknown_store_redirects(client, active_user): await _login(client, active_user) resp = await client.get("/catalog/stores/no-such-store/groups", follow_redirects=False) assert resp.status_code == 303 # ── GET /catalog/stores/{id}/products ──────────────────────────────────────── @pytest.mark.asyncio async def test_catalog_products_shows_all(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_group(override_db, active_user.id, "s1", "g1") _make_product(override_db, active_user.id, "s1", "g1", "p1", "Пуэр") _make_product(override_db, active_user.id, "s1", "g1", "p2", "Улун") override_db.commit() resp = await client.get("/catalog/stores/s1/products") assert resp.status_code == 200 assert "Пуэр" in resp.text assert "Улун" in resp.text @pytest.mark.asyncio async def test_catalog_products_filtered_by_group(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_group(override_db, active_user.id, "s1", "g1", "Группа А") _make_group(override_db, active_user.id, "s1", "g2", "Группа Б") _make_product(override_db, active_user.id, "s1", "g1", "p1", "Товар А") _make_product(override_db, active_user.id, "s1", "g2", "p2", "Товар Б") override_db.commit() resp = await client.get("/catalog/stores/s1/products?group=g1") assert resp.status_code == 200 assert "Товар А" in resp.text assert "Товар Б" not in resp.text @pytest.mark.asyncio async def test_catalog_products_shows_group_column(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_group(override_db, active_user.id, "s1", "g1", "МояГруппа") _make_product(override_db, active_user.id, "s1", "g1", "p1", "Товар") override_db.commit() resp = await client.get("/catalog/stores/s1/products") assert "МояГруппа" in resp.text @pytest.mark.asyncio async def test_catalog_products_ungrouped_shown(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_product(override_db, active_user.id, "s1", None, "p1", "Без группы") override_db.commit() resp = await client.get("/catalog/stores/s1/products") assert "Без группы" in resp.text # ── POST /catalog/stores/{id}/toggle ───────────────────────────────────────── @pytest.mark.asyncio async def test_store_toggle_first_disable_seeds_others(client, active_user, override_db): """First toggle on a store disables it by seeding include-filters for all other stores.""" await _login(client, active_user) _make_store(override_db, active_user.id, "s1", "Магазин 1") _make_store(override_db, active_user.id, "s2", "Магазин 2") _make_store(override_db, active_user.id, "s3", "Магазин 3") override_db.commit() resp = await client.post("/catalog/stores/s1/toggle", follow_redirects=False) assert resp.status_code == 303 cfg = override_db.query(SyncConfig).filter_by(user_id=active_user.id).first() filters = override_db.query(SyncFilter).filter_by( sync_config_id=cfg.id, entity_type="store", filter_mode="include" ).all() ids = {f.entity_id for f in filters} # s1 was toggled off → only s2 and s3 are in include list assert "s1" not in ids assert "s2" in ids assert "s3" in ids @pytest.mark.asyncio async def test_store_toggle_re_enable(client, active_user, override_db): """Toggling a disabled store re-adds it to the include list.""" await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_store(override_db, active_user.id, "s2") override_db.commit() # Disable s1 first await client.post("/catalog/stores/s1/toggle") # Now re-enable s1 await client.post("/catalog/stores/s1/toggle") cfg = override_db.query(SyncConfig).filter_by(user_id=active_user.id).first() filters = override_db.query(SyncFilter).filter_by( sync_config_id=cfg.id, entity_type="store", filter_mode="include" ).all() ids = {f.entity_id for f in filters} assert "s1" in ids @pytest.mark.asyncio async def test_store_toggle_requires_login(client): resp = await client.post("/catalog/stores/s1/toggle", follow_redirects=False) assert resp.status_code == 303 assert "/login" in resp.headers["location"] # ── POST /catalog/stores/{id}/groups/{gid}/toggle ──────────────────────────── @pytest.mark.asyncio async def test_group_toggle_first_disable(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_group(override_db, active_user.id, "s1", "g1", "Группа 1") _make_group(override_db, active_user.id, "s1", "g2", "Группа 2") override_db.commit() resp = await client.post("/catalog/stores/s1/groups/g1/toggle", follow_redirects=False) assert resp.status_code == 303 cfg = override_db.query(SyncConfig).filter_by(user_id=active_user.id).first() filters = override_db.query(SyncFilter).filter_by( sync_config_id=cfg.id, entity_type="group", filter_mode="include", parent_entity_id="s1", ).all() ids = {f.entity_id for f in filters} assert "g1" not in ids assert "g2" in ids @pytest.mark.asyncio async def test_group_toggle_re_enable(client, active_user, override_db): await _login(client, active_user) _make_store(override_db, active_user.id, "s1") _make_group(override_db, active_user.id, "s1", "g1") _make_group(override_db, active_user.id, "s1", "g2") override_db.commit() await client.post("/catalog/stores/s1/groups/g1/toggle") await client.post("/catalog/stores/s1/groups/g1/toggle") cfg = override_db.query(SyncConfig).filter_by(user_id=active_user.id).first() filters = override_db.query(SyncFilter).filter_by( sync_config_id=cfg.id, entity_type="group", parent_entity_id="s1" ).all() ids = {f.entity_id for f in filters} assert "g1" in ids