Files
portal/app/tests/Unit/Autopodbor/ProposalClassifierTest.php
T
Дмитрий 43012c982e
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
feat(autopodbor): «Удалить» на актуализации — мягкое отклонение находки
Кнопка «Удалить» на карточках «На актуализацию» рядом с Добавить/Заменить.
Отклоняет ТОЛЬКО находку (новый сайт/адрес): фирма поля остаётся, находка → архив,
её новые ключи запоминаются у фирмы поля (новая колонка dismissed_actualize_keys jsonb).
Классификатор вычитает отклонённые ключи → та же находка больше не всплывает; реально
другой новый сайт даст новый ключ → покажется. Не глушим по имени, конкурента не убираем.

Бэк: миграция + модель-каст + ProposalClassifier (вычитание) + эндпоинт dismissActualize
+ роут. Фронт: api + store + кнопка «Удалить». Тесты: классификатор 8/8, ветка 185/185,
фронт 30/30. Canon-sync schema.sql (v8.62) — follow-up (миграция idempotent-guarded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 15:50:59 +03:00

103 lines
4.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Services\Autopodbor\AutopodborNormalizer;
use App\Services\Autopodbor\CompetitorIdentity;
use App\Services\Autopodbor\ProposalClassifier;
function classifier(): ProposalClassifier
{
return new ProposalClassifier(new CompetitorIdentity(new AutopodborNormalizer));
}
function grp(array $out, int $findId): array
{
foreach ($out as $row) {
if ($row['find']['id'] === $findId) {
return $row;
}
}
throw new RuntimeException("find $findId not classified");
}
it('новая — ни с чем не пересеклась', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => []]],
existing: [['id' => 9, 'box' => 'field', 'name' => 'B', 'site_url' => 'b.ru', 'directory_urls' => []]],
);
expect(grp($out, 1)['group'])->toBe('new');
});
it('скрытая — полный дубль активной (те же опознавалки)', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'A новое имя', 'site_url' => 'a.ru', 'directory_urls' => []]],
existing: [['id' => 9, 'box' => 'field', 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => []]],
);
expect(grp($out, 1)['group'])->toBe('hidden');
});
it('на актуализацию — совпала карточка, но новый сайт (дельта не пуста)', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'A', 'site_url' => 'a-new.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111']]],
existing: [['id' => 9, 'box' => 'field', 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111']]],
);
$r = grp($out, 1);
expect($r['group'])->toBe('actualize')
->and($r['matched_id'])->toBe(9)
->and($r['delta_keys'])->toBe(['s:a-new.ru']);
});
it('ранее удалённая — совпала только с архивом', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => []]],
existing: [['id' => 9, 'box' => 'archived', 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => []]],
);
$r = grp($out, 1);
expect($r['group'])->toBe('archived')->and($r['matched_id'])->toBe(9);
});
it('приоритет активного над архивом', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => []]],
existing: [
['id' => 8, 'box' => 'archived', 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => []],
['id' => 9, 'box' => 'field', 'name' => 'A', 'site_url' => 'a.ru', 'directory_urls' => []],
],
);
$r = grp($out, 1);
expect($r['group'])->toBe('hidden')->and($r['matched_id'])->toBe(9);
});
it('без опознавалок находка всегда новая', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'Безымянный', 'site_url' => null, 'directory_urls' => []]],
existing: [['id' => 9, 'box' => 'field', 'name' => 'Безымянный', 'site_url' => null, 'directory_urls' => []]],
);
expect(grp($out, 1)['group'])->toBe('new');
});
it('отклонённый клиентом ключ актуализации → находка уходит в hidden, а не actualize', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'A', 'site_url' => 'a-new.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111']]],
existing: [[
'id' => 9, 'box' => 'field', 'name' => 'A', 'site_url' => 'a.ru',
'directory_urls' => ['https://2gis.ru/x/firm/111'],
'dismissed_actualize_keys' => ['s:a-new.ru'], // клиент отклонил этот новый сайт
]],
);
expect(grp($out, 1)['group'])->toBe('hidden');
});
it('другой новый сайт (не отклонённый) → всё ещё actualize', function () {
$out = classifier()->classify(
finds: [['id' => 1, 'name' => 'A', 'site_url' => 'a-other.ru', 'directory_urls' => ['https://2gis.ru/x/firm/111']]],
existing: [[
'id' => 9, 'box' => 'field', 'name' => 'A', 'site_url' => 'a.ru',
'directory_urls' => ['https://2gis.ru/x/firm/111'],
'dismissed_actualize_keys' => ['s:a-new.ru'],
]],
);
expect(grp($out, 1)['group'])->toBe('actualize');
});