diff --git a/database/factories/ProvinceFactory.php b/database/factories/ProvinceFactory.php index 767b375..891ea6e 100644 --- a/database/factories/ProvinceFactory.php +++ b/database/factories/ProvinceFactory.php @@ -17,7 +17,9 @@ class ProvinceFactory extends Factory public function definition(): array { return [ - // + 'name' => $this->faker->state, + 'lat' => $this->faker->latitude, + 'lon' => $this->faker->longitude, ]; } } diff --git a/resources/lang/fa/validation.php b/resources/lang/fa/validation.php index b92f5eb..903e525 100644 --- a/resources/lang/fa/validation.php +++ b/resources/lang/fa/validation.php @@ -204,7 +204,9 @@ return [ 'category_id' => 'موضوع', 'subcategory_id' => 'زیر موضوع', 'description' => 'توضیحات', - 'size' => 'حداکثر تعداد' + 'size' => 'حداکثر تعداد', + 'extension' => 'کد تلفن', + 'caller_id' => 'شماره تماس‌گیرنده', ], ]; diff --git a/tests/Feature/Auth/LoginTest.php b/tests/Feature/Auth/LoginTest.php new file mode 100644 index 0000000..5649fae --- /dev/null +++ b/tests/Feature/Auth/LoginTest.php @@ -0,0 +1,99 @@ +withoutExceptionHandling(); +// +// $this->artisan('passport:install'); +// +// $user = User::factory()->create(); +// +// $response = $this->postJson('/api/auth/login', [ +// 'username' => $user->username, +// 'password' => 'password', +// ]); +// +// $token = Token::where('user_id', $user->id)->first(); +// +// $response->assertStatus(200) +// ->assertJson([ +// 'message' => __('messages.successful') +// ]); +// +// $this->assertDatabaseHas('oauth_access_tokens' , [ +// 'user_id' => $user->id, +// ]); +// +// $this->assertInstanceOf(Token::class, $token); +// } + + public function test_user_can_login() + { + $user = User::factory() + ->create([ + 'password' => bcrypt('password'), + 'province_id' => Province::factory() + ]); + + $response = $this->postJson('/server/auth/login', [ + 'username' => $user->username, + 'password' => 'password', + 'telephone_id' => $this->faker->numerify('TID####') + ]); + + $response->assertOk() + ->assertJson([ + 'message' => __('messages.successful'), + ]); + + $this->assertAuthenticatedAs($user); + } + + public function test_username_is_required() + { + $response = $this->postJson('/server/auth/login', [ + 'password' => 'password', + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'username' => __('validation.required', ['attribute' => 'نام کاربری']), + ]); + } + + + public function test_password_is_required() + { + $response = $this->postJson('/server/auth/login', [ + 'username' => $this->faker->lexify('?????'), + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'password' => __('validation.required', ['attribute' => 'رمز عبور']) + ]); + } + public function test_telephone_id_is_required() + { + $response = $this->postJson('/server/auth/login', [ + 'username' => $this->faker->lexify('?????'), + 'password' => 'password', + ]); + $response->assertUnprocessable() + ->assertInvalid([ + 'telephone_id' => __('validation.required', ['attribute' => 'کد تلفن']) + ]); + } +} diff --git a/tests/Feature/Call/ListTest.php b/tests/Feature/Call/ListTest.php new file mode 100644 index 0000000..907842a --- /dev/null +++ b/tests/Feature/Call/ListTest.php @@ -0,0 +1,129 @@ +getJson(route('calls.list')); +// $response->assertStatus(401); +// } +// +// public function test_user_without_permission_cannot_access(): void +// { +// $user = User::factory()->create(); +// $this->actingAs($user); +// +// $response = $this->getJson(route('calls.list')); +// $response->assertForbidden(); +// } +// +// public function test_phone_number_must_be_string(): void +// { +// $user = User::factory() +// ->has(Permission::factory([ +// 'name' => 'manage_calls', +// 'name_fa' => 'مدیریت تماس‌ها', +// ])) +// ->create(); +// +// // 🟢 احراز هویت کاربر قبل از ارسال درخواست +// $this->actingAs($user); +// +// $response = $this->getJson(route('calls.list', [ +// 'phone_number' => $this->faker->in, +// ])); +// +// $response->assertUnprocessable() +// ->assertInvalid([ +// 'phone_number' => __('validation.string', ['attribute' => 'شماره تلفن']), +// ]); +// } +// +// +// public function test_size_must_be_integer(): void +// { +// $user = $this->createUserWithPermission(); +// $this->actingAs($user); +// +// $response = $this->getJson(route('calls.list', [ +// 'size' => 'not-an-integer', +// ])); +// +// $response->assertUnprocessable() +// ->assertInvalid([ +// 'size' => __('validation.integer', ['attribute' => 'حداکثر تعداد']), +// ]); +// } +// +// public function test_calls_are_returned_with_limit(): void +// { +// $user = $this->createUserWithPermission(); +// $this->actingAs($user); +// +// Call::factory()->count(8)->create(); +// +// $response = $this->getJson(route('calls.list', ['size' => 5])); +// +// $response->assertOk() +// ->assertJsonCount(5, 'data') +// ->assertJson(fn (AssertableJson $json) => +// $json->has('message') +// ->has('data') +// ->has('data.0', fn (AssertableJson $json) => +// $json->hasAll([ +// 'id', +// 'operator_id', +// 'operator_full_name', +// 'operator_username', +// 'telephone_id', +// 'caller_phone_number', +// 'description', +// 'category_id', +// 'category_name', +// 'subcategory_id', +// 'subcategory_name', +// 'created_at', +// ]) +// ) +// ); +// } +// +// public function test_calls_are_filtered_by_phone_number(): void +// { +// $user = $this->createUserWithPermission(); +// $this->actingAs($user); +// +// $targetPhone = '09' . $this->faker->numberBetween(100000000, 999999999); +// Call::factory()->count(4)->create(['caller_phone_number' => $targetPhone]); +// Call::factory()->count(2)->create(); // other numbers +// +// $response = $this->getJson(route('calls.list', ['phone_number' => $targetPhone])); +// +// $response->assertOk() +// ->assertJsonCount(4, 'data') +// ->assertJsonFragment(['caller_phone_number' => $targetPhone]); +// } +// +// private function createUserWithPermission(): User +// { +// return User::factory() +// ->has(Permission::factory([ +// 'name' => 'manage_calls', +// 'name_fa' => 'مدیریت تماس‌ها', +// ])) +// ->create(); +// } +//} diff --git a/tests/Feature/Call/StoreTest.php b/tests/Feature/Call/StoreTest.php new file mode 100644 index 0000000..03b2ed7 --- /dev/null +++ b/tests/Feature/Call/StoreTest.php @@ -0,0 +1,120 @@ +create(); + + $response = $this->actingAs($user)->postJson(route('calls.store')); + + $response->assertUnprocessable() + ->assertInvalid([ + 'extension' => __('validation.required', ['attribute' => 'کد تلفن']), + 'caller_id' => __('validation.required', ['attribute' => 'شماره تماس‌گیرنده']), + ]); + } + + public function test_extension_must_be_string(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->postJson(route('calls.store'), [ + 'extension' => $this->faker->numberBetween(1000, 9999), + 'caller_id' => $this->faker->numerify('09#########'), + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'extension' => __('validation.string', ['attribute' => 'کد تلفن']), + ]); + } + + public function test_extension_must_exist_in_users_table(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->postJson(route('calls.store'), [ + 'extension' => 'invalid-ext-' . $this->faker->uuid, + 'caller_id' => $this->faker->numerify('09#########'), + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'extension' => __('validation.exists', ['attribute' => 'کد تلفن']), + ]); + } + + public function test_caller_id_must_be_numeric(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->postJson(route('calls.store'), [ + 'extension' => $user->telephone_id, + 'caller_id' => $this->faker->word(), + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'caller_id' => __('validation.numeric', ['attribute' => 'شماره تماس‌گیرنده']), + ]); + } + + public function test_can_create_a_call(): void + { + $operator = User::factory()->make(); // bypass fillable + $operator->telephone_id = 'tel-' . $this->faker->unique()->numerify('###'); + $operator->save(); + + $this->artisan('db:seed --class=EventSeeder'); + + $this->mock(NotificationService::class, function (MockInterface $mock) { + $mock->shouldReceive('deliverDataToNotificationServer'); + }); + + $callerId = $this->faker->numerify('09#########'); + + $response = $this->postJson(route('calls.store'), [ + 'extension' => $operator->telephone_id, + 'caller_id' => $callerId, + ]); + + $response->assertStatus(200) + ->assertExactJson([ + 'message' => __('messages.successful'), + ]); + + $this->assertDatabaseHas('calls', [ + 'caller_phone_number' => $callerId, + 'telephone_id' => $operator->telephone_id, + 'operator_id' => $operator->id, + 'operator_username' => $operator->username, + 'operator_full_name' => $operator->full_name, + ]); + + $event = Event::query()->where('id', CallEvents::CALL_CREATED)->first(); + + $this->assertDatabaseHas('call_histories', [ + 'event_name' => $event->name, + 'event_id' => $event->id, + 'caller_phone_number' => $callerId, + 'telephone_id' => $operator->telephone_id, + ]); + } + +} diff --git a/tests/Feature/Call/UpdateTest.php b/tests/Feature/Call/UpdateTest.php new file mode 100644 index 0000000..9dc555f --- /dev/null +++ b/tests/Feature/Call/UpdateTest.php @@ -0,0 +1,208 @@ +create([ + 'telephone_id' => 'tel-' . $this->faker->unique()->numerify('###'), + ]); + + $call = Call::factory()->create([ + 'telephone_id' => $callOwner->telephone_id, + ]); + + $unauthorizedUser = User::factory()->create(); // no permission + + $this->actingAs($unauthorizedUser); + + $response = $this->postJson(route('calls.update', $call->id), [ + 'category_id' => 1, + 'subcategory_id' => 2, + 'description' => '...', + ]); + + $response->assertForbidden(); + } + + public function test_validation_fails_when_required_fields_are_missing(): void + { + $telephoneId = 'tel-' . $this->faker->unique()->numerify('###'); + + $user = User::factory() + ->has(Permission::factory([ + 'name' => 'manage_calls', + 'name_fa' => 'مدیریت تماس‌ها', + ])) + ->create([ + 'telephone_id' => $telephoneId, + ]); + + $call = Call::factory()->create([ + 'telephone_id' => $telephoneId, + ]); + + $this->actingAs($user); + + $response = $this->postJson(route('calls.update', $call->id), []); + + $response->assertUnprocessable() + ->assertInvalid([ + 'category_id' => __('validation.required', ['attribute' => 'موضوع']), + 'subcategory_id' => __('validation.required', ['attribute' => 'زیر موضوع']), + ]); + } + + public function test_category_id_and_subcategory_id_must_be_integers(): void + { + $telephoneId = 'tel-' . $this->faker->unique()->numerify('###'); + + $user = User::factory() + ->has(Permission::factory([ + 'name' => 'manage_calls', + 'name_fa' => 'مدیریت تماس‌ها', + ])) + ->create([ + 'telephone_id' => $telephoneId, + ]); + + $call = Call::factory()->create([ + 'telephone_id' => $telephoneId, + ]); + + $this->actingAs($user); + + $response = $this->postJson(route('calls.update', $call->id), [ + 'category_id' => 'not-an-int', + 'subcategory_id' => 'also-string', + 'description' => 'توضیح', + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'category_id' => __('validation.integer', ['attribute' => 'موضوع']), + 'subcategory_id' => __('validation.integer', ['attribute' => 'زیر موضوع']), + ]); + } + + public function test_description_field_must_be_string(): void + { + $telephoneId = 'tel-' . $this->faker->unique()->numerify('###'); + + $user = User::factory() + ->has(Permission::factory([ + 'name' => 'manage_calls', + 'name_fa' => 'مدیریت تماس‌ها', + ])) + ->create([ + 'telephone_id' => $telephoneId, + ]); + + $call = Call::factory()->create([ + 'telephone_id' => $telephoneId, + ]); + + $this->actingAs($user); + + $response = $this->postJson(route('calls.update', $call->id), [ + 'category_id' => 1, + 'subcategory_id' => 2, + 'description' => ['not', 'a', 'string'], + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'description' => __('validation.string', ['attribute' => 'توضیحات']), + ]); + } + + public function test_combination_of_category_and_subcategory_must_exist(): void + { + $telephoneId = 'tel-' . $this->faker->unique()->numerify('###'); + + $user = User::factory() + ->has(Permission::factory([ + 'name' => 'manage_calls', + 'name_fa' => 'مدیریت تماس‌ها', + ])) + ->create([ + 'telephone_id' => $telephoneId, + ]); + + $call = Call::factory()->create([ + 'telephone_id' => $telephoneId, + ]); + + $this->actingAs($user); + + $response = $this->postJson(route('calls.update', $call->id), [ + 'category_id' => 12345, + 'subcategory_id' => 67890, + 'description' => '...', + ]); + + $response->assertUnprocessable() + ->assertInvalid([ + 'category' => __('messages.combination_of_category_and_subcategory_does_not_exist'), + ]); + } + + public function test_operator_can_update_call_successfully(): void + { + $telephoneId = 'tel-' . $this->faker->unique()->numerify('###'); + + $user = User::factory() + ->has(Permission::factory([ + 'name' => 'manage_calls', + 'name_fa' => 'مدیریت تماس‌ها', + ])) + ->create([ + 'telephone_id' => $telephoneId, + ]); + + $call = Call::factory()->create([ + 'telephone_id' => $telephoneId, + ]); + + $category = Category::factory()->create(); + + $this->actingAs($user); + + $response = $this->postJson(route('calls.update', $call->id), [ + 'category_id' => $category->category_id, + 'subcategory_id' => $category->subcategory_id, + 'description' => 'توضیحات تستی', + ]); + + $response->assertOk() + ->assertJson([ + 'message' => __('messages.successful'), + ]); + + $this->assertDatabaseHas('calls', [ + 'id' => $call->id, + 'category_id' => $category->category_id, + 'subcategory_id' => $category->subcategory_id, + 'description' => 'توضیحات تستی', + ]); + + $this->assertDatabaseHas('call_histories', [ + 'call_id' => $call->id, + 'event_id' => CallEvents::OPERATOR_STORED_CALL_DATA, + ]); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index fe1ffc2..1b5fd39 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,9 +2,17 @@ namespace Tests; +use Database\Seeders\EventSeeder; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; abstract class TestCase extends BaseTestCase { - // + + protected function setUp(): void + { + parent::setUp(); + app()->setLocale('fa'); + config(['logging.default' => 'null']); + $this->seed(EventSeeder::class); + } }