create(); // Ensure admin role & manage-settings permission exist $role = Role::firstOrCreate(['slug' => 'admin'], ['name' => 'Admin']); $permission = Permission::firstOrCreate(['slug' => 'manage-settings'], ['name' => 'Manage Settings']); $user->roles()->syncWithoutDetaching([$role->id]); if (method_exists($user, 'givePermissionTo')) { $user->givePermissionTo('manage-settings'); } return $user; } it('creates an sms profile and encrypts password', function () { $user = adminUserForSms(); test()->actingAs($user); $resp = test()->postJson(route('admin.sms-profiles.store'), [ 'name' => 'Primary SMS', 'active' => true, 'api_username' => 'testuser', 'api_password' => 'super-secret', ]); $resp->assertCreated(); $resp->assertJsonStructure(['profile' => ['id', 'uuid', 'name', 'active', 'api_username', 'created_at', 'updated_at']]); $profile = SmsProfile::first(); expect($profile)->not->toBeNull(); // Attribute is hidden, but verify decrypt roundtrip via model method expect($profile->decryptApiPassword())->toBe('super-secret'); }); it('queues a job to send sms and logs as sent', function () { $user = adminUserForSms(); test()->actingAs($user); $profile = SmsProfile::factory()->create([ 'api_username' => 'apiuser', 'api_password' => 'apipass', ]); $sender = SmsSender::factory()->create(['profile_id' => $profile->id]); // Bind a fake client that always returns sent with provider id app()->instance(SmsClient::class, new class implements SmsClient { public function send(App\Models\SmsProfile $profile, SmsMessage $message): SmsResult { return new SmsResult('sent', '123456'); } public function getCreditBalance(App\Models\SmsProfile $profile): int { return 0; } public function getPriceQuotes(App\Models\SmsProfile $profile): array { return []; } }); // Run the job synchronously (new SendSmsJob( profileId: $profile->id, to: '+38640111222', content: 'Hello from test', senderId: null, countryCode: null, deliveryReport: true, clientReference: null, ))->handle(app(\App\Services\Sms\SmsService::class)); $log = SmsLog::first(); expect($log)->not->toBeNull(); expect($log->status)->toBe('sent'); expect($log->provider_message_id)->toBe('123456'); }); it('returns balance and price quotes', function () { $user = adminUserForSms(); test()->actingAs($user); $profile = SmsProfile::factory()->create([ 'api_username' => 'apiuser', 'api_password' => 'apipass', ]); $base = config('services.sms.providers.smsapi_si.base_url'); $creditsUrl = rtrim($base, '/').config('services.sms.providers.smsapi_si.credits_endpoint', '/preveri-stanje-kreditov'); $priceUrl = rtrim($base, '/').config('services.sms.providers.smsapi_si.price_endpoint', '/dobi-ceno'); Http::fake([ $creditsUrl => Http::response('42', 200), $priceUrl => Http::response('0.05 EUR##0.07 EUR', 200), ]); test()->postJson(route('admin.sms-profiles.balance', $profile)) ->assertSuccessful() ->assertJson(['balance' => '42']); test()->postJson(route('admin.sms-profiles.price', $profile)) ->assertSuccessful() ->assertJson(['quotes' => ['0.05 EUR', '0.07 EUR']]); });