diff --git a/ProcessMaker/Contracts/CaseApiRepositoryInterface.php b/ProcessMaker/Contracts/CaseApiRepositoryInterface.php new file mode 100644 index 0000000000..b71ffdd0d2 --- /dev/null +++ b/ProcessMaker/Contracts/CaseApiRepositoryInterface.php @@ -0,0 +1,62 @@ +json([ + 'message' => $this->getMessage(), + ], JsonResponse::HTTP_UNPROCESSABLE_ENTITY); + } +} diff --git a/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php index 301457724e..ffa3056358 100644 --- a/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php +++ b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php @@ -3,65 +3,25 @@ namespace ProcessMaker\Http\Controllers\Api\V1_1; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use ProcessMaker\Http\Controllers\Controller; -use ProcessMaker\Http\Requests\GetAllCasesRequest; +use ProcessMaker\Http\Requests\CaseListRequest; use ProcessMaker\Http\Resources\V1_1\CaseResource; -use ProcessMaker\Models\CaseStarted; +use ProcessMaker\Repositories\CaseApiRepository; class CaseController extends Controller { - /** - * Default fields used in the query select statement. - */ - protected $defaultFields = [ - 'case_number', - 'user_id', - 'case_title', - 'case_title_formatted', - 'case_status', - 'processes', - 'requests', - 'request_tokens', - 'tasks', - 'participants', - 'initiated_at', - 'completed_at', - ]; - - protected $sortableFields = [ - 'case_number', - 'initiated_at', - 'completed_at', - ]; - - protected $filterableFields = [ - 'case_number', - 'case_title', - 'case_status', - 'processes', - 'requests', - 'request_tokens', - 'tasks', - 'participants', - 'initiated_at', - 'completed_at', - ]; + protected $caseRepository; - protected $searchableFields = [ - 'case_number', - 'case_title', - ]; + const DEFAULT_PAGE_SIZE = 15; - protected $dateFields = [ - 'initiated_at', - 'completed_at', - 'created_at', - 'updated_at', - ]; - - const DEFAULT_SORT_DIRECTION = 'asc'; + public function __construct(private Request $request, CaseApiRepository $caseRepository) { + $this->caseRepository = $caseRepository; + } + /* The comment block you provided is a PHPDoc block. It is used to document the purpose and usage of a method in PHP + code. In this specific block: */ /** * Get a list of all started cases. * @@ -77,128 +37,72 @@ class CaseController extends Controller * * @return array */ - public function getAllCases(GetAllCasesRequest $request): array + public function getAllCases(CaseListRequest $request): JSonResponse { - $pageSize = $request->get('pageSize', 15); - - $query = CaseStarted::select($this->defaultFields); - - $this->filters($request, $query); - - $pagination = CaseResource::collection($query->paginate($pageSize)); - - return [ - 'data' => $pagination->items(), - 'meta' => [ - 'total' => $pagination->total(), - 'perPage' => $pagination->perPage(), - 'currentPage' => $pagination->currentPage(), - 'lastPage' => $pagination->lastPage(), - ], - ]; + $query = $this->caseRepository->getAllCases($request); + return $this->paginateResponse($query); } /** - * Apply filters to the query. + * Get a list of all started cases. * * @param Request $request - * @param Builder $query - * - * @return void - */ - private function filters(Request $request, Builder $query): void - { - if ($request->has('userId')) { - $query->where('user_id', $request->get('userId')); - } - - if ($request->has('status')) { - $query->where('case_status', $request->get('status')); - } - - $this->search($request, $query); - $this->filterBy($request, $query); - $this->sortBy($request, $query); - } - - /** - * Sort the query. * - * @param Request $request: Query parameter format: sortBy=field:asc,field2:desc,... - * @param Builder $query + * @queryParam userId int Filter by user ID. + * @queryParam sortBy string Sort by field:asc,field2:desc,... + * @queryParam filterBy array Filter by field=value&field2=value2&... + * @queryParam search string Search by case number or case title. + * @queryParam pageSize int Number of items per page. + * @queryParam page int Page number. * - * @return void + * @return array */ - private function sortBy(Request $request, Builder $query): void + public function getInProgress(CaseListRequest $request): JSonResponse { - $sort = explode(',', $request->get('sortBy')); - - foreach ($sort as $value) { - if (!preg_match('/^[a-zA-Z_]+:(asc|desc)$/', $value)) { - continue; - } - - $sort = explode(':', $value); - $field = $sort[0]; - $order = $sort[1] ?? self::DEFAULT_SORT_DIRECTION; - - if (in_array($field, $this->sortableFields)) { - $query->orderBy($field, $order); - } - } + $query = $this->caseRepository->getInProgressCases($request); + return $this->paginateResponse($query); } /** - * Filter the query. + * Get a list of all started cases. * - * @param Request $request: Query parameter format: filterBy[field]=value&filterBy[field2]=value2&... - * @param Builder $query - * @param array $dateFields List of date fields in current model + * @param Request $request * - * @return void + * @queryParam userId int Filter by user ID. + * @queryParam sortBy string Sort by field:asc,field2:desc,... + * @queryParam filterBy array Filter by field=value&field2=value2&... + * @queryParam search string Search by case number or case title. + * @queryParam pageSize int Number of items per page. + * @queryParam page int Page number. + * + * @return array */ - private function filterBy(Request $request, Builder $query): void + public function getCompleted(CaseListRequest $request): JSonResponse { - if ($request->has('filterBy')) { - $filterByValue = $request->get('filterBy'); - - foreach ($filterByValue as $key => $value) { - if (!in_array($key, $this->filterableFields)) { - continue; - } - - if (in_array($key, $this->dateFields)) { - $query->whereDate($key, $value); - continue; - } - - $query->where($key, $value); - } - } + $query = $this->caseRepository->getCompletedCases($request); + return $this->paginateResponse($query); } /** - * Search by case number or case title. - - * @param Request $request: Query parameter format: search=keyword + * Handle pagination and return JSON response. + * * @param Builder $query * - * @return void + * @return JsonResponse */ - private function search(Request $request, Builder $query): void + private function paginateResponse(Builder $query): JsonResponse { - if ($request->has('search')) { - $search = $request->get('search'); + $pageSize = $this->request->get('pageSize', self::DEFAULT_PAGE_SIZE); + $pagination = CaseResource::collection($query->paginate($pageSize)); - $query->where(function ($q) use ($search) { - foreach ($this->searchableFields as $field) { - if ($field === 'case_number') { - $q->orWhere($field, $search); - } else { - $q->orWhereFullText($field, $search . '*', ['mode' => 'boolean']); - } - } - }); - } + return response()->json([ + 'data' => $pagination->items(), + 'meta' => [ + 'total' => $pagination->total(), + 'perPage' => $pagination->perPage(), + 'currentPage' => $pagination->currentPage(), + 'lastPage' => $pagination->lastPage(), + ], + ]); } } diff --git a/ProcessMaker/Http/Requests/GetAllCasesRequest.php b/ProcessMaker/Http/Requests/CaseListRequest.php similarity index 95% rename from ProcessMaker/Http/Requests/GetAllCasesRequest.php rename to ProcessMaker/Http/Requests/CaseListRequest.php index 500cb15bf7..c55e944406 100644 --- a/ProcessMaker/Http/Requests/GetAllCasesRequest.php +++ b/ProcessMaker/Http/Requests/CaseListRequest.php @@ -5,7 +5,7 @@ use Illuminate\Foundation\Http\FormRequest; use ProcessMaker\Rules\SortBy; -class GetAllCasesRequest extends FormRequest +class CaseListRequest extends FormRequest { /** * Determine if the user is authorized to make this request. diff --git a/ProcessMaker/Models/CaseParticipated.php b/ProcessMaker/Models/CaseParticipated.php new file mode 100644 index 0000000000..60b076d795 --- /dev/null +++ b/ProcessMaker/Models/CaseParticipated.php @@ -0,0 +1,55 @@ + AsArrayObject::class, + 'requests' => AsArrayObject::class, + 'request_tokens' => AsArrayObject::class, + 'tasks' => AsArrayObject::class, + 'participants' => AsArrayObject::class, + 'initiated_at' => 'datetime', + 'completed_at' => 'datetime', + ]; + + protected static function newFactory(): Factory + { + return CaseParticipatedFactory::new(); + } + + /** + * Get the user that owns the case. + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/ProcessMaker/Repositories/CaseApiRepository.php b/ProcessMaker/Repositories/CaseApiRepository.php new file mode 100644 index 0000000000..ccc6b14b92 --- /dev/null +++ b/ProcessMaker/Repositories/CaseApiRepository.php @@ -0,0 +1,212 @@ +defaultFields); + $this->applyFilters($request, $query); + return $query; + } + + /** + * Get all cases in progress + * + * @param Request $request + * + * @return Builder + */ + public function getInProgressCases(Request $request): Builder + { + $query = CaseParticipated::select($this->defaultFields) + ->where('case_status', 'IN_PROGRESS'); + $this->applyFilters($request, $query); + return $query; + } + + /** + * Get all completed cases + * + * @param Request $request + * + * @return Builder + */ + public function getCompletedCases(Request $request): Builder + { + $query = CaseParticipated::select($this->defaultFields) + ->where('case_status', 'COMPLETED'); + $this->applyFilters($request, $query); + return $query; + } + + /** + * Apply filters to the query. + * + * @param Request $request + * @param Builder $query + * + * @return void + */ + protected function applyFilters(Request $request, Builder $query): void + { + if ($request->has('userId')) { + $query->where('user_id', $request->get('userId')); + } + + if ($request->has('status')) { + $query->where('case_status', $request->get('status')); + } + + $this->search($request, $query); + $this->filterBy($request, $query); + $this->sortBy($request, $query); + } + + /** + * Search by case number or case title. + + * @param Request $request: Query parameter format: search=keyword + * @param Builder $query + * + * @return void + */ + public function search(Request $request, Builder $query): void + { + if ($request->has('search')) { + $search = $request->get('search'); + + $query->where(function ($q) use ($search) { + foreach ($this->searchableFields as $field) { + if ($field === 'case_title') { + $q->orWhereFullText($field, $search . '*', ['mode' => 'boolean']); + } else { + $q->where($field, $search); + } + } + }); + } + } + + /** + * Filter the query. + * + * @param Request $request: Query parameter format: filterBy[field]=value&filterBy[field2]=value2&... + * @param Builder $query + * @param array $dateFields List of date fields in current model + * + * @return void + */ + public function filterBy(Request $request, Builder $query): void + { + if ($request->has('filterBy')) { + $filterByValue = $request->get('filterBy'); + + foreach ($filterByValue as $key => $value) { + if (!in_array($key, $this->filterableFields)) { + throw new CaseValidationException("Filter by field $key is not allowed."); + } + + if (in_array($key, $this->dateFields)) { + $query->whereDate($key, $value); + continue; + } + + $query->where($key, $value); + } + } + } + + /** + * Sort the query. + * + * @param Request $request: Query parameter format: sortBy=field:asc,field2:desc,... + * @param Builder $query + * + * @return void + */ + public function sortBy(Request $request, Builder $query): void + { + if ($request->has('sortBy')) { + $sort = explode(',', $request->get('sortBy')); + + foreach ($sort as $value) { + $sort = explode(':', $value); + $field = $sort[0]; + $order = $sort[1] ?? self::DEFAULT_SORT_DIRECTION; + + if (!in_array($field, $this->sortableFields)) { + throw new CaseValidationException("Sort by field $field is not allowed."); + } + + $query->orderBy($field, $order); + } + } + } +} diff --git a/database/factories/CaseParticipatedFactory.php b/database/factories/CaseParticipatedFactory.php new file mode 100644 index 0000000000..c10c8b27e8 --- /dev/null +++ b/database/factories/CaseParticipatedFactory.php @@ -0,0 +1,80 @@ + + */ +class CaseParticipatedFactory extends Factory +{ + protected $model = CaseParticipated::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'user_id' => fake()->randomElement([1, 3]), + 'case_number' => fake()->unique()->randomNumber(), + 'case_title' => fake()->words(3, true), + 'case_title_formatted' => fake()->words(3, true), + 'case_status' => fake()->randomElement(['IN_PROGRESS', 'COMPLETED']), + 'processes' => array_map(function() { + return [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(2, true), + ]; + }, range(1, 3)), + 'requests' => [ + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(2, true), + 'parent_request' => fake()->randomNumber(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(3, true), + 'parent_request' => fake()->randomNumber(), + ], + ], + 'request_tokens' => fake()->randomElement([fake()->randomNumber(), fake()->randomNumber(), fake()->randomNumber()]), + 'tasks' => [ + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(4, true), + ], + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(3, true), + ], + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(2, true), + ], + ], + 'participants' => [ + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + ], + 'initiated_at' => fake()->dateTime(), + 'completed_at' => fake()->dateTime(), + 'keywords' => '', + ]; + } +} diff --git a/database/migrations/2024_09_12_172734_create_cases_participated_table.php b/database/migrations/2024_09_12_172734_create_cases_participated_table.php new file mode 100644 index 0000000000..0e28554f63 --- /dev/null +++ b/database/migrations/2024_09_12_172734_create_cases_participated_table.php @@ -0,0 +1,48 @@ +unsignedInteger('user_id'); + $table->unsignedInteger('case_number'); + $table->string('case_title', 255); + $table->text('case_title_formatted'); + $table->string('case_status', 20); + $table->json('processes'); + $table->json('requests'); + $table->json('request_tokens'); + $table->json('tasks'); + $table->json('participants'); + $table->timestamp('initiated_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + $table->text('keywords'); + + $table->primary(['user_id', 'case_number']); + $table->foreign('user_id')->references('id')->on('users'); + + $table->index(['user_id', 'case_status', 'created_at']); + $table->index(['user_id', 'case_status', 'completed_at']); + + $table->fullText('case_title'); + $table->fullText('keywords'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cases_participated'); + } +}; diff --git a/routes/v1_1/api.php b/routes/v1_1/api.php index f15b2d8f77..1c722ad5cb 100644 --- a/routes/v1_1/api.php +++ b/routes/v1_1/api.php @@ -32,6 +32,14 @@ Route::name('cases.')->prefix('cases')->group(function () { // Route to list all cases Route::get('get_all_cases', [CaseController::class, 'getAllCases']) - ->name('cases.all_cases'); + ->name('all_cases'); + + // Route to list all in-progress cases + Route::get('get_in_progress', [CaseController::class, 'getInProgress']) + ->name('in_progress'); + + // Route to list all completed cases + Route::get('get_completed', [CaseController::class, 'getCompleted']) + ->name('completed'); }); }); diff --git a/tests/Feature/Api/V1_1/CaseControllerTest.php b/tests/Feature/Api/V1_1/CaseControllerTest.php new file mode 100644 index 0000000000..f002e69643 --- /dev/null +++ b/tests/Feature/Api/V1_1/CaseControllerTest.php @@ -0,0 +1,237 @@ +create([ + 'username' => $username, + 'password' => Hash::make($password), + 'status' => $status, + ]); + } + + private function createCasesStartedForUser(int $userId, int $count = 1, $data = []) + { + return CaseStarted::factory()->count($count)->create(array_merge(['user_id' => $userId], $data)); + } + + private function createCasesParticipatedForUser(int $userId, int $count = 1, $data = []) + { + return CaseParticipated::factory()->count($count)->create(array_merge(['user_id' => $userId], $data)); + } + + public function test_get_all_cases(): void + { + $userA = $this->createUser('user_a'); + $cases = $this->createCasesStartedForUser($userA->id, 10); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases')); + $response->assertStatus(200); + $response->assertJsonCount($cases->count(), 'data'); + } + + public function test_get_in_progress(): void + { + $userA = $this->createUser('user_a'); + $cases = $this->createCasesParticipatedForUser($userA->id, 5, ['case_status' => 'IN_PROGRESS']); + + $response = $this->apiCall('GET', route('api.1.1.cases.in_progress')); + $response->assertStatus(200); + $response->assertJsonCount($cases->count(), 'data'); + $response->assertJsonFragment(['case_status' => 'IN_PROGRESS']); + $response->assertJsonMissing(['case_status' => 'COMPLETED']); + } + + public function test_get_completed(): void + { + $userA = $this->createUser('user_a'); + $cases = $this->createCasesParticipatedForUser($userA->id, 5, ['case_status' => 'COMPLETED']); + + $response = $this->apiCall('GET', route('api.1.1.cases.completed')); + $response->assertStatus(200); + $response->assertJsonCount($cases->count(), 'data'); + $response->assertJsonFragment(['case_status' => 'COMPLETED']); + $response->assertJsonMissing(['case_status' => 'IN_PROGRESS']); + } + + public function test_get_all_cases_by_users(): void + { + $userA = $this->createUser('user_a'); + $userB = $this->createUser('user_b'); + + $casesA = $this->createCasesStartedForUser($userA->id, 5, ['case_status' => 'IN_PROGRESS']); + $casesB = $this->createCasesStartedForUser($userB->id, 6, ['case_status' => 'COMPLETED']); + $casesC = $this->createCasesStartedForUser($userA->id, 4, ['case_status' => 'IN_PROGRESS']); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases')); + + $total = $casesA->count() + $casesB->count() + $casesC->count(); + $response->assertStatus(200); + $response->assertJsonCount($total, 'data'); + + $totalUserA = $casesA->count() + $casesC->count(); + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['userId' => $userA->id])); + $response->assertStatus(200); + $response->assertJsonCount($totalUserA, 'data'); + $response->assertJsonMissing(['user_id' => $userB->id]); + + $totalUserB = $casesB->count(); + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['userId' => $userB->id])); + $response->assertStatus(200); + $response->assertJsonCount($totalUserB, 'data'); + $response->assertJsonMissing(['user_id' => $userA->id]); + } + + public function test_get_all_cases_by_status(): void + { + $userA = $this->createUser('user_a'); + $userB = $this->createUser('user_b'); + + $casesA = $this->createCasesStartedForUser($userA->id, 5, ['case_status' => 'COMPLETED']); + $casesB = $this->createCasesStartedForUser($userB->id, 6, ['case_status' => 'IN_PROGRESS']); + $casesC = $this->createCasesStartedForUser($userA->id, 4, ['case_status' => 'COMPLETED']); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['status' => 'IN_PROGRESS'])); + $response->assertStatus(200); + $response->assertJsonCount($casesB->count(), 'data'); + + $totalCompleted = $casesA->count() + $casesC->count(); + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['status' => 'COMPLETED'])); + $response->assertStatus(200); + $response->assertJsonCount($totalCompleted, 'data'); + } + + public function test_get_in_progress_by_user(): void + { + $userA = $this->createUser('user_a'); + $userB = $this->createUser('user_b'); + $userC = $this->createUser('user_c'); + $casesA = $this->createCasesParticipatedForUser($userA->id, 5, ['case_status' => 'IN_PROGRESS']); + $casesB = $this->createCasesParticipatedForUser($userB->id, 6, ['case_status' => 'IN_PROGRESS']); + $casesC = $this->createCasesParticipatedForUser($userC->id, 4, ['case_status' => 'IN_PROGRESS']); + + $response = $this->apiCall('GET', route('api.1.1.cases.in_progress', ['userId' => $userA->id])); + $response->assertStatus(200); + $response->assertJsonCount($casesA->count(), 'data'); + + $response = $this->apiCall('GET', route('api.1.1.cases.in_progress', ['userId' => $userB->id])); + $response->assertStatus(200); + $response->assertJsonCount($casesB->count(), 'data'); + + $response = $this->apiCall('GET', route('api.1.1.cases.in_progress', ['userId' => $userC->id])); + $response->assertStatus(200); + $response->assertJsonCount($casesC->count(), 'data'); + } + + public function test_search_all_cases_by_case_number(): void + { + $userA = $this->createUser('user_a'); + $this->createCasesStartedForUser($userA->id, 5); + $caseNumber = 123456; + $this->createCasesStartedForUser($userA->id, 1, ['case_number' => $caseNumber]); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases')); + $response->assertStatus(200); + $response->assertJsonCount(6, 'data'); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['search' => $caseNumber])); + $response->assertStatus(200); + $response->assertJsonCount(1, 'data'); + } + + public function test_get_all_cases_sort_by_case_number(): void + { + $userA = $this->createUser('user_a'); + $cases = $this->createCasesStartedForUser($userA->id, 10); + $casesSorted = $cases->sortBy('case_number'); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['sortBy' => 'case_number:asc'])); + $response->assertStatus(200); + $response->assertJsonCount($cases->count(), 'data'); + $response->assertJsonPath('data.0.case_number', $casesSorted->first()->case_number); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['sortBy' => 'case_number:desc'])); + $response->assertStatus(200); + $response->assertJsonCount($cases->count(), 'data'); + $response->assertJsonPath('data.0.case_number', $casesSorted->last()->case_number); + } + + public function test_get_all_cases_sort_by_completed_at(): void + { + $userA = $this->createUser('user_a'); + $cases = $this->createCasesStartedForUser($userA->id, 10); + $casesSorted = $cases->sortBy('completed_at'); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['sortBy' => 'completed_at:asc'])); + $response->assertStatus(200); + $response->assertJsonCount($cases->count(), 'data'); + $response->assertJsonPath('data.0.completed_at', $casesSorted->first()->completed_at->format('Y-m-d\TH:i:s.u\Z')); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['sortBy' => 'completed_at:desc'])); + $response->assertStatus(200); + $response->assertJsonCount($cases->count(), 'data'); + $response->assertJsonPath('data.0.completed_at', $casesSorted->last()->completed_at->format('Y-m-d\TH:i:s.u\Z')); + } + + public function test_get_all_cases_sort_by_invalid_field(): void + { + $invalidField = 'invalid_field'; + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['sortBy' => $invalidField])); + $response->assertStatus(422); + $response->assertJsonPath('message', 'The sortBy must be a comma-separated list of field:asc|desc.'); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['sortBy' => "$invalidField:asc"])); + $response->assertStatus(422); + $response->assertJsonFragment(['message' => "Sort by field $invalidField is not allowed."]); + } + + public function test_get_all_cases_filter_by(): void + { + $userA = $this->createUser('user_a'); + $casesA = $this->createCasesStartedForUser($userA->id, 5); + $caseNumber = 123456; + $casesB = $this->createCasesStartedForUser($userA->id, 1, ['case_number' => $caseNumber, 'case_status' => 'IN_PROGRESS']); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases')); + $response->assertStatus(200); + $response->assertJsonCount(6, 'data'); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['filterBy' => ['case_number' => $caseNumber]])); + $response->assertStatus(200); + $response->assertJsonCount(1, 'data'); + $response->assertJsonFragment(['case_number' => $caseNumber]); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['filterBy' => ['case_status' => 'IN_PROGRESS']])); + $response->assertStatus(200); + $total = $casesA->where('case_status', 'IN_PROGRESS')->count() + $casesB->where('case_status', 'IN_PROGRESS')->count(); + $response->assertJsonCount($total, 'data'); + $response->assertJsonFragment(['case_status' => 'IN_PROGRESS']); + $response->assertJsonMissing(['case_status' => 'COMPLETED']); + } + + public function test_get_all_cases_filter_by_invalid_field(): void + { + $invalidField = 'invalid_field'; + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['filterBy' => ''])); + $response->assertStatus(422); + $response->assertJsonPath('message', 'The Filter by field must be an array.'); + + $response = $this->apiCall('GET', route('api.1.1.cases.all_cases', ['filterBy' => [$invalidField => 'value']])); + $response->assertStatus(422); + $response->assertJsonPath('message', "Filter by field $invalidField is not allowed."); + } +}