makeContext(withBot: true); $payload = [ 'message' => [ 'chat' => ['id' => '555111222'], 'contact' => ['phone_number' => $ctx['client']->phone], 'text' => '', ], ]; Http::fake(['*' => Http::response(['ok' => true])]); $resp = $this->withHeaders([ 'X-Telegram-Bot-Api-Secret-Token' => 'secret-' . $ctx['company']->slug, ])->postJson("/telegram/webhook/{$ctx['company']->slug}", $payload); $resp->assertOk(); $ctx['client']->refresh(); $this->assertEquals('555111222', $ctx['client']->telegram_chat_id); } public function test_webhook_rejects_wrong_secret(): void { $ctx = $this->makeContext(withBot: true); $resp = $this->withHeaders(['X-Telegram-Bot-Api-Secret-Token' => 'wrong']) ->postJson("/telegram/webhook/{$ctx['company']->slug}", ['message' => []]); $resp->assertStatus(401); } public function test_dispatcher_uses_telegram_when_chat_id_present(): void { $ctx = $this->makeContext(withBot: true); $ctx['client']->telegram_chat_id = '999'; $ctx['client']->saveQuietly(); Http::fake([ 'api.telegram.org/*' => Http::response(['ok' => true, 'result' => []]), ]); $wo = $this->makeWorkOrder($ctx); $ok = app(NotificationDispatcher::class)->workOrderReady($wo); $this->assertTrue($ok); Http::assertSent(fn ($req) => str_contains($req->url(), 'sendMessage')); } public function test_dispatcher_falls_back_to_email_when_no_chat_id(): void { $ctx = $this->makeContext(withBot: true); // No chat_id set on client. \Illuminate\Support\Facades\Mail::fake(); Http::fake(); $wo = $this->makeWorkOrder($ctx); $ok = app(NotificationDispatcher::class)->workOrderReady($wo); $this->assertTrue($ok); Http::assertNothingSent(); \Illuminate\Support\Facades\Mail::assertSent(\App\Mail\WorkOrderReadyMail::class); } public function test_dispatcher_returns_false_when_all_channels_disabled(): void { $ctx = $this->makeContext(withBot: true); $ctx['client']->email = null; $ctx['client']->saveQuietly(); $wo = $this->makeWorkOrder($ctx); $ok = app(NotificationDispatcher::class)->workOrderReady($wo); $this->assertFalse($ok); } public function test_reminder_cron_respects_cooldown(): void { $ctx = $this->makeContext(); // Set settings: after_days=1 (anything older than 1 day triggers) $ctx['company']->update(['settings' => array_merge((array) $ctx['company']->settings, [ 'reminder' => ['after_days' => 1, 'cooldown_days' => 30], ])]); $client = $ctx['client']; $vehicle = Vehicle::create([ 'client_id' => $client->id, 'make' => 'BMW', 'model' => 'X5', 'plate' => 'REM-1', ]); // Closed WO 5 days ago. WorkOrder::create([ 'number' => WorkOrder::generateNumber($ctx['company']->id), 'client_id' => $client->id, 'vehicle_id' => $vehicle->id, 'opened_at' => now()->subDays(10), 'closed_at' => now()->subDays(5), 'status' => 'done', ]); // Already sent within cooldown. ServiceReminderSent::create([ 'company_id' => $ctx['company']->id, 'vehicle_id' => $vehicle->id, 'client_id' => $client->id, 'channel' => 'email', 'type' => 'general', 'sent_at' => now()->subDays(5), ]); \Illuminate\Support\Facades\Mail::fake(); $this->artisan('reminders:send', ['--slug' => $ctx['company']->slug]) ->assertSuccessful(); \Illuminate\Support\Facades\Mail::assertNothingSent(); } private function makeContext(bool $withBot = false): array { $plan = Plan::firstOrCreate(['slug' => 'test'], [ 'name' => 'Test', 'price' => 0, 'features' => [], ]); $slug = 'tg-' . uniqid(); $settings = []; if ($withBot) { $settings['telegram'] = [ 'bot_token' => 'FAKE:TOKEN', 'webhook_secret' => "secret-{$slug}", ]; } $company = Company::create([ 'plan_id' => $plan->id, 'slug' => $slug, 'name' => 'TG Service', 'status' => 'active', 'settings' => $settings, ]); app(TenantManager::class)->setCurrent($company); $client = Client::create([ 'name' => 'Tester', 'phone' => '+37377' . random_int(100000, 999999), 'email' => 'tester@example.com', 'type' => 'individual', 'status' => 'active', ]); return compact('company', 'client'); } private function makeWorkOrder(array $ctx): WorkOrder { $vehicle = Vehicle::create([ 'client_id' => $ctx['client']->id, 'make' => 'Audi', 'model' => 'A4', 'plate' => 'TG-' . random_int(100, 999), ]); $wo = WorkOrder::create([ 'number' => WorkOrder::generateNumber($ctx['company']->id), 'client_id' => $ctx['client']->id, 'vehicle_id' => $vehicle->id, 'opened_at' => now(), 'status' => 'ready', 'total' => 250.00, ]); return $wo; } }