From 44cc8a7dbd07603f09bb82c5cd70e45a0e5a62e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 1 Jun 2026 07:18:17 +0300 Subject: [PATCH] feat(region): RossvyazPrefixLookup + RossvyazRecord DTO --- app/app/Services/Dto/RossvyazRecord.php | 20 ++++ app/app/Services/RossvyazPrefixLookup.php | 60 ++++++++++++ .../Services/RossvyazPrefixLookupTest.php | 94 +++++++++++++++++++ app/tests/fixtures/rossvyaz/sample.csv | 4 + 4 files changed, 178 insertions(+) create mode 100644 app/app/Services/Dto/RossvyazRecord.php create mode 100644 app/app/Services/RossvyazPrefixLookup.php create mode 100644 app/tests/Feature/Services/RossvyazPrefixLookupTest.php create mode 100644 app/tests/fixtures/rossvyaz/sample.csv diff --git a/app/app/Services/Dto/RossvyazRecord.php b/app/app/Services/Dto/RossvyazRecord.php new file mode 100644 index 00000000..ca4d0899 --- /dev/null +++ b/app/app/Services/Dto/RossvyazRecord.php @@ -0,0 +1,20 @@ +selectOne( + 'SELECT region, operator, subject_code + FROM phone_ranges + WHERE def_code = ? AND from_num <= ? AND to_num >= ? + ORDER BY (to_num - from_num) ASC + LIMIT 1', + [$defCode, $subscriber, $subscriber], + ); + + if ($row === null) { + return null; + } + + return new RossvyazRecord( + subjectCode: $row->subject_code !== null ? (int) $row->subject_code : null, + region: (string) $row->region, + operator: (string) $row->operator, + ); + } +} diff --git a/app/tests/Feature/Services/RossvyazPrefixLookupTest.php b/app/tests/Feature/Services/RossvyazPrefixLookupTest.php new file mode 100644 index 00000000..d49efc1e --- /dev/null +++ b/app/tests/Feature/Services/RossvyazPrefixLookupTest.php @@ -0,0 +1,94 @@ +insertGetId([ + 'source_url' => 'https://rossvyaz.gov.ru/test', + 'checksum_sha256' => str_repeat('a', 64), + 'status' => 'completed', + 'imported_at' => now(), + ]); +} + +/** + * @param array $overrides + */ +function seedPhoneRange(array $overrides = []): void +{ + DB::table('phone_ranges')->insert(array_merge([ + 'def_code' => 921, + 'from_num' => 5550000, + 'to_num' => 5559999, + 'operator' => 'МегаФон', + 'region' => 'Санкт-Петербург', + 'subject_code' => 83, + 'imported_at' => now(), + 'import_id' => seedRossvyazImport(), + ], $overrides)); +} + +it('mobile prefix returns correct region and operator', function (): void { + seedPhoneRange(); + + $rec = app(RossvyazPrefixLookup::class)->find('79215555123'); + + expect($rec)->toBeInstanceOf(RossvyazRecord::class) + ->and($rec->subjectCode)->toBe(83) + ->and($rec->region)->toBe('Санкт-Петербург') + ->and($rec->operator)->toBe('МегаФон'); +}); + +it('prefers narrower range when two ranges overlap', function (): void { + $importId = seedRossvyazImport(); + // Широкий диапазон (вся 495-зона) — Московская область (56). + seedPhoneRange([ + 'def_code' => 495, 'from_num' => 1000000, 'to_num' => 9999999, + 'operator' => 'Ростелеком', 'region' => 'Московская область', + 'subject_code' => 56, 'import_id' => $importId, + ]); + // Узкий диапазон внутри — Москва (82). Должен выиграть (ORDER BY width ASC). + seedPhoneRange([ + 'def_code' => 495, 'from_num' => 2000000, 'to_num' => 2009999, + 'operator' => 'МГТС', 'region' => 'Москва', + 'subject_code' => 82, 'import_id' => $importId, + ]); + + $rec = app(RossvyazPrefixLookup::class)->find('74952005000'); + + expect($rec)->not->toBeNull() + ->and($rec->subjectCode)->toBe(82) + ->and($rec->region)->toBe('Москва'); +}); + +it('returns null for unknown prefix', function (): void { + seedPhoneRange(); // только def_code=921 + + expect(app(RossvyazPrefixLookup::class)->find('79991234567'))->toBeNull(); +}); + +it('returns null when subscriber number is outside any range', function (): void { + seedPhoneRange(['def_code' => 921, 'from_num' => 5550000, 'to_num' => 5559999]); + + // def_code совпадает (921), но subscriber 4440000 вне [5550000, 5559999] + expect(app(RossvyazPrefixLookup::class)->find('79214440000'))->toBeNull(); +}); + +it('returns null for malformed phone', function (): void { + seedPhoneRange(); + + expect(app(RossvyazPrefixLookup::class)->find('123'))->toBeNull(); +}); diff --git a/app/tests/fixtures/rossvyaz/sample.csv b/app/tests/fixtures/rossvyaz/sample.csv new file mode 100644 index 00000000..2fae21e4 --- /dev/null +++ b/app/tests/fixtures/rossvyaz/sample.csv @@ -0,0 +1,4 @@ +АВС/ DEF;От;До;Емкость;Оператор;Регион +495;2000000;2009999;10000;ОАО МГТС;Москва +921;5550000;5559999;10000;ПАО МегаФон;Санкт-Петербург +999;0000000;0009999;10000;Тест Оператор;Атлантида