--- /dev/null
+<?php
+
+namespace tests\unit\services;
+
+use Codeception\Test\Unit;
+use yii_app\services\MarketplaceService;
+
+/**
+ * Тесты парсинга адреса доставки из текстовой строки FlowWow/YM.
+ *
+ * Покрывает:
+ * - MarketplaceService::parseAddressFromDeliveryText()
+ * - Различные форматы delivery-строк из прода
+ * - Edge cases: пустые строки, отсутствие адреса, сюрприз-доставка
+ *
+ * @group marketplace
+ * @group delivery
+ */
+class MarketplaceServiceDeliveryParsingTest extends Unit
+{
+ /**
+ * Полный адрес: город, улица, дом
+ * Пример из прода: заказ #4409
+ */
+ public function testParseFullAddress_CityStreetHouse(): void
+ {
+ $text = 'Доставка: 8 марта 2026 в 10:30—12:30, Нижний Новгород, улица Героев Советского Союза Костьева, 19, Кв 41 этаж 1 подъезд 3';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ $this->assertNotEquals('Уточняется', $result['city'], 'Город должен быть распознан');
+ $this->assertNotEquals('Уточняется', $result['street'], 'Улица должна быть распознана');
+ $this->assertNotEquals('Уточняется', $result['house'], 'Дом должен быть распознан');
+ }
+
+ /**
+ * Адрес без улицы: "Уточните адрес доставки у получателя"
+ * Пример из прода: заказ #4408
+ */
+ public function testParseNoAddress_AskRecipient(): void
+ {
+ $text = 'Доставка: сегодня, 27 февраля 2026 в 17:30—18:00, Уточните адрес доставки у получателя';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ $this->assertEquals('Уточняется', $result['city']);
+ $this->assertEquals('Уточняется', $result['street']);
+ $this->assertEquals('Уточняется', $result['house']);
+ }
+
+ /**
+ * Сюрприз-доставка: адрес будет позже через SMS
+ * Пример из прода: заказы #4459, #4460, #4461, #4464
+ */
+ public function testParseSurpriseDelivery_SmsLater(): void
+ {
+ $text = 'Доставка: Отправим смс получателю 8 марта в 07:30, чтобы узнать данные для доставки Пожалуйста, не звоните раньше, чтобы сохранить сюрприз';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ // Адреса нет — всё "Уточняется"
+ $this->assertEquals('Уточняется', $result['street']);
+ $this->assertEquals('Уточняется', $result['house']);
+ }
+
+ /**
+ * Пустая строка
+ */
+ public function testParseEmptyString(): void
+ {
+ $result = MarketplaceService::parseAddressFromDeliveryText('');
+
+ $this->assertEquals('Уточняется', $result['city']);
+ $this->assertEquals('Уточняется', $result['street']);
+ $this->assertEquals('Уточняется', $result['house']);
+ $this->assertEquals(0.0, $result['latitude']);
+ $this->assertEquals(0.0, $result['longitude']);
+ }
+
+ /**
+ * Только "Доставка:" без адреса
+ */
+ public function testParseDeliveryPrefixOnly(): void
+ {
+ $result = MarketplaceService::parseAddressFromDeliveryText('Доставка:');
+
+ $this->assertEquals('Уточняется', $result['street']);
+ }
+
+ /**
+ * GPS-координаты для Нижнего Новгорода
+ */
+ public function testParseNizhnyNovgorod_HasGps(): void
+ {
+ $text = 'Доставка: 5 марта 2026 в 12:00—14:00, Нижний Новгород, ул. Ленина, 10';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ $this->assertGreaterThan(0, $result['latitude'], 'Широта должна быть заполнена для НН');
+ $this->assertGreaterThan(0, $result['longitude'], 'Долгота должна быть заполнена для НН');
+ }
+
+ /**
+ * GPS-координаты для Москвы
+ */
+ public function testParseMoscow_HasGps(): void
+ {
+ $text = 'Доставка: 5 марта 2026 в 12:00—14:00, Москва, Тверская, 1';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ $this->assertGreaterThan(0, $result['latitude'], 'Широта должна быть заполнена для Москвы');
+ $this->assertGreaterThan(0, $result['longitude'], 'Долгота должна быть заполнена для Москвы');
+ }
+
+ /**
+ * Неизвестный город — GPS = 0
+ */
+ public function testParseUnknownCity_ZeroGps(): void
+ {
+ $text = 'Доставка: 5 марта 2026 в 12:00—14:00, Саратов, ул. Мира, 5';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ $this->assertEquals(0.0, $result['latitude']);
+ $this->assertEquals(0.0, $result['longitude']);
+ }
+
+ /**
+ * Самовывоз
+ */
+ public function testParsePickup(): void
+ {
+ $text = 'Самовывоз: 5 марта 2026 в 12:00—14:00, Нижний Новгород, ул. Речная, 22';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ $this->assertNotEquals('Уточняется', $result['street'], 'Улица должна быть распознана для самовывоза');
+ }
+
+ /**
+ * Результат всегда содержит все 5 ключей
+ */
+ public function testParseResult_AlwaysHasAllKeys(): void
+ {
+ $result = MarketplaceService::parseAddressFromDeliveryText('любой текст');
+
+ $this->assertArrayHasKey('city', $result);
+ $this->assertArrayHasKey('street', $result);
+ $this->assertArrayHasKey('house', $result);
+ $this->assertArrayHasKey('latitude', $result);
+ $this->assertArrayHasKey('longitude', $result);
+ }
+
+ /**
+ * Все значения — строки или float, никогда не null
+ */
+ public function testParseResult_NeverReturnsNull(): void
+ {
+ $inputs = [
+ '',
+ 'Доставка:',
+ 'Доставка: сегодня',
+ 'random text',
+ 'Доставка: Отправим смс получателю',
+ ];
+
+ foreach ($inputs as $input) {
+ $result = MarketplaceService::parseAddressFromDeliveryText($input);
+
+ $this->assertNotNull($result['city'], "city не должен быть null для: '$input'");
+ $this->assertNotNull($result['street'], "street не должен быть null для: '$input'");
+ $this->assertNotNull($result['house'], "house не должен быть null для: '$input'");
+ $this->assertNotNull($result['latitude'], "latitude не должен быть null для: '$input'");
+ $this->assertNotNull($result['longitude'], "longitude не должен быть null для: '$input'");
+ }
+ }
+
+ /**
+ * Город с двумя словами парсится корректно
+ */
+ public function testParseTwoWordCity(): void
+ {
+ $text = 'Доставка: 5 марта 2026 в 10:00—12:00, Нижний Новгород, Большая Покровская, 1';
+
+ $result = MarketplaceService::parseAddressFromDeliveryText($text);
+
+ // Должен распознать хотя бы что-то кроме "Уточняется"
+ $this->assertNotEquals('Уточняется', $result['street']);
+ }
+}