service = app(OtpService::class); } public function test_generate_code_returns_four_digit_numeric_string(): void { $code = $this->service->generateCode(); $this->assertMatchesRegularExpression('/^[1-9]\d{3}$/', $code); } public function test_send_for_phone_stores_code_with_expiry(): void { Carbon::setTestNow('2026-09-21 10:00:00'); $otpCode = $this->service->sendForPhone('09120000000'); $this->assertSame('09120000000', $otpCode->phone); $this->assertMatchesRegularExpression('/^\d{4}$/', $otpCode->code); $this->assertTrue($otpCode->expires_at->eq(Carbon::parse('2026-09-21 10:03:00'))); $this->assertDatabaseCount('otp_codes', 1); } public function test_send_for_phone_uses_custom_minutes(): void { Carbon::setTestNow('2026-09-21 10:00:00'); $otpCode = $this->service->sendForPhone('09120000000', 10); $this->assertTrue($otpCode->expires_at->eq(Carbon::parse('2026-09-21 10:10:00'))); } public function test_send_for_phone_replaces_existing_code_for_same_phone(): void { $existing = OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1111']); $otpCode = $this->service->sendForPhone('09120000000'); $this->assertTrue($otpCode->is($existing)); $this->assertDatabaseCount('otp_codes', 1); $this->assertDatabaseMissing('otp_codes', ['phone' => '09120000000', 'code' => '1111']); } public function test_send_for_phone_sends_verify_code_pattern_sms(): void { config(['services.iranpayamak.patterns.verify_code' => 'verify-pattern']); $otpCode = $this->service->sendForPhone('09120000000'); Http::assertSent(function (Request $request) use ($otpCode) { return $request['code'] === 'verify-pattern' && $request['recipient'] === '09120000000' && $request['attributes']['otp'] === $otpCode->code; }); } public function test_send_for_phone_still_stores_code_when_sms_fails(): void { Http::fake([config('services.iranpayamak.api_url') => Http::response('error', 500)]); $otpCode = $this->service->sendForPhone('09120000000'); $this->assertDatabaseHas('otp_codes', ['id' => $otpCode->id, 'phone' => '09120000000']); } public function test_verify_code_returns_true_and_deletes_valid_code(): void { OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1234']); $this->assertTrue($this->service->verifyCode('09120000000', '1234')); $this->assertDatabaseCount('otp_codes', 0); } public function test_verify_code_returns_false_for_wrong_code_and_keeps_record(): void { OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1234']); $this->assertFalse($this->service->verifyCode('09120000000', '0000')); $this->assertDatabaseCount('otp_codes', 1); } public function test_verify_code_returns_false_for_other_phone(): void { OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1234']); $this->assertFalse($this->service->verifyCode('09130000000', '1234')); $this->assertDatabaseCount('otp_codes', 1); } public function test_verify_code_returns_false_for_expired_code(): void { OtpCode::factory()->expired()->create(['phone' => '09120000000', 'code' => '1234']); $this->assertFalse($this->service->verifyCode('09120000000', '1234')); $this->assertDatabaseCount('otp_codes', 1); } public function test_verify_code_returns_false_when_code_expires_exactly_now(): void { Carbon::setTestNow('2026-09-21 10:00:00'); OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1234', 'expires_at' => now()]); $this->assertFalse($this->service->verifyCode('09120000000', '1234')); } public function test_verify_code_cannot_be_reused(): void { OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1234']); $this->service->verifyCode('09120000000', '1234'); $this->assertFalse($this->service->verifyCode('09120000000', '1234')); } public function test_exists_valid_returns_true_without_deleting_code(): void { OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1234']); $this->assertTrue($this->service->existsValid('09120000000', '1234')); $this->assertDatabaseCount('otp_codes', 1); } public function test_exists_valid_returns_false_for_wrong_code(): void { OtpCode::factory()->create(['phone' => '09120000000', 'code' => '1234']); $this->assertFalse($this->service->existsValid('09120000000', '0000')); } public function test_exists_valid_returns_false_for_expired_code(): void { OtpCode::factory()->expired()->create(['phone' => '09120000000', 'code' => '1234']); $this->assertFalse($this->service->existsValid('09120000000', '1234')); } }