(new ImitationTestCase())->seedReferenceData()); * * Schema dependencies (exact columns verified against db/schema.sql): * pricing_tiers: id, tier_no (1..7), leads_in_tier, price_per_lead_kopecks, * is_active, effective_from, created_at, updated_at * suppliers: id, code, name, accepts_types (varchar[]), cost_rub, * channel, quality_score, is_active, sort_order, created_at * * Note: suppliers (b1/b2/b3/direct) are seeded via the initial schema * migration and delta-migrations — they are expected to already exist in * the test database. This case does NOT re-seed suppliers; it only verifies * that at least one supplier row with code='b1' is present and seeds * pricing_tiers via PricingTierSeeder. * * phone_ranges are NOT seeded globally. Tests that exercise the region- * resolution cascade (Россвязь lookup) should call seedPhoneRange() directly * for the specific range their scenario requires. * * Task 0.5 — Phase 1 Portal Client Imitation Harness. * Spec: docs/superpowers/specs/2026-06-03-portal-client-imitation-phase1-design.md */ abstract class ImitationTestCase extends TestCase { use DatabaseTransactions; use SharesSupplierPdo; protected function setUp(): void { parent::setUp(); $this->seedReferenceData(); } /** * Seed shared reference data required by all imitation tests. * * Called automatically from setUp(). Safe to call multiple times within a * transaction (PricingTierSeeder uses updateOrCreate; supplier check is * read-only). */ public function seedReferenceData(): void { // Pricing tiers — required by LedgerService::chargeForDelivery. // PricingTierSeeder uses updateOrCreate so it is safe to call within // a DatabaseTransactions-wrapped test. $this->seed(PricingTierSeeder::class); // Tenant context: global bypass to allow cross-tenant reads during seeding. DB::statement("SELECT set_config('app.current_tenant_id', '0', true)"); } /** * Seed a single phone range for Россвязь prefix lookup tests. * * Only call this when your specific test scenario exercises the Россвязь * branch of LeadRegionResolver (e.g. DaData degradation tests). * * @param int $defCode DEF-code prefix (e.g. 999). * @param int $from Lower bound of number range (e.g. 0). * @param int $to Upper bound of number range (e.g. 99999). * @param int $subjectCode Subject code (1..89, порядковый, НЕ ГИБДД). * Use App\Support\RussianRegions::nameToCode() for lookup. */ protected function seedPhoneRange( int $defCode, int $from, int $to, int $subjectCode, ): void { // Anchor phone_ranges_imports row first — phone_ranges.import_id is a // required FK (migration 2026_05_31_100000). F1 fix: the previous version // used non-existent columns (range_from/range_to/region_name) and omitted // import_id, so every Россвязь-branch test that called it failed at runtime. $importId = DB::table('phone_ranges_imports')->insertGetId([ 'imported_at' => now(), 'source_url' => 'test://rossvyaz', 'rows_inserted' => 1, 'rows_updated' => 0, 'checksum_sha256' => str_repeat('0', 64), 'status' => 'completed', 'completed_at' => now(), ]); DB::table('phone_ranges')->insert([ 'def_code' => $defCode, 'from_num' => $from, 'to_num' => $to, 'operator' => 'test-operator', 'region' => RussianRegions::CODE_TO_NAME[$subjectCode] ?? 'test-region', 'region_normalized' => null, 'subject_code' => $subjectCode, 'imported_at' => now(), 'import_id' => $importId, ]); } }