Тема
ТЗ — Google-сервисы (cms/integration-google)
Слой: 🟡 функц-модуль · Зрелость доноров: ★ · Донор: — Статус: ТЗ к разработке
Назначение и возможности
Серверные интеграции с сервисами Google: Maps (данные для блока карты), reCAPTCHA (защита форм ядра), серверные события Google Analytics 4 (Measurement Protocol), Search Console (индексация) и Merchant Center (товарный фид). Клиентская вставка GA4-счётчика — в cms/analytics, здесь только серверные вызовы API.
- Коннектор Google Maps — геокодирование адресов для блока карты;
provides: captcha-provider— reCAPTCHA для защиты форм ядра;- Серверные события GA4 (Measurement Protocol) — дополнение к клиентской GA, с явным гейтом по Consent Mode v2 (
analytics_storage) черезcms/cookie-consent; - Search Console: подтверждение владения сайтом, ping sitemap и проверка статуса индексации URL при публикации (через
SitemapRegistry/previous_slugядра); - Google Merchant Center: выгрузка и предварительная валидация товарного фида по расписанию — невалидные позиции отклоняются с отчётом, не половинчатая заливка;
- Google Ads: офлайн-конверсии по
LeadCreated(аналог Директа у Яндекса), OAuth с рефреш-токеном — Ads API не поддерживает сервис-аккаунты; - Кеширование результатов геокодирования — не бьёт в API повторно на тот же адрес;
- Единая точка настройки сервис-аккаунта и OAuth-подключений Google-сервисов.
Зависимости и выключение
requires: cms/integrations-bus · suggests: cms/analytics, cms/cookie-consent (гейт Consent Mode v2), cms/utm (обогащение атрибуции) · provides: captcha-provider
Поведение при выключении: блок карты работает на последнем закешированном геокодировании; формы ядра переключаются на резервный captcha-provider (если подключён) или работают без капчи с усиленным rate-limit; фид Merchant перестаёт обновляться (последняя валидная версия остаётся доступной); GA4-события Measurement Protocol и офлайн-конверсии Ads не отправляются — лид/заказ создаётся штатно, форма не блокируется; sitemap-пинг Search Console не выполняется — переиндексация идёт по обычному краулингу Google.
Модель данных
| Таблица | Ключевые поля | Примечание |
|---|---|---|
cms_integration_google_geocode_cache | id, address_hash, coordinates (json), cached_at | кеш геокодирования адресов |
cms_integration_google_merchant_feed_log | id, status, items_count, rejected_count, generated_at | журнал выгрузки товарного фида |
cms_integration_google_service_account | id, key_ref, scopes (json), status (enum), verified_at | ссылка на JSON-ключ сервис-аккаунта (.env) + проверенные scope |
cms_integration_google_ads_oauth | id, access_token (encrypted), refresh_token (encrypted), expires_at, status (enum), connected_by, connected_at | OAuth-подключение Google Ads (единственное, сервис-аккаунт для Ads не поддерживается) |
cms_integration_google_ads_conversions_log | id, lead_id (FK, nullable), gclid, goal_code, status (enum), sent_at | журнал офлайн-конверсий Ads |
Индексы: уникальный address_hash; coordinates — json() + cast array; status/goal_code не Enum (goal_code — строка, whitelist на уровне валидации); status — PHP Enum; key_ref/access_token/refresh_token — encrypted cast; lead_id в ads_conversions_log — constrained('cms_leads')->nullOnDelete(); журнальные таблицы (merchant_feed_log, ads_conversions_log) — append-only, BRIN по *_at.
ПДн-паспорт: gclid — идентификатор рекламного клика Google, не ПДн (сопоставление с личностью — на стороне Google); в Ads уходят только gclid + goal_code, не email/телефон/ФИО лида. Товарный фид Merchant — характеристики товара (название, цена, наличие, картинка), не данные покупателя. key_ref/токены — секреты подключения, не ПДн, шифруются и не подлежат экспорту в «выгрузить всё по субъекту».
Входные и выходные данные
Входы — всё, что не перечислено ниже, отвергается (whitelist-принцип §11 стандарта):
| Источник | Данные/поля | Чем валидируется |
|---|---|---|
GET /api/v1/integration-google/geocode | address (строка) | FormRequest: длина, whitelist символов, rate-limit по IP |
| Filament: настройка модуля | флаги включения, geocode_cache_ttl_days, geocode_timeout_ms, GA4 Measurement ID/API secret (.env-ссылки) | FormRequest, числовые поля — диапазоны |
| Загрузка JSON-ключа сервис-аккаунта (Filament) | файл ключа | проверка формата JSON + тестовый вызов API (Search Console/Merchant) при сохранении, не «сохранили и надеемся» |
OAuth callback Ads (GET .../ads/oauth/callback) | code, state от Google OAuth | state сверяется с ранее выданным (CSRF-защита) |
Событие ядра CookieConsentGiven (слушается, от cms/cookie-consent) | categories[] | Контракт события cms/cookie-consent; используется для Consent Mode v2 |
Событие ядра LeadCreated (слушается) | стандартный payload + опциональное скрытое поле формы _gclid | Контракт события ядра; _gclid не гарантирован (см. «Крайние случаи») |
Событие ядра PagePublished/ContentEntrySaved/PageDeleted (слушается) | slug, previous_slug (nullable) | Контракт события ядра (ревизия 14.07.2026) |
| Admin API: генерация фида Merchant | — (без тела) | Idempotency-Key обязателен (мутация с внешним эффектом, §7 стандарта) |
| Импорт / вебхук | — | Не применимо: входящих вебхуков от Google нет, внешние вызовы — только исходящие через cms/integrations-bus |
Выходы
| Потребитель | Данные | Формат |
|---|---|---|
| Блок карты (посетитель) | coordinates (или fallback/meta.degraded) | JSON {data, meta} |
| Filament: журнал фида/конверсий | записи журналов | keyset-пагинация {data, meta.next_cursor} |
| GA4 (Measurement Protocol) | событие конверсии + consent_state | формат Google MP, через cms/integrations-bus |
| Google Merchant Center | валидированный товарный фид | Content API формат, через cms/integrations-bus |
| Google Search Console | sitemap ping / статус индексации URL | Search Console API, через cms/integrations-bus |
| Google Ads (Offline Conversions) | gclid, goal_code, datetime | формат Ads API, через cms/integrations-bus |
| Подписчики событий | GoogleMerchantFeedGenerated, GoogleMerchantFeedRejectedItems, GoogleGeocodeFailed, GoogleServiceAccountRevoked, GoogleAdsOAuthTokenExpired, GoogleAdsConversionSent/Failed, GoogleSearchConsolePinged/Failed | payload по таблице в «События и обмен» |
Настройки (группа integration-google)
| Ключ | Тип | Дефолт | affectsPageCache | Описание |
|---|---|---|---|---|
integration-google.maps_enabled | bool | true | да | Использовать Google Maps в блоке карты |
integration-google.recaptcha_enabled | bool | false | нет | Включить reCAPTCHA на формах ядра |
integration-google.merchant_feed_enabled | bool | false | нет | Kill-switch генерации фида Merchant |
integration-google.ga4_server_events_enabled | bool | false | нет | Kill-switch серверных событий Measurement Protocol |
integration-google.search_console_ping_enabled | bool | false | нет | Kill-switch sitemap-пинга Search Console |
integration-google.ads_conversions_enabled | bool | false | нет | Kill-switch офлайн-конверсий Ads |
integration-google.geocode_cache_ttl_days | int | 90 | нет | Срок жизни кеша геокодирования |
integration-google.geocode_timeout_ms | int | 1500 | нет | Таймаут синхронного вызова геокодера (обязан быть ≤2000 мс — §9 стандарта) |
integration-google.geocode_daily_quota | int | 2500 | нет | Информационный лимит бесплатной квоты Maps Platform — сверх неё оплата по счётчику |
integration-google.consent_gate_mode | enum(strict/cookieless_ping) | strict | нет | При отсутствии analytics_storage: strict — событие не отправляется, cookieless_ping — отправляется в denied-режиме без идентификаторов (для моделирования Google) |
integration-google.merchant_feed_log_retention_days | int | 180 | нет | Хранение журнала выгрузки фида |
integration-google.ads_conversions_log_retention_days | int | 90 | нет | Хранение журнала офлайн-конверсий Ads |
Секреты (ключ API Maps, ключ reCAPTCHA, GA4 Measurement ID + API secret, JSON-ключ сервис-аккаунта, client_id/client_secret OAuth-приложения Ads) — только .env. OAuth access_token/refresh_token Ads-подключения — в БД зашифрованы.
API
| Метод | Путь | Доступ | Назначение |
|---|---|---|---|
| GET | /api/v1/integration-google/geocode | rate-limit | Геокодирование адреса для блока карты (с кешем) |
| GET | /api/v1/admin/integration-google/merchant-feed | admin (integration-google.view) | Журнал выгрузки фида (включая отчёт по отклонённым позициям) |
| POST | /api/v1/admin/integration-google/merchant-feed/generate | admin (integration-google.manage), Idempotency-Key | Ручной запуск генерации фида |
| GET | /api/v1/admin/integration-google/service-account | admin (integration-google.view) | Статус сервис-аккаунта (scope, дата проверки) |
| POST | /api/v1/admin/integration-google/service-account | admin (integration-google.oauth-manage) | Загрузка/замена JSON-ключа сервис-аккаунта |
| GET | /api/v1/admin/integration-google/ads/oauth | admin (integration-google.view) | Статус OAuth-подключения Ads |
| POST | /api/v1/admin/integration-google/ads/oauth/connect | admin (integration-google.oauth-manage) | Инициировать OAuth-подключение Ads |
| GET | /api/v1/admin/integration-google/ads/oauth/callback | admin, state-валидация | Callback OAuth, сохранение токена |
| GET | /api/v1/admin/integration-google/ads/conversions | admin (integration-google.view) | Журнал офлайн-конверсий Ads |
Все журналы — keyset-пагинация, не OFFSET. Ответ /geocode при недоступности геокодера — 200 с meta.degraded: true и закешированным/пустым результатом (конвенция деградации, ревизия ядра 14.07.2026 п.2 — применяется ко всем provides-провайдерам и внешним вызовам).
Компоненты
Блоки: карта с точками (геокодирование через сервис). Filament: журнал выгрузки фида Merchant (с отчётом об отклонённых позициях), панель сервис-аккаунта (scope, дата проверки), панель OAuth-подключения Ads, журнал офлайн-конверсий. Команды: cms:integration-google:generate-feed --json, cms:integration-google:refresh-ads-token --json, cms:integration-google:verify-service-account --json, cms:integration-google:warm-geocode-cache --json.
Демо-контент: сидер создаёт записи geocode_cache (типовые адреса) и запись в merchant_feed_log (в т.ч. один демо-пример отклонённой позиции) — блок карты и журнал фида видны в /_gallery без ручного ввода ключей API.
Фронтенд-бюджет: блок карты — ленивая загрузка JS API Maps по видимости (IntersectionObserver), не в критическом пути первой отрисовки; резерв размеров контейнера — без CLS. Источник домена Maps JS API регистрируется в CSP через канонический фильтр FilterBus security.csp (ревизия №2 ядра, п. 5); инлайн-код инициализации — с nonce ядра.
События и обмен
| Событие | Когда | Payload |
|---|---|---|
GoogleMerchantFeedGenerated | фид Merchant сгенерирован и принят | items_count, rejected_count, generated_at |
GoogleMerchantFeedRejectedItems | часть позиций фида не прошла валидацию | rejected_count, errors[] (по позиции: причина отклонения) |
GoogleGeocodeFailed | геокодирование адреса завершилось ошибкой/таймаутом | address_hash, error |
GoogleServiceAccountRevoked | сервис-аккаунт отозван/недоступен | scope — требуется загрузка нового ключа |
GoogleAdsOAuthTokenExpired | рефреш Ads-токена невозможен | — требуется ручная переавторизация |
GoogleAdsConversionSent / GoogleAdsConversionFailed | офлайн-конверсия принята/отклонена Ads | lead_id, goal_code[, error] |
GoogleSearchConsolePinged / GoogleSearchConsolePingFailed | sitemap-пинг отправлен/не удался | sitemap_url[, error] |
Реализует provides: captcha-provider (reCAPTCHA). Слушает CookieConsentGiven (гейт GA4), LeadCreated (офлайн-конверсии Ads), PagePublished/ContentEntrySaved (sitemap-пинг с учётом previous_slug). Весь вызов внешних API Google — через cms/integrations-bus (канал 4).
Таблица взаимодействий
| Сущность/модуль | Канал | Направление | Что происходит |
|---|---|---|---|
| Ядро: формы (лиды/комментарии/отзывы) | provides-контракт captcha-provider (3) | in | Ядро резолвит reCAPTCHA через DI при recaptcha_enabled=true |
cms/cookie-consent (suggests) | событие CookieConsentGiven (1) | in | Гейт отправки GA4 Measurement Protocol по категории analytics (Consent Mode v2) |
Ядро: LeadCreated | событие (1) | in | Тонкий слушатель кладёт job офлайн-конверсии Ads в очередь, если есть _gclid |
Ядро: PagePublished/ContentEntrySaved/PageDeleted | событие (1) | in | Job sitemap-пинга Search Console (использует SitemapRegistry ядра, не собственный список URL) |
cms/integrations-bus (requires) | сервис-вызов (4) | out | Все HTTP-вызовы к API Google (Maps, reCAPTCHA, GA4, Search Console, Merchant, Ads) |
cms/utm (suggests) | сервис-вызов (4, опционально) | in | Обогащение атрибуции офлайн-конверсии данными визита |
cms/health | событие (1) | out | GoogleServiceAccountRevoked/GoogleAdsOAuthTokenExpired/квоты на исходе → алерт |
cms/audit (если включён) | событие (1) | out | Фиксирует смену сервис-аккаунта, подключение Ads, генерацию фида |
Фоновая работа
Очередь integration-google:
GenerateMerchantFeed— по расписанию, валидирует каждую позицию перед отправкой (обязательные поля, схема, доступность картинки) — невалидные позиции отклоняются с построчным отчётом, валидные отправляются, частичная заливка невалидных запрещена;RefreshAdsOAuthToken— по расписанию, аналогично Yandex-механизму (см. «Крайние случаи»);VerifyServiceAccount— по расписанию, тестовый вызов Search Console/Merchant API для раннего обнаружения отзыва прав (не ждать провала боевой отправки фида);SendGA4Event— из слушателяLeadCreated/OrderCompleted(еслиcms/analyticsиздаёт свои цели), гейт поCookieConsentGiven;SendAdsConversion— из слушателяLeadCreated, идемпотентна поlead_id;PingSearchConsole— из слушателей публикации/удаления, идемпотентна поurl.
Геокодирование — единственный синхронный внешний вызов, с таймаутом geocode_timeout_ms (≤2000 мс) и graceful fallback. Ретраи с backoff и circuit breaker — на уровне cms/integrations-bus.
Эксплуатация (ранбук): метрики integration_google_geocode_requests_total, integration_google_geocode_cache_hit_ratio, integration_google_merchant_rejected_ratio, integration_google_ads_token_ttl_seconds. Алерты: GoogleServiceAccountRevoked, GoogleAdsOAuthTokenExpired, доля отклонённых позиций фида выше порога, доля GoogleGeocodeFailed выше порога за час.
| Симптом | Что проверить / команда |
|---|---|
| Карта не показывает новые адреса | maps_enabled, ключ API Maps в .env, доля GoogleGeocodeFailed за сутки |
| Фид Merchant не обновляется / много отклонений | merchant_feed_enabled, cms:integration-google:generate-feed --json, отчёт по отклонённым позициям в журнале |
| GA4-события не долетают | ga4_server_events_enabled, статус согласия analytics в cms/cookie-consent, consent_gate_mode |
| Ads-конверсии не долетают | статус OAuth Ads (cms:integration-google:refresh-ads-token --json), наличие _gclid в форме |
| Search Console не индексирует | cms:integration-google:verify-service-account --json, статус сервис-аккаунта, доступность SitemapRegistry-источников |
Бэкап/рестор: все таблицы модуля — в бэкапе. geocode_cache не требует пересоздания после рестора (не критично, только для скорости — прогревается трафиком или warm-geocode-cache); журналы — исторические данные, восстанавливаются как есть.
Производительность и кеш
Ожидаемые объёмы: типичный сайт — геокодирование единиц-десятков адресов при сохранении блока карты, публичный /geocode не гоняет геокодер на каждый визит (координаты уже сохранены); каталог с калькулятором доставки — до нескольких сотен геокодирований в сутки. Бесплатная квота Maps Platform — порядка 2500 запросов/сутки на метод в рамках месячного кредита (geocode_daily_quota, информационная настройка), сверх неё — оплата по счётчику. GA4-событий — по числу конверсий с согласием на аналитику. Merchant-фид — по размеру каталога (сотни–десятки тысяч товаров), генерация — батчами, не построчными вызовами API.
Горячие пути и бюджет запросов: GET /geocode — синхронный внешний вызов только при промахе кеша (0 внешних вызовов при попадании, 1 индексный SELECT); GA4-событие/ Ads-конверсия/sitemap-пинг — асинхронно из очереди, не в горячем пути публикации/лида (слушатель тонкий).
Почему синхронный вызов геокодера допустим (§9 стандарта: внешний HTTP на горячем пути запрещён, кроме синхронных подсказок с таймаутом и graceful fallback): вызов ограничен geocode_timeout_ms (дефолт 1500 мс, обязан быть ≤2000 мс), при таймауте — немедленный fallback на кеш/пустой результат. Все остальные внешние вызовы — строго из очереди integration-google.
Критичные индексы: уникальный address_hash на geocode_cache; lead_id на ads_conversions_log под идемпотентность job; BRIN по *_at на журнальных таблицах.
Что кешируется: тег integration-google:geocode — результаты геокодирования (TTL geocode_cache_ttl_days, инвалидация по истечению TTL). Статус сервис-аккаунта/OAuth и настройки — стандартным кешем группы (SettingsStore ядра, §6 стандарта). Журналы — не кешируются (низкочастотные admin-запросы с keyset-пагинацией).
Безопасность
- reCAPTCHA реализует контракт
captcha-provider, проверка токена на сервере перед обработкой формы — токен не проверяется на клиенте; домен виджета (google.com/gstatic.com) регистрируется модулем через канонический фильтр FilterBussecurity.csp, инлайн-инициализация — с nonce ядра, не собственным; - сервис-аккаунт vs OAuth: сервис-аккаунт (JSON-ключ) — для Search Console API и Merchant Content API (server-to-server, права выдаются вручную владельцем аккаунта Google одноразово при настройке); OAuth с рефреш-токеном — только для Google Ads API (Ads API не поддерживает сервис-аккаунты, требует пользовательского согласия);
- отзыв сервис-аккаунта на стороне Google (владелец убрал права) обнаруживается джобой
VerifyServiceAccount, не только по факту провала боевого вызова; - GA4 Measurement Protocol-события не отправляются без согласия
analytics_storage(см. «Крайние случаи», Consent Mode v2) — публичный/api/v1/analytics/eventне относится к этому модулю (владелец —cms/analytics), здесь только серверные MP-вызовы; - валидация фида Merchant перед отправкой — обязательные поля (
id,title,price,availability,image_link), схема, доступность картинки по URL; невалидные позиции отклоняются с построчным отчётом, заливка валидных не блокируется отклонёнными; - секреты (ключи API, JSON-ключ сервис-аккаунта,
client_id/client_secretAds) — только.env; OAuth-токены Ads —encryptedcast, маскируются в журналеcms/integrations-bus.
Права: integration-google.view, integration-google.manage, integration-google.oauth-manage.
Матрица ролей:
| Действие | Администратор | Менеджер | Редактор | Studio |
|---|---|---|---|---|
Просмотр журналов (фид/конверсии) — view | ✅ | ✅ | — | ✅ |
Ручной запуск генерации фида — manage | ✅ | ✅ | — | ✅ |
Загрузка ключа сервис-аккаунта / подключение Ads OAuth — oauth-manage | ✅ | — | — | ✅ |
Правка ключей API (.env) | — | — | — | ✅ (доступ к серверу) |
Kill-switch (ads_conversions_enabled, ga4_server_events_enabled, merchant_feed_enabled, search_console_ping_enabled) | ✅ | — | — | ✅ |
UX-требования
Админ: ключи API/сервис-аккаунт не заданы → понятная заглушка «интеграция не настроена — обратитесь к разработчику»; отозванный сервис-аккаунт → баннер «Сервис- аккаунт Google отклонён, загрузите новый ключ»; истёкший OAuth Ads → баннер «Требуется переавторизация Google Ads» с кнопкой «Переподключить»; отчёт по отклонённым позициям фида — скачиваемый список с причиной по каждой позиции, не просто счётчик; массовое действие в журнале конверсий — повторная отправка выбранных failed; подтверждение при замене JSON-ключа сервис-аккаунта («Действия от имени старого ключа прекратятся немедленно»).
Посетитель: недоступность карты (ключ невалиден/лимит/таймаут) → последний закешированный результат либо адрес текстом со ссылкой «открыть в Google Maps», не пустой прямоугольник; недоступность reCAPTCHA → форма отправляется без капчи с усиленным rate-limit (деградация §11 ядра), без видимой посетителю ошибки; отсутствие согласия на аналитику → сайт не показывает ошибок, GA4-события просто не уходят.
Крайние случаи и типовые баги
- Consent Mode v2: нет согласия на
analytics_storage→ GA4 Measurement Protocol- события не отправляются (consent_gate_mode=strict, дефолт) либо отправляются в denied-режиме без идентификаторов посетителя (cookieless_ping) — выбор явная настройка, не хардкод одного поведения; событиеAnalyticsGoalTriggeredотcms/analyticsпри этом всё равно фиксируется на сервере (агрегированная статистика), только внешняя отправка в GA4 гейтится; - Отсутствие
cms/cookie-consent(suggests не установлен) → без сигнала о согласииga4_server_events_enabledфактически не может быть безопасноtrue: Filament требует явного подтверждения при включении без установленногоcms/cookie-consent(«вы подтверждаете правовое основание для отправки без баннера согласия»); - Сервис-аккаунт отозван (владелец Google-аккаунта убрал права) →
VerifyServiceAccountобнаруживает это плановой проверкой, не только по провалу боевой генерации фида;GoogleServiceAccountRevoked, Search Console/Merchant приостанавливаются с понятным сообщением, geocoding и reCAPTCHA не затронуты (используют отдельные API-ключи, не сервис-аккаунт); - Истёк
refresh_tokenAds → аналогично Яндекс.Директу:GoogleAdsOAuthTokenExpired, офлайн-конверсии приостанавливаются без ошибки в остальном сайте, нужна ручная переавторизация; - Невалидные позиции в фиде Merchant (нет
gtin/битая ссылка на картинку/цена ≤ 0) → отклоняются с построчным отчётом (GoogleMerchantFeedRejectedItems), валидные позиции отправляются — частичная заливка без предупреждения запрещена; LeadCreatedбез_gclid(лид не с рекламы Google) → офлайн-конверсия не отправляется — ожидаемое поведение, не ошибка;- Квота GA4 Measurement Protocol / Search Console API исчерпана → job откладывается до следующего окна через
cms/integrations-bus, не теряется молча; - Смена схемы ответа Geocoder API (Google меняет структуру ответа) → парсер валидирует ожидаемую структуру перед извлечением координат; неожиданная —
GoogleGeocodeFailed, fallback на кеш/пустой результат, не необработанное исключение; - Гонка: двойной сабмит формы с одним
gclid→ уникальность(lead_id)вads_conversions_logпредотвращает дубль job; - Выключен
cms/integrations-bus(requires) → ни один внешний вызов не работает; geocoding деградирует на кеш, остальные функции приостанавливаются с понятным сообщением (§3 стандарта); - Измерение city/site отсутствует → кеш геокодирования и офлайн-конверсии не завязаны на
city_id/site_id, один код-путь в обоих режимах; - ⚠️ Противоречие: канонический payload
LeadCreatedне гарантирует_gclid— аналогично_yclidу Яндекса (см./cms-v2/modules/integration-yandex). Разрешение: требование к конкретной форме (скрытое поле через движок полей), не к событию ядра; оба модуля документируют один и тот же паттерн независимо, чтобы не вводить общую зависимость ради одного поля; - ⚠️ Противоречие: исходное ТЗ не задавало таймаут синхронного вызова геокодера (нарушение §9). Разрешение: настройка
geocode_timeout_ms(дефолт 1500 мс, ≤2000 мс).
Донорский код
Донор: — (новая разработка).
Легаси-импорт (§16 стандарта): не применимо — интеграция не хранит доменных данных для миграции с донора. Полезный batch-сценарий — прогрев кеша геокодирования по уже существующим адресам каталога командой cms:integration-google:warm-geocode-cache --source=<профиль> --dry-run после переезда клиента, чтобы блок карты не делал массовых синхронных вызовов геокодера в первые дни.
Тесты и приёмка
- [ ] Мок API Google Maps: геокодирование с кешированием и graceful fallback на таймаут
geocode_timeout_ms(дефолт ≤2000 мс); - [ ] reCAPTCHA реализует
captcha-provider, проверяется мок-токеном в тесте формы; - [ ] Генерация фида Merchant валидирует позиции перед отправкой: невалидные отклоняются с построчным отчётом, валидные отправляются — частичная заливка без отчёта невозможна (тест на смешанный набор позиций);
- [ ] GA4-событие не отправляется без согласия
analytics_storageприconsent_gate_mode=strict; приcookieless_pingотправляется без идентификаторов; - [ ]
VerifyServiceAccountобнаруживает отзыв прав плановой проверкой, издаётGoogleServiceAccountRevoked, не роняет боевую генерацию фида необработанным сбоем; - [ ] Рефреш OAuth Ads-токена запускается заранее; неудачный рефреш переводит подключение в
expiredи издаётGoogleAdsOAuthTokenExpired, не роняя очередь; - [ ] Офлайн-конверсия Ads отправляется только при
_gclidв лиде; повторная постановка job идемпотентна (тест на дубль); - [ ] При выключении модуля формы ядра работают без капчи либо с резервным провайдером;
- [ ] Кеш геокодирования снижает число вызовов API при повторных запросах адреса;
- [ ] Неожиданная схема ответа геокодера не роняет запрос — трактуется как
GoogleGeocodeFailed; - [ ] Права
integration-google.manage/.oauth-manageразграничивают запуск генерации фида и загрузку ключа сервис-аккаунта/подключение Ads; - [ ] Контрактный набор
cms-testingзелёный, testbench-изоляция, feature-тест на каждый роут; тестовая БД толькоintegration_google_test,migrate:fresh/refresh/resetзапрещены.