From e746f29dd01023d7e5a5f41bd82a27ab93af88dc Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker Date: Wed, 25 Sep 2024 16:23:41 -0400 Subject: [PATCH 1/7] feature/FOUR-19267 --- .../Api/ProcessRequestController.php | 15 +++++++++++ routes/api.php | 1 + tests/Feature/Api/ProcessRequestsTest.php | 27 +++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index 29f6df3b0e..fb1f06069e 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -839,4 +839,19 @@ public function endEventDestination(ProcessRequest $request) return response()->json(['message' => __('End event found'), 'data' => $data]); } + + /** + * This endpoint returns requests by case number + * + * @param Request $request + * + * @return ApiCollection + */ + public function getRequestsByCase(Request $request) + { + $case_number = $request->input('case_number', 0); + $response = ProcessRequest::where('case_number', $case_number)->get(); + + return new ApiCollection($response); + } } diff --git a/routes/api.php b/routes/api.php index 0c85364ffd..f32d2b8c2a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -215,6 +215,7 @@ // Cases //Route::get('cases', [ProcessRequestController::class, 'index'])->name('cases.index'); + Route::get('requests-by-case', [ProcessRequestController::class, 'getRequestsByCase'])->name('requests.getRequestsByCase'); // Requests Route::get('requests', [ProcessRequestController::class, 'index'])->name('requests.index'); // Already filtered in controller Route::get('requests/{process}/count', [ProcessRequestController::class, 'getCount'])->name('requests.count'); diff --git a/tests/Feature/Api/ProcessRequestsTest.php b/tests/Feature/Api/ProcessRequestsTest.php index bc6c837089..a574be1843 100644 --- a/tests/Feature/Api/ProcessRequestsTest.php +++ b/tests/Feature/Api/ProcessRequestsTest.php @@ -986,4 +986,31 @@ public function testScreenRequested() $data = $response->json()['data']; $this->assertEmpty($data); } + + + /** + * Get a list of Requests by Cases. + */ + public function testRequestByCase() + { + $case_number = 10; + + ProcessRequest::factory()->count(10)->create([ + 'case_number' => $case_number, + ]); + + $response = $this->apiCall('GET', `requests-by-case?case_number=` . $case_number); + + //Validate the header status code + $response->assertStatus(200); + + // Verify structure + $response->assertJsonStructure([ + 'data' => ['*' => self::STRUCTURE], + 'meta', + ]); + + // Verify count + $this->assertEquals(10, $response->json()['meta']['total']); + } } From d9927ffa0e968a7c028f9cee3466b2d1e181ebf1 Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker Date: Thu, 26 Sep 2024 10:23:59 -0400 Subject: [PATCH 2/7] feature/FOUR-19267 --- .../Api/ProcessRequestController.php | 23 ++++++++++++++++++- tests/Feature/Api/ProcessRequestsTest.php | 15 ++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index fb1f06069e..b73c22c964 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -849,9 +849,30 @@ public function endEventDestination(ProcessRequest $request) */ public function getRequestsByCase(Request $request) { + $user = Auth::user(); + $case_number = $request->input('case_number', 0); - $response = ProcessRequest::where('case_number', $case_number)->get(); + $query = ProcessRequest::forUser($user); + $includes = $request->input('include', ''); + foreach (array_filter(explode(',', $includes)) as $include) { + if (in_array($include, ProcessRequest::$allowedIncludes)) { + $query->with($include); + } + } + + $response = $query->where('case_number', $case_number) + ->orderBy( + str_ireplace('.', '->', $request->input('order_by', 'name')), + $request->input('order_direction', 'ASC') + ) + ->select('process_requests.*') + ->withAggregate('processVersion', 'alternative') + ->paginate($request->input('per_page', 10)); + $response = $response->map(function ($processRequest) use ($request) { + return new ProcessRequestResource($processRequest); + }); + return new ApiCollection($response); } } diff --git a/tests/Feature/Api/ProcessRequestsTest.php b/tests/Feature/Api/ProcessRequestsTest.php index a574be1843..fb62b042ce 100644 --- a/tests/Feature/Api/ProcessRequestsTest.php +++ b/tests/Feature/Api/ProcessRequestsTest.php @@ -986,20 +986,21 @@ public function testScreenRequested() $data = $response->json()['data']; $this->assertEmpty($data); } - - + /** * Get a list of Requests by Cases. */ public function testRequestByCase() { - $case_number = 10; - - ProcessRequest::factory()->count(10)->create([ - 'case_number' => $case_number, + ProcessRequest::query()->delete(); + $request = ProcessRequest::factory()->create(); + ProcessRequest::factory()->count(9)->create([ + 'parent_request_id' => $request->id, ]); - $response = $this->apiCall('GET', `requests-by-case?case_number=` . $case_number); + $url = '/requests-by-case?case_number=' . $request->case_number; + + $response = $this->apiCall('GET', $url); //Validate the header status code $response->assertStatus(200); From 2f1bb240828da8c42e3a3d317fde6405f86fbfdf Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker Date: Thu, 26 Sep 2024 16:00:14 -0400 Subject: [PATCH 3/7] feature/FOUR-19267 --- .../Api/ProcessRequestController.php | 42 +- ProcessMaker/Models/ProcessRequest.php | 32 + tests/Feature/Api/ProcessRequestsTest.php | 954 +----------------- 3 files changed, 66 insertions(+), 962 deletions(-) diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index b204012aa8..ec59b145af 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -847,32 +847,32 @@ public function endEventDestination(ProcessRequest $request) * * @return ApiCollection */ - public function getRequestsByCase(Request $request) + public function getRequestsByCase(Request $request, User $user = null) { - $user = Auth::user(); - - $case_number = $request->input('case_number', 0); - $query = ProcessRequest::forUser($user); - $includes = $request->input('include', ''); - foreach (array_filter(explode(',', $includes)) as $include) { - if (in_array($include, ProcessRequest::$allowedIncludes)) { - $query->with($include); - } + if (!$user) { + $user = Auth::user(); } - if ($advancedFilter = $request->input('advanced_filter', '')) { - Filter::filter($query, $advancedFilter); - } + // Validate the inputs, including optional ones + $request->validate([ + 'case_number' => 'required|integer', + 'status' => 'nullable|string|in:ACTIVE,COMPLETED,ERROR,CANCELED', + 'order_by' => 'nullable|string|in:id,name,status,user_id,initiated_at,participants', + 'order_direction' => 'nullable|string|in:asc,desc', + 'page' => 'nullable|integer|min:1', + 'per_page' => 'nullable|integer', + ]); + + $query = ProcessRequest::forUser($user); + + // Filter by case_number + $query->filterByCaseNumber($request); - $response = $query->where('case_number', $case_number) - ->orderBy( - str_ireplace('.', '->', $request->input('order_by', 'name')), - $request->input('order_direction', 'ASC') - ) - ->select('process_requests.*') - ->withAggregate('processVersion', 'alternative') - ->paginate($request->input('per_page', 10)); + // Apply ordering only if a valid order_by field is provided + $query->applyOrdering($request); + $response = $query->applyPagination($request); + // Get activeTasks and participants $response = $response->map(function ($processRequest) use ($request) { return new ProcessRequestResource($processRequest); }); diff --git a/ProcessMaker/Models/ProcessRequest.php b/ProcessMaker/Models/ProcessRequest.php index 8b83e3a498..3679ff9f3c 100644 --- a/ProcessMaker/Models/ProcessRequest.php +++ b/ProcessMaker/Models/ProcessRequest.php @@ -1089,4 +1089,36 @@ public function getElementDestination(): ?array return $endEvents->first()->elementDestination; } + + /** + * Scope apply order + */ + public function scopeApplyOrdering($query, $request) + { + $orderBy = $request->input('order_by', 'name'); + $orderDirection = $request->input('order_direction', 'asc'); + + return $query->orderBy($orderBy, $orderDirection); + } + + /** + * Scope apply pagination + */ + public function scopeApplyPagination($query, $request) + { + $page = $request->input('page', 1); + $perPage = $request->input('per_page', 10); + + return $query->paginate($perPage); + } + + /** + * Scope to filter by case_number through the processRequest relationship + */ + public function scopeFilterByCaseNumber($query, $request) + { + $caseNumber = $request->input('case_number'); + + return $query->where('case_number', $caseNumber); + } } diff --git a/tests/Feature/Api/ProcessRequestsTest.php b/tests/Feature/Api/ProcessRequestsTest.php index fb62b042ce..dcf4a13906 100644 --- a/tests/Feature/Api/ProcessRequestsTest.php +++ b/tests/Feature/Api/ProcessRequestsTest.php @@ -44,768 +44,21 @@ class ProcessRequestsTest extends TestCase 'created_at', 'updated_at', ]; - - /** - * Get a list of Requests without query parameters. - */ - public function testListRequest() - { - ProcessRequest::query()->delete(); - - ProcessRequest::factory()->count(10)->create(); - - $response = $this->apiCall('GET', self::API_TEST_URL); - - //Validate the header status code - $response->assertStatus(200); - - // Verify structure - $response->assertJsonStructure([ - 'data' => ['*' => self::STRUCTURE], - 'meta', - ]); - - // Verify count - $this->assertEquals(10, $response->json()['meta']['total']); - } - - /** - * Count the total of request per process - */ - public function testCountRequest() - { - ProcessRequest::query()->delete(); - $process = Process::factory()->create(); - ProcessRequest::factory()->count(10)->create([ - 'process_id' => $process->id, - ]); - - $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $process->id . '/count'); - - // Validate the header status code - $response->assertStatus(200); - - // Verify structure - $response->assertJsonStructure([ - 'meta', - ]); - - // Verify count - $this->assertEquals(10, $response->json()['meta']['total']); - } - - /** - * Test to verify that the list dates are in the correct format (yyyy-mm-dd H:i+GMT) - */ - public function testScreenListDates() - { - $name = 'testRequestTimezone'; - $newEntity = ProcessRequest::factory()->create(['name' => $name]); - $route = self::API_TEST_URL . '?filter=' . $name; - $response = $this->apiCall('GET', $route); - - $this->assertEquals( - $newEntity->updated_at->format('c'), - $response->getData()->data[0]->updated_at - ); - - $this->assertEquals( - $newEntity->created_at->format('c'), - $response->getData()->data[0]->created_at - ); - } - - /** - * Get a list of Request with parameters - */ - public function testListRequestIncludingData() - { - $requestname = 'mytestrequestnameincludesdata'; - - //Create requests with data - ProcessRequest::factory()->create([ - 'name' => $requestname, - 'data' => ['test' => 'value1'], - ]); - - //Set direction to ascending - $query = "?page=1&include=data&order_by=data.test&order_direction=ASC&filter=$requestname"; - $response = $this->apiCall('GET', self::API_TEST_URL . $query); - - $response->assertStatus(200); - $response->assertJsonFragment(['data' => ['test' => 'value1']]); - } - - /** - * Get a list of Request with parameters - */ - public function testListRequestOrderByData() - { - $requestname = 'mytestrequestnameorderbydata'; - - //Create requests with data - ProcessRequest::factory()->create([ - 'name' => $requestname, - 'data' => ['test' => 'value1'], - ]); - - ProcessRequest::factory()->create([ - 'name' => $requestname, - 'data' => ['test' => 'value2'], - ]); - - //Set direction to ascending - $query = "?page=1&include=data&order_by=data.test&order_direction=ASC&filter=$requestname"; - $response = $this->apiCall('GET', self::API_TEST_URL . $query); - - //Verify that the request with test data of "value1" is first - $response->assertStatus(200); - $this->assertEquals('value1', $response->json()['data'][0]['data']['test']); - - //Set direction to descending - $query = "?page=1&include=data&order_by=data.test&order_direction=DESC&filter=$requestname"; - $response = $this->apiCall('GET', self::API_TEST_URL . $query); - - //Verify that the request with test data of "value2" is first - $response->assertStatus(200); - $this->assertEquals('value2', $response->json()['data'][0]['data']['test']); - } - - /** - * Get a list of Request with parameters - */ - public function testListRequestWithQueryParameter() - { - $requestname = 'mytestrequestname'; - - ProcessRequest::factory()->create([ - 'name' => $requestname, - ]); - - //List Request with filter option - $perPage = Faker::create()->randomDigitNotNull; - $query = '?page=1&per_page=' . $perPage . '&order_by=name&order_direction=DESC&filter=' . $requestname; - $response = $this->apiCall('GET', self::API_TEST_URL . $query); - - //Validate the header status code - $response->assertStatus(200); - - //verify structure paginate - $response->assertJsonStructure([ - 'data', - 'meta', - ]); - - // Verify return data - $this->assertEquals(1, $response->json()['meta']['total']); - $this->assertEquals('name', $response->json()['meta']['sort_by']); - $this->assertEquals('DESC', $response->json()['meta']['sort_order']); - } - - /** - * Test that we can filter by participant - */ - public function testFilterByParticipant() - { - $participant = User::factory()->create(); - $otherUser = User::factory()->create(); - - $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); - $otherRequest = ProcessRequest::factory()->create(['status' => 'ACTIVE']); - - $token = ProcessRequestToken::factory()->create([ - 'process_request_id' => $request->id, - 'user_id' => $participant->id, - ]); - - $otherToken = ProcessRequestToken::factory()->create([ - 'process_request_id' => $otherRequest->id, - 'user_id' => $otherUser->id, - ]); - - $response = $this->apiCall('GET', self::API_TEST_URL, ['pmql' => "participant = \"{$participant->username}\""]); - $this->assertEquals(1, $response->json()['meta']['total']); - $this->assertEquals($request->id, $response->json()['data'][0]['id']); - } - - /** - * Test that paged values are returned as expected - */ - public function testWithPagination() - { - $process = Process::factory()->create(); - ProcessRequest::factory()->count(5)->create([ - 'name' => $process->name, - 'process_id' => $process->id, - ]); - $query = '?page=2&per_page=3&order_by=name'; - - $response = $this->apiCall('GET', self::API_TEST_URL . $query); - - //Validate the header status code - $response->assertStatus(200); - $json = $response->json(); - - // 2 items on 2nd page - $this->assertCount(2, $json['data']); - - // keys should be re-indexed so they get converted to - // arrays instead of objects on json_encode - $this->assertEquals([0, 1], array_keys($json['data'])); - } - - /** - * Get a list of Request by type - */ - public function testListRequestWithType() - { - $in_progress = ProcessRequest::factory()->create([ - 'status' => 'ACTIVE', - 'user_id' => $this->user->id, - ]); - - $completed = ProcessRequest::factory()->create([ - 'status' => 'COMPLETED', - 'user_id' => $this->user->id, - ]); - - $response = $this->apiCall('GET', self::API_TEST_URL . '/?type=completed'); - $json = $response->json(); - $this->assertCount(1, $json['data']); - $this->assertEquals($completed->id, $json['data'][0]['id']); - - $response = $this->apiCall('GET', self::API_TEST_URL . '/?type=in_progress'); - $json = $response->json(); - $this->assertCount(1, $json['data']); - $this->assertEquals($in_progress->id, $json['data'][0]['id']); - } - - /** - * Get a list of Request with assocations included - */ - public function testListRequestWithIncludes() - { - $process = Process::factory()->create(); - - ProcessRequest::factory()->create([ - 'process_id' => $process->id, - ]); - - $response = $this->apiCall('GET', self::API_TEST_URL . '/?include=process'); - $json = $response->json(); - $this->assertEquals($process->id, $json['data'][0]['process']['id']); - } - - /** - * Get a list of requests with a user that has view all permission - */ - public function testListRequestViewAllPermission() - { - $this->user = User::factory()->create(['status'=>'ACTIVE']); - $processRequest = ProcessRequest::factory()->create([]); - - $response = $this->apiCall('GET', self::API_TEST_URL); - $json = $response->json(); - $this->assertCount(0, $json['data']); - - $this->user->giveDirectPermission('view-all_requests'); - $this->user->refresh(); - - $response = $this->apiCall('GET', self::API_TEST_URL); - $json = $response->json(); - $this->assertEquals($processRequest->id, $json['data'][0]['id']); - } - - /** - * Get a request - */ - public function testGetRequest() - { - //get the id from the factory - $request = ProcessRequest::factory()->create()->id; - - //load api - $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $request); - - //Validate the status is correct - $response->assertStatus(200); - - //verify structure - $response->assertJsonStructure(self::STRUCTURE); - } - - /** - * Parameters required for update of request - */ - public function testUpdateProcessRequestParametersRequired() - { - $id = ProcessRequest::factory()->create(['name' => 'mytestrequestname'])->id; - //The post must have the required parameters - $url = self::API_TEST_URL . '/' . $id; - - $response = $this->apiCall('PUT', $url, [ - 'name' => null, - ]); - - //Validate the header status code - $response->assertStatus(422); - } - - /** - * Update request in process - */ - public function testUpdateProcessRequest() - { - $faker = Faker::create(); - - $url = self::API_TEST_URL . '/' . ProcessRequest::factory()->create()->id; - - //Load the starting request data - $verify = $this->apiCall('GET', $url); - - //Post saved success - $response = $this->apiCall('PUT', $url, [ - 'name' => $faker->unique()->name(), - 'data' => ['test' => 1], - 'process_id' => json_decode($verify->getContent())->process_id, - ]); - - //Validate the header status code - $response->assertStatus(204); - - //Load the updated request data - $verify_new = $this->apiCall('GET', $url); - - //Check that it has changed - $this->assertNotEquals($verify, $verify_new); - } - - /** - * Check that the validation wont allow duplicate requestnames - */ - public function testUpdateProcessRequestTitleExists() - { - $request1 = ProcessRequest::factory()->create([ - 'name' => 'MyRequestName', - ]); - - $request2 = ProcessRequest::factory()->create(); - - $url = self::API_TEST_URL . '/' . $request2->id; - - $response = $this->apiCall('PUT', $url, [ - 'name' => 'MyRequestName', - ]); - //Validate the header status code - $response->assertStatus(422); - $response->assertSeeText('The Name has already been taken'); - } - - /** - * test to be sure that you cannot cancel a request until you have been given permission - */ - public function testCancelRequestWithPermissions() - { - // We need an admin user and a non-admin user - $admin = $this->user; - - $nonAdmin = User::factory()->create([ - 'is_administrator' => false, - ]); - - // Create a single process in order to create - // two process requests with the same process - $process = Process::factory()->create([ - 'user_id' => $admin->id, - ]); - - // Create the initial process request - $initialProcessVersionRequest = ProcessRequest::factory()->create([ - 'user_id' => $nonAdmin->id, - 'process_id' => $process->id, - ]); - - // Attempt to cancel a request - $this->user = $nonAdmin; - $route = route('api.requests.update', [$initialProcessVersionRequest->id]); - $response = $this->apiCall('PUT', $route, ['status' => 'CANCELED']); - - // Confirm the user does not have access - $response->assertStatus(403); - - // Add the user to the list of users that can cancel - $this->user = $admin; - $route = route('api.processes.update', [$process->id]); - $response = $this->apiCall('PUT', $route, [ - 'name' => 'Update Process', - 'description' => 'Update Test', - 'cancel_request' => ['users' => [$nonAdmin->id], 'groups' => []], - ]); - - // Create a second process request with the - // same process, this time the process request - // will honor the new process configuration - $secondProcessVersionRequest = ProcessRequest::factory()->create([ - 'user_id' => $nonAdmin->id, - 'process_id' => $process->id, - ]); - - // Attempt to cancel a request - $this->user = $nonAdmin; - $route = route('api.requests.update', [$secondProcessVersionRequest->id]); - $response = $this->apiCall('PUT', $route, ['status' => 'CANCELED']); - - // Assert that the API updated - $response->assertStatus(204); - } - - /** - * Test ability to complete a request if it has the status: ERROR - */ - public function testCompleteRequest() - { - $this->user->is_administrator = false; - $this->user->saveOrFail(); - $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); - - // give the user editData permission to get past the route check - $request->processVersion->usersCanEditData()->sync([ - $this->user->id => [ - 'method' => 'EDIT_DATA', - 'process_id' => $request->process->id, - ], - ]); - - $route = route('api.requests.update', [$request->id]); - $response = $this->apiCall('PUT', $route, ['status' => 'COMPLETED']); - - // Confirm the user does not have access - $response->assertStatus(403); - $this->assertEquals('Not authorized to complete this request.', $response->json()['message']); - - // Make user admin again - $this->user->is_administrator = true; - $this->user->saveOrFail(); - $response = $this->apiCall('PUT', $route, ['status' => 'COMPLETED']); - - // Confirm only ERROR status can be changed - $response->assertStatus(422); - $this->assertEquals('Only requests with status: ERROR can be manually completed', - $response->json()['errors']['status'][0] - ); - - $request->status = 'ERROR'; - $request->saveOrFail(); - - $response = $this->apiCall('PUT', $route, ['status' => 'COMPLETED']); - $response->assertStatus(204); - - $request->refresh(); - $this->assertEquals('COMPLETED', $request->status); - - // Verify comment added - $this->assertEquals( - $this->user->fullname . ' manually completed the request from an error state', - Comment::first()->body - ); - - // Verify metadata was removed from data object - $this->assertFalse(array_key_exists('_user', $request->data)); - } - - /** - * Delete request and request tokens in parent process, and children processes - */ - public function testDeleteParentProcessRequestShouldRemoveRequestAndTokensForParentAndChildren() - { - $user = User::factory()->create(); - // Prepare data - $parentProcessRequest = ProcessRequest::factory()->create(['status' => 'ACTIVE']); - - $childProcessRequest1 = ProcessRequest::factory()->create([ - 'status' => 'ACTIVE', - 'parent_request_id' => $parentProcessRequest->id, - ]); - - $childProcessRequest2 = ProcessRequest::factory()->create([ - 'status' => 'ACTIVE', - 'parent_request_id' => $childProcessRequest1->id, - ]); - - $parentTokens = ProcessRequestToken::factory()->count(3)->create([ - 'process_request_id' => $parentProcessRequest->id, - ]); - - $childTokens1 = ProcessRequestToken::factory()->count(4)->create([ - 'process_request_id' => $childProcessRequest1->id, - ]); - - $childTokens2 = ProcessRequestToken::factory()->count(5)->create([ - 'process_request_id' => $childProcessRequest2->id, - ]); - - // Assert database has parent and child requests - $this->assertEquals(1, ProcessRequest::where('id', $parentProcessRequest->id)->count()); - $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest1->id)->count()); - $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest2->id)->count()); - - // Assert count database has tokens for parent and child requests - $this->assertEquals(3, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); - $this->assertEquals(4, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); - $this->assertEquals(5, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); - - //Remove request - $url = self::API_TEST_URL . '/' . $parentProcessRequest->id; - $response = $this->apiCall('DELETE', $url); - - //Validate the header status code - $response->assertStatus(204); - - // Assert database does not has parent and child requests - $this->assertEquals(0, ProcessRequest::where('id', $parentProcessRequest->id)->count()); - $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest1->id)->count()); - $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest2->id)->count()); - - // Assert database does not has parent and child requests tokens - $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); - $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); - $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); - } - - /** - * Delete request and request tokens in subprocess and child process - */ - public function testDeleteChildProcessRequestShouldRemoveRequestAndTokensForChildren() - { - $user = User::factory()->create(); - // Prepare data - $parentProcessRequest = ProcessRequest::factory()->create(['status' => 'ACTIVE']); - - $childProcessRequest1 = ProcessRequest::factory()->create([ - 'status' => 'ACTIVE', - 'parent_request_id' => $parentProcessRequest->id, - ]); - - $childProcessRequest2 = ProcessRequest::factory()->create([ - 'status' => 'ACTIVE', - 'parent_request_id' => $childProcessRequest1->id, - ]); - - $parentTokens = ProcessRequestToken::factory()->count(3)->create([ - 'process_request_id' => $parentProcessRequest->id, - ]); - - $childTokens1 = ProcessRequestToken::factory()->count(4)->create([ - 'process_request_id' => $childProcessRequest1->id, - ]); - - $childTokens2 = ProcessRequestToken::factory()->count(5)->create([ - 'process_request_id' => $childProcessRequest2->id, - ]); - - // Assert database has parent and child requests - $this->assertEquals(1, ProcessRequest::where('id', $parentProcessRequest->id)->count()); - $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest1->id)->count()); - $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest2->id)->count()); - - // Assert count database has tokens for parent and child requests - $this->assertEquals(3, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); - $this->assertEquals(4, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); - $this->assertEquals(5, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); - - //Remove request - $url = self::API_TEST_URL . '/' . $childProcessRequest1->id; - $response = $this->apiCall('DELETE', $url); - - //Validate the header status code - $response->assertStatus(204); - - // Assert database does not has parent and child requests - $this->assertEquals(1, ProcessRequest::where('id', $parentProcessRequest->id)->count()); - $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest1->id)->count()); - $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest2->id)->count()); - - // Assert database does not has parent and child requests tokens - $this->assertEquals(3, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); - $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); - $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); - } - + /** - * The request does not exist in process + * Get a list of Requests by Cases. */ - public function testDeleteProcessRequestNotExist() - { - //ProcessRequest not exist - $url = self::API_TEST_URL . '/' . ProcessRequest::factory()->make()->id; - $response = $this->apiCall('DELETE', $url); - - //Validate the header status code - $response->assertStatus(405); - } - - public function testListCanceledProcessRequests() + public function testRequestByCase() { ProcessRequest::query()->delete(); - - ProcessRequest::factory()->count(5)->create(['status' => 'ACTIVE']); - ProcessRequest::factory()->count(3)->create(['status' => 'COMPLETED']); - ProcessRequest::factory()->count(1)->create(['status' => 'CANCELED']); - - // The list of requests should show just ACTIVE requests - $response = $this->apiCall('GET', self::API_TEST_URL . '?type=in_progress'); - $this->assertEquals(5, $response->json()['meta']['total']); - - // The list of completed does NOT include CANCELED requests - $response = $this->apiCall('GET', self::API_TEST_URL . '?type=completed'); - $this->assertEquals(3, $response->json()['meta']['total']); - - // The list of all requests includes everything - $response = $this->apiCall('GET', self::API_TEST_URL . '?type=all'); - $this->assertEquals(9, $response->json()['meta']['total']); - } - - /** - * Verifies that a file uploaded in a request can be downloaded - */ - public function testFileDownload() - { - // We create a fake file to upload - $testFileName = 'test.txt'; - Storage::fake('public'); - $fileUpload = UploadedFile::fake()->create($testFileName, 1); - - // Create a request $request = ProcessRequest::factory()->create(); - - // Crate a user without administrator privileges - $user = User::factory()->create([ - 'status' => 'ACTIVE', - 'is_administrator' => false, - ]); - - // Add the file to the request - $addedMedia = $request - ->addMedia($fileUpload) - ->withCustomProperties(['data_name' => 'test']) - ->toMediaCollection('local'); - - $route = self::API_TEST_URL . '/' . $request->id . '/files/' . $addedMedia->id; - $response = $this->apiCall('GET', $route); - - // Validate the header status code - $response->assertStatus(200); - - // Verify that a file with the fake file is downloaded - $this->assertEquals($testFileName, $response->getFile()->getFileName()); - } - - public function testParticipantPermissionsToView() - { - $participant = User::factory()->create(); - $otherUser = User::factory()->create(); - - $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); - $token = ProcessRequestToken::factory()->create([ - 'process_request_id' => $request->id, - 'user_id' => $participant->id, - ]); - - $url = route('api.requests.show', $request); - $this->user = $otherUser; - - $response = $this->apiCall('get', $url); - $response->assertStatus(403); - - $this->user = $participant; - - $response = $this->apiCall('get', $url); - $response->assertStatus(200); - - // Participant can still see the data when completed - $request->update(['status' => 'COMPLETED']); - $response = $this->apiCall('get', $url); - $response->assertStatus(200); - } - - public function testUserCanEditCompletedData() - { - $this->user = User::factory()->create(); - $process = Process::factory()->create(); - - $initialProcessRequest = ProcessRequest::factory()->create([ - 'status' => 'COMPLETED', - 'data' => ['foo' => 'bar'], - 'process_id' => $process->id, - ]); - - $url = route('api.requests.update', $initialProcessRequest); - - // Attempt to edit ProcessRequest completed data - $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); - - // Verify we can't (yet) - $response->assertStatus(403); - - $editAllRequestsData = Permission::where('name', 'edit-request_data')->first(); - $this->user->permissions()->attach($editAllRequestsData); - $this->user->refresh(); - session()->forget('permissions'); - $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); - - // Attempt to edit complete data with the permissions - // fo the user now set - $response->assertStatus(204); - - $this->user->permissions()->detach($editAllRequestsData); - $this->user->refresh(); - session()->forget('permissions'); - $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); - - // Try again after removing the permissions - // from the user - $response->assertStatus(403); - - // Add process level permission - $process->usersCanEditData()->sync( - [$this->user->id => ['method' => 'EDIT_DATA']] - ); - - $process->save(); - - // Create a new process request with the update process - // configuration since the process request now honors - // the process configuration how it existed when the - // process request was initiated - $secondProcessRequest = ProcessRequest::factory()->create([ - 'status' => 'COMPLETED', - 'data' => ['foo' => 'bar'], - 'process_id' => $process->id, - ]); - - $url = route('api.requests.update', $secondProcessRequest); - $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); - - // Verify we can now edit the completed ProcessRequest data - $response->assertStatus(204); - } - - /** - * Test lists of requests and permissions - * - * @return void - */ - public function testGetProcessRequestListAndPermissions() - { - // Setup user as non administrator - $this->user->is_administrator = false; - $this->user->save(); - - ProcessRequest::factory()->count(10)->create([ - 'user_id' => $this->user->getKey(), + ProcessRequest::factory()->count(9)->create([ + 'parent_request_id' => $request->id, ]); - $response = $this->apiCall('GET', self::API_TEST_URL); + $url = '/requests-by-case?case_number=' . $request->case_number; + + $response = $this->apiCall('GET', $url); //Validate the header status code $response->assertStatus(200); @@ -815,203 +68,22 @@ public function testGetProcessRequestListAndPermissions() 'data' => ['*' => self::STRUCTURE], 'meta', ]); + // Verify count $this->assertEquals(10, $response->json()['meta']['total']); - - // Create 10 more - ProcessRequest::factory()->count(10)->create([ - 'user_id' => $this->user->getKey(), - ]); - - $response = $this->apiCall('GET', self::API_TEST_URL . '?per_page=15'); - - //Validate the header status code - $response->assertStatus(200); - - // Verify structure - $response->assertJsonStructure([ - 'data' => ['*' => self::STRUCTURE], - 'meta', - ]); - - // Verify page count - $this->assertEquals(15, $response->json()['meta']['count']); - // Verify total count - $this->assertEquals(20, $response->json()['meta']['total']); - - // Create 10 more for different users - ProcessRequest::factory()->count(10)->create(); - - $response = $this->apiCall('GET', self::API_TEST_URL . '?per_page=15'); - - //Validate the header status code - $response->assertStatus(200); - - // Verify structure - $response->assertJsonStructure([ - 'data' => ['*' => self::STRUCTURE], - 'meta', - ]); - - // Verify page count - $this->assertEquals(15, $response->json()['meta']['count']); - // Verify total count - $this->assertEquals(20, $response->json()['meta']['total']); - } - - public function testGetRequestToken() - { - $expectedResponse = [ - 'advanceStatus', - 'completed_at', - 'completed_at_formatted', - 'completed_by', - 'count', - 'created_at', - 'created_at_formatted', - 'repeat_message', - 'element_id', - 'element_name', - 'is_sequence_flow', - 'status', - 'status_translation', - 'user' => [ - 'id', - 'username', - 'firstname', - 'lastname', - 'fullname', - ], - 'user_id', - ]; - - // Create other User - $otherUser = User::factory()->create(); - - //create a request and a token - $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); - $token = ProcessRequestToken::factory()->create([ - 'process_request_id' => $request->id, - 'user_id' => $this->user, - ]); - - // Validate the status is correct - $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $token->element_id])); - $response->assertStatus(200); - - // Verify structure - $response->assertJsonStructure($expectedResponse); - - // Validate non existing process element - $nonExistentElementId = 999; - - $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $nonExistentElementId])); - $response->assertStatus(404); - - // Verify with other user without permissions - $this->user = $otherUser; - - $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $token->element_id])); - $response->assertStatus(403); - - $this->user->giveDirectPermission('view-all_requests'); - $this->user->refresh(); - - // Verify with other user with permissions - $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $token->element_id])); - $response->assertStatus(200); - } - - public function testAdvancedFilter() - { - $hit = ProcessRequest::factory()->create([ - 'data' => ['foo' => 'bar'], - ]); - $miss = ProcessRequest::factory()->create([ - 'data' => ['foo' => 'baz'], - ]); - - $filterString = json_encode([ - [ - 'subject' => ['type' => 'Field', 'value' => 'data.foo'], - 'operator' => '=', - 'value' => 'bar', - ], - ]); - - $response = $this->apiCall('GET', self::API_TEST_URL, ['advanced_filter' => $filterString, 'include' => 'data']); - $json = $response->json(); - - $this->assertEquals($hit->id, $json['data'][0]['id']); - } - - // Test enableIsActionbyemail function - public function testEnableIsActionbyemail() - { - //create a token - $token = ProcessRequestToken::factory()->create([ - 'status' => 'ACTIVE', - ]); - $this->assertEquals($token->is_actionbyemail, 0); - - $res = (new ProcessRequestController)->enableIsActionbyemail($token->getKey()); - - $this->assertTrue($res); } - /** - * Test the screenRequested method of ProcessRequestController. - */ - public function testScreenRequested() - { - $request = ProcessRequest::factory()->create(); - ProcessRequestToken::factory()->create([ - 'process_request_id' => $request->id, - 'element_type' => 'userTask', - 'status' => 'CLOSED', - ]); - - $params = [ - 'page' => 1, - 'per_page' => 10, - 'order_by' => 'completed_at', - 'order_direction' => 'asc', - 'filter' => '', - ]; - $route = route('api.requests.detail.screen', ['request' => $request->id]); - $response = $this->apiCall('GET', $route, $params); - - $response->assertStatus(200); - // Assert empty because tokens does not have screens. - $data = $response->json()['data']; - $this->assertEmpty($data); - } - /** * Get a list of Requests by Cases. */ - public function testRequestByCase() + public function testRequestByCaseWithoutCaseNumber() { - ProcessRequest::query()->delete(); - $request = ProcessRequest::factory()->create(); - ProcessRequest::factory()->count(9)->create([ - 'parent_request_id' => $request->id, - ]); - - $url = '/requests-by-case?case_number=' . $request->case_number; + $url = '/requests-by-case'; $response = $this->apiCall('GET', $url); //Validate the header status code - $response->assertStatus(200); - - // Verify structure - $response->assertJsonStructure([ - 'data' => ['*' => self::STRUCTURE], - 'meta', - ]); - - // Verify count - $this->assertEquals(10, $response->json()['meta']['total']); + $response->assertStatus(422); + $this->assertEquals('The Case number field is required.', $response->json()['message']); } } From 2878d4096ff8e6f6fe50eefb39c40bdfccd06c35 Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker Date: Thu, 26 Sep 2024 16:07:26 -0400 Subject: [PATCH 4/7] feature/FOUR-19267 --- tests/Feature/Api/ProcessRequestsTest.php | 942 ++++++++++++++++++++++ 1 file changed, 942 insertions(+) diff --git a/tests/Feature/Api/ProcessRequestsTest.php b/tests/Feature/Api/ProcessRequestsTest.php index dcf4a13906..efa1e76d7a 100644 --- a/tests/Feature/Api/ProcessRequestsTest.php +++ b/tests/Feature/Api/ProcessRequestsTest.php @@ -44,6 +44,948 @@ class ProcessRequestsTest extends TestCase 'created_at', 'updated_at', ]; + + /** + * Get a list of Requests without query parameters. + */ + public function testListRequest() + { + ProcessRequest::query()->delete(); + + ProcessRequest::factory()->count(10)->create(); + + $response = $this->apiCall('GET', self::API_TEST_URL); + + //Validate the header status code + $response->assertStatus(200); + + // Verify structure + $response->assertJsonStructure([ + 'data' => ['*' => self::STRUCTURE], + 'meta', + ]); + + // Verify count + $this->assertEquals(10, $response->json()['meta']['total']); + } + + /** + * Count the total of request per process + */ + public function testCountRequest() + { + ProcessRequest::query()->delete(); + $process = Process::factory()->create(); + ProcessRequest::factory()->count(10)->create([ + 'process_id' => $process->id, + ]); + + $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $process->id . '/count'); + + // Validate the header status code + $response->assertStatus(200); + + // Verify structure + $response->assertJsonStructure([ + 'meta', + ]); + + // Verify count + $this->assertEquals(10, $response->json()['meta']['total']); + } + + /** + * Test to verify that the list dates are in the correct format (yyyy-mm-dd H:i+GMT) + */ + public function testScreenListDates() + { + $name = 'testRequestTimezone'; + $newEntity = ProcessRequest::factory()->create(['name' => $name]); + $route = self::API_TEST_URL . '?filter=' . $name; + $response = $this->apiCall('GET', $route); + + $this->assertEquals( + $newEntity->updated_at->format('c'), + $response->getData()->data[0]->updated_at + ); + + $this->assertEquals( + $newEntity->created_at->format('c'), + $response->getData()->data[0]->created_at + ); + } + + /** + * Get a list of Request with parameters + */ + public function testListRequestIncludingData() + { + $requestname = 'mytestrequestnameincludesdata'; + + //Create requests with data + ProcessRequest::factory()->create([ + 'name' => $requestname, + 'data' => ['test' => 'value1'], + ]); + + //Set direction to ascending + $query = "?page=1&include=data&order_by=data.test&order_direction=ASC&filter=$requestname"; + $response = $this->apiCall('GET', self::API_TEST_URL . $query); + + $response->assertStatus(200); + $response->assertJsonFragment(['data' => ['test' => 'value1']]); + } + + /** + * Get a list of Request with parameters + */ + public function testListRequestOrderByData() + { + $requestname = 'mytestrequestnameorderbydata'; + + //Create requests with data + ProcessRequest::factory()->create([ + 'name' => $requestname, + 'data' => ['test' => 'value1'], + ]); + + ProcessRequest::factory()->create([ + 'name' => $requestname, + 'data' => ['test' => 'value2'], + ]); + + //Set direction to ascending + $query = "?page=1&include=data&order_by=data.test&order_direction=ASC&filter=$requestname"; + $response = $this->apiCall('GET', self::API_TEST_URL . $query); + + //Verify that the request with test data of "value1" is first + $response->assertStatus(200); + $this->assertEquals('value1', $response->json()['data'][0]['data']['test']); + + //Set direction to descending + $query = "?page=1&include=data&order_by=data.test&order_direction=DESC&filter=$requestname"; + $response = $this->apiCall('GET', self::API_TEST_URL . $query); + + //Verify that the request with test data of "value2" is first + $response->assertStatus(200); + $this->assertEquals('value2', $response->json()['data'][0]['data']['test']); + } + + /** + * Get a list of Request with parameters + */ + public function testListRequestWithQueryParameter() + { + $requestname = 'mytestrequestname'; + + ProcessRequest::factory()->create([ + 'name' => $requestname, + ]); + + //List Request with filter option + $perPage = Faker::create()->randomDigitNotNull; + $query = '?page=1&per_page=' . $perPage . '&order_by=name&order_direction=DESC&filter=' . $requestname; + $response = $this->apiCall('GET', self::API_TEST_URL . $query); + + //Validate the header status code + $response->assertStatus(200); + + //verify structure paginate + $response->assertJsonStructure([ + 'data', + 'meta', + ]); + + // Verify return data + $this->assertEquals(1, $response->json()['meta']['total']); + $this->assertEquals('name', $response->json()['meta']['sort_by']); + $this->assertEquals('DESC', $response->json()['meta']['sort_order']); + } + + /** + * Test that we can filter by participant + */ + public function testFilterByParticipant() + { + $participant = User::factory()->create(); + $otherUser = User::factory()->create(); + + $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); + $otherRequest = ProcessRequest::factory()->create(['status' => 'ACTIVE']); + + $token = ProcessRequestToken::factory()->create([ + 'process_request_id' => $request->id, + 'user_id' => $participant->id, + ]); + + $otherToken = ProcessRequestToken::factory()->create([ + 'process_request_id' => $otherRequest->id, + 'user_id' => $otherUser->id, + ]); + + $response = $this->apiCall('GET', self::API_TEST_URL, ['pmql' => "participant = \"{$participant->username}\""]); + $this->assertEquals(1, $response->json()['meta']['total']); + $this->assertEquals($request->id, $response->json()['data'][0]['id']); + } + + /** + * Test that paged values are returned as expected + */ + public function testWithPagination() + { + $process = Process::factory()->create(); + ProcessRequest::factory()->count(5)->create([ + 'name' => $process->name, + 'process_id' => $process->id, + ]); + $query = '?page=2&per_page=3&order_by=name'; + + $response = $this->apiCall('GET', self::API_TEST_URL . $query); + + //Validate the header status code + $response->assertStatus(200); + $json = $response->json(); + + // 2 items on 2nd page + $this->assertCount(2, $json['data']); + + // keys should be re-indexed so they get converted to + // arrays instead of objects on json_encode + $this->assertEquals([0, 1], array_keys($json['data'])); + } + + /** + * Get a list of Request by type + */ + public function testListRequestWithType() + { + $in_progress = ProcessRequest::factory()->create([ + 'status' => 'ACTIVE', + 'user_id' => $this->user->id, + ]); + + $completed = ProcessRequest::factory()->create([ + 'status' => 'COMPLETED', + 'user_id' => $this->user->id, + ]); + + $response = $this->apiCall('GET', self::API_TEST_URL . '/?type=completed'); + $json = $response->json(); + $this->assertCount(1, $json['data']); + $this->assertEquals($completed->id, $json['data'][0]['id']); + + $response = $this->apiCall('GET', self::API_TEST_URL . '/?type=in_progress'); + $json = $response->json(); + $this->assertCount(1, $json['data']); + $this->assertEquals($in_progress->id, $json['data'][0]['id']); + } + + /** + * Get a list of Request with assocations included + */ + public function testListRequestWithIncludes() + { + $process = Process::factory()->create(); + + ProcessRequest::factory()->create([ + 'process_id' => $process->id, + ]); + + $response = $this->apiCall('GET', self::API_TEST_URL . '/?include=process'); + $json = $response->json(); + $this->assertEquals($process->id, $json['data'][0]['process']['id']); + } + + /** + * Get a list of requests with a user that has view all permission + */ + public function testListRequestViewAllPermission() + { + $this->user = User::factory()->create(['status'=>'ACTIVE']); + $processRequest = ProcessRequest::factory()->create([]); + + $response = $this->apiCall('GET', self::API_TEST_URL); + $json = $response->json(); + $this->assertCount(0, $json['data']); + + $this->user->giveDirectPermission('view-all_requests'); + $this->user->refresh(); + + $response = $this->apiCall('GET', self::API_TEST_URL); + $json = $response->json(); + $this->assertEquals($processRequest->id, $json['data'][0]['id']); + } + + /** + * Get a request + */ + public function testGetRequest() + { + //get the id from the factory + $request = ProcessRequest::factory()->create()->id; + + //load api + $response = $this->apiCall('GET', self::API_TEST_URL . '/' . $request); + + //Validate the status is correct + $response->assertStatus(200); + + //verify structure + $response->assertJsonStructure(self::STRUCTURE); + } + + /** + * Parameters required for update of request + */ + public function testUpdateProcessRequestParametersRequired() + { + $id = ProcessRequest::factory()->create(['name' => 'mytestrequestname'])->id; + //The post must have the required parameters + $url = self::API_TEST_URL . '/' . $id; + + $response = $this->apiCall('PUT', $url, [ + 'name' => null, + ]); + + //Validate the header status code + $response->assertStatus(422); + } + + /** + * Update request in process + */ + public function testUpdateProcessRequest() + { + $faker = Faker::create(); + + $url = self::API_TEST_URL . '/' . ProcessRequest::factory()->create()->id; + + //Load the starting request data + $verify = $this->apiCall('GET', $url); + + //Post saved success + $response = $this->apiCall('PUT', $url, [ + 'name' => $faker->unique()->name(), + 'data' => ['test' => 1], + 'process_id' => json_decode($verify->getContent())->process_id, + ]); + + //Validate the header status code + $response->assertStatus(204); + + //Load the updated request data + $verify_new = $this->apiCall('GET', $url); + + //Check that it has changed + $this->assertNotEquals($verify, $verify_new); + } + + /** + * Check that the validation wont allow duplicate requestnames + */ + public function testUpdateProcessRequestTitleExists() + { + $request1 = ProcessRequest::factory()->create([ + 'name' => 'MyRequestName', + ]); + + $request2 = ProcessRequest::factory()->create(); + + $url = self::API_TEST_URL . '/' . $request2->id; + + $response = $this->apiCall('PUT', $url, [ + 'name' => 'MyRequestName', + ]); + //Validate the header status code + $response->assertStatus(422); + $response->assertSeeText('The Name has already been taken'); + } + + /** + * test to be sure that you cannot cancel a request until you have been given permission + */ + public function testCancelRequestWithPermissions() + { + // We need an admin user and a non-admin user + $admin = $this->user; + + $nonAdmin = User::factory()->create([ + 'is_administrator' => false, + ]); + + // Create a single process in order to create + // two process requests with the same process + $process = Process::factory()->create([ + 'user_id' => $admin->id, + ]); + + // Create the initial process request + $initialProcessVersionRequest = ProcessRequest::factory()->create([ + 'user_id' => $nonAdmin->id, + 'process_id' => $process->id, + ]); + + // Attempt to cancel a request + $this->user = $nonAdmin; + $route = route('api.requests.update', [$initialProcessVersionRequest->id]); + $response = $this->apiCall('PUT', $route, ['status' => 'CANCELED']); + + // Confirm the user does not have access + $response->assertStatus(403); + + // Add the user to the list of users that can cancel + $this->user = $admin; + $route = route('api.processes.update', [$process->id]); + $response = $this->apiCall('PUT', $route, [ + 'name' => 'Update Process', + 'description' => 'Update Test', + 'cancel_request' => ['users' => [$nonAdmin->id], 'groups' => []], + ]); + + // Create a second process request with the + // same process, this time the process request + // will honor the new process configuration + $secondProcessVersionRequest = ProcessRequest::factory()->create([ + 'user_id' => $nonAdmin->id, + 'process_id' => $process->id, + ]); + + // Attempt to cancel a request + $this->user = $nonAdmin; + $route = route('api.requests.update', [$secondProcessVersionRequest->id]); + $response = $this->apiCall('PUT', $route, ['status' => 'CANCELED']); + + // Assert that the API updated + $response->assertStatus(204); + } + + /** + * Test ability to complete a request if it has the status: ERROR + */ + public function testCompleteRequest() + { + $this->user->is_administrator = false; + $this->user->saveOrFail(); + $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); + + // give the user editData permission to get past the route check + $request->processVersion->usersCanEditData()->sync([ + $this->user->id => [ + 'method' => 'EDIT_DATA', + 'process_id' => $request->process->id, + ], + ]); + + $route = route('api.requests.update', [$request->id]); + $response = $this->apiCall('PUT', $route, ['status' => 'COMPLETED']); + + // Confirm the user does not have access + $response->assertStatus(403); + $this->assertEquals('Not authorized to complete this request.', $response->json()['message']); + + // Make user admin again + $this->user->is_administrator = true; + $this->user->saveOrFail(); + $response = $this->apiCall('PUT', $route, ['status' => 'COMPLETED']); + + // Confirm only ERROR status can be changed + $response->assertStatus(422); + $this->assertEquals('Only requests with status: ERROR can be manually completed', + $response->json()['errors']['status'][0] + ); + + $request->status = 'ERROR'; + $request->saveOrFail(); + + $response = $this->apiCall('PUT', $route, ['status' => 'COMPLETED']); + $response->assertStatus(204); + + $request->refresh(); + $this->assertEquals('COMPLETED', $request->status); + + // Verify comment added + $this->assertEquals( + $this->user->fullname . ' manually completed the request from an error state', + Comment::first()->body + ); + + // Verify metadata was removed from data object + $this->assertFalse(array_key_exists('_user', $request->data)); + } + + /** + * Delete request and request tokens in parent process, and children processes + */ + public function testDeleteParentProcessRequestShouldRemoveRequestAndTokensForParentAndChildren() + { + $user = User::factory()->create(); + // Prepare data + $parentProcessRequest = ProcessRequest::factory()->create(['status' => 'ACTIVE']); + + $childProcessRequest1 = ProcessRequest::factory()->create([ + 'status' => 'ACTIVE', + 'parent_request_id' => $parentProcessRequest->id, + ]); + + $childProcessRequest2 = ProcessRequest::factory()->create([ + 'status' => 'ACTIVE', + 'parent_request_id' => $childProcessRequest1->id, + ]); + + $parentTokens = ProcessRequestToken::factory()->count(3)->create([ + 'process_request_id' => $parentProcessRequest->id, + ]); + + $childTokens1 = ProcessRequestToken::factory()->count(4)->create([ + 'process_request_id' => $childProcessRequest1->id, + ]); + + $childTokens2 = ProcessRequestToken::factory()->count(5)->create([ + 'process_request_id' => $childProcessRequest2->id, + ]); + + // Assert database has parent and child requests + $this->assertEquals(1, ProcessRequest::where('id', $parentProcessRequest->id)->count()); + $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest1->id)->count()); + $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest2->id)->count()); + + // Assert count database has tokens for parent and child requests + $this->assertEquals(3, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); + $this->assertEquals(4, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); + $this->assertEquals(5, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); + + //Remove request + $url = self::API_TEST_URL . '/' . $parentProcessRequest->id; + $response = $this->apiCall('DELETE', $url); + + //Validate the header status code + $response->assertStatus(204); + + // Assert database does not has parent and child requests + $this->assertEquals(0, ProcessRequest::where('id', $parentProcessRequest->id)->count()); + $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest1->id)->count()); + $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest2->id)->count()); + + // Assert database does not has parent and child requests tokens + $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); + $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); + $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); + } + + /** + * Delete request and request tokens in subprocess and child process + */ + public function testDeleteChildProcessRequestShouldRemoveRequestAndTokensForChildren() + { + $user = User::factory()->create(); + // Prepare data + $parentProcessRequest = ProcessRequest::factory()->create(['status' => 'ACTIVE']); + + $childProcessRequest1 = ProcessRequest::factory()->create([ + 'status' => 'ACTIVE', + 'parent_request_id' => $parentProcessRequest->id, + ]); + + $childProcessRequest2 = ProcessRequest::factory()->create([ + 'status' => 'ACTIVE', + 'parent_request_id' => $childProcessRequest1->id, + ]); + + $parentTokens = ProcessRequestToken::factory()->count(3)->create([ + 'process_request_id' => $parentProcessRequest->id, + ]); + + $childTokens1 = ProcessRequestToken::factory()->count(4)->create([ + 'process_request_id' => $childProcessRequest1->id, + ]); + + $childTokens2 = ProcessRequestToken::factory()->count(5)->create([ + 'process_request_id' => $childProcessRequest2->id, + ]); + + // Assert database has parent and child requests + $this->assertEquals(1, ProcessRequest::where('id', $parentProcessRequest->id)->count()); + $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest1->id)->count()); + $this->assertEquals(1, ProcessRequest::where('id', $childProcessRequest2->id)->count()); + + // Assert count database has tokens for parent and child requests + $this->assertEquals(3, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); + $this->assertEquals(4, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); + $this->assertEquals(5, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); + + //Remove request + $url = self::API_TEST_URL . '/' . $childProcessRequest1->id; + $response = $this->apiCall('DELETE', $url); + + //Validate the header status code + $response->assertStatus(204); + + // Assert database does not has parent and child requests + $this->assertEquals(1, ProcessRequest::where('id', $parentProcessRequest->id)->count()); + $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest1->id)->count()); + $this->assertEquals(0, ProcessRequest::where('id', $childProcessRequest2->id)->count()); + + // Assert database does not has parent and child requests tokens + $this->assertEquals(3, ProcessRequestToken::where('process_request_id', $parentProcessRequest->id)->count()); + $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest1->id)->count()); + $this->assertEquals(0, ProcessRequestToken::where('process_request_id', $childProcessRequest2->id)->count()); + } + + /** + * The request does not exist in process + */ + public function testDeleteProcessRequestNotExist() + { + //ProcessRequest not exist + $url = self::API_TEST_URL . '/' . ProcessRequest::factory()->make()->id; + $response = $this->apiCall('DELETE', $url); + + //Validate the header status code + $response->assertStatus(405); + } + + public function testListCanceledProcessRequests() + { + ProcessRequest::query()->delete(); + + ProcessRequest::factory()->count(5)->create(['status' => 'ACTIVE']); + ProcessRequest::factory()->count(3)->create(['status' => 'COMPLETED']); + ProcessRequest::factory()->count(1)->create(['status' => 'CANCELED']); + + // The list of requests should show just ACTIVE requests + $response = $this->apiCall('GET', self::API_TEST_URL . '?type=in_progress'); + $this->assertEquals(5, $response->json()['meta']['total']); + + // The list of completed does NOT include CANCELED requests + $response = $this->apiCall('GET', self::API_TEST_URL . '?type=completed'); + $this->assertEquals(3, $response->json()['meta']['total']); + + // The list of all requests includes everything + $response = $this->apiCall('GET', self::API_TEST_URL . '?type=all'); + $this->assertEquals(9, $response->json()['meta']['total']); + } + + /** + * Verifies that a file uploaded in a request can be downloaded + */ + public function testFileDownload() + { + // We create a fake file to upload + $testFileName = 'test.txt'; + Storage::fake('public'); + $fileUpload = UploadedFile::fake()->create($testFileName, 1); + + // Create a request + $request = ProcessRequest::factory()->create(); + + // Crate a user without administrator privileges + $user = User::factory()->create([ + 'status' => 'ACTIVE', + 'is_administrator' => false, + ]); + + // Add the file to the request + $addedMedia = $request + ->addMedia($fileUpload) + ->withCustomProperties(['data_name' => 'test']) + ->toMediaCollection('local'); + + $route = self::API_TEST_URL . '/' . $request->id . '/files/' . $addedMedia->id; + $response = $this->apiCall('GET', $route); + + // Validate the header status code + $response->assertStatus(200); + + // Verify that a file with the fake file is downloaded + $this->assertEquals($testFileName, $response->getFile()->getFileName()); + } + + public function testParticipantPermissionsToView() + { + $participant = User::factory()->create(); + $otherUser = User::factory()->create(); + + $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); + $token = ProcessRequestToken::factory()->create([ + 'process_request_id' => $request->id, + 'user_id' => $participant->id, + ]); + + $url = route('api.requests.show', $request); + $this->user = $otherUser; + + $response = $this->apiCall('get', $url); + $response->assertStatus(403); + + $this->user = $participant; + + $response = $this->apiCall('get', $url); + $response->assertStatus(200); + + // Participant can still see the data when completed + $request->update(['status' => 'COMPLETED']); + $response = $this->apiCall('get', $url); + $response->assertStatus(200); + } + + public function testUserCanEditCompletedData() + { + $this->user = User::factory()->create(); + $process = Process::factory()->create(); + + $initialProcessRequest = ProcessRequest::factory()->create([ + 'status' => 'COMPLETED', + 'data' => ['foo' => 'bar'], + 'process_id' => $process->id, + ]); + + $url = route('api.requests.update', $initialProcessRequest); + + // Attempt to edit ProcessRequest completed data + $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); + + // Verify we can't (yet) + $response->assertStatus(403); + + $editAllRequestsData = Permission::where('name', 'edit-request_data')->first(); + $this->user->permissions()->attach($editAllRequestsData); + $this->user->refresh(); + session()->forget('permissions'); + $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); + + // Attempt to edit complete data with the permissions + // fo the user now set + $response->assertStatus(204); + + $this->user->permissions()->detach($editAllRequestsData); + $this->user->refresh(); + session()->forget('permissions'); + $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); + + // Try again after removing the permissions + // from the user + $response->assertStatus(403); + + // Add process level permission + $process->usersCanEditData()->sync( + [$this->user->id => ['method' => 'EDIT_DATA']] + ); + + $process->save(); + + // Create a new process request with the update process + // configuration since the process request now honors + // the process configuration how it existed when the + // process request was initiated + $secondProcessRequest = ProcessRequest::factory()->create([ + 'status' => 'COMPLETED', + 'data' => ['foo' => 'bar'], + 'process_id' => $process->id, + ]); + + $url = route('api.requests.update', $secondProcessRequest); + $response = $this->apiCall('put', $url, ['data' => ['foo' => '123']]); + + // Verify we can now edit the completed ProcessRequest data + $response->assertStatus(204); + } + + /** + * Test lists of requests and permissions + * + * @return void + */ + public function testGetProcessRequestListAndPermissions() + { + // Setup user as non administrator + $this->user->is_administrator = false; + $this->user->save(); + + ProcessRequest::factory()->count(10)->create([ + 'user_id' => $this->user->getKey(), + ]); + + $response = $this->apiCall('GET', self::API_TEST_URL); + + //Validate the header status code + $response->assertStatus(200); + + // Verify structure + $response->assertJsonStructure([ + 'data' => ['*' => self::STRUCTURE], + 'meta', + ]); + // Verify count + $this->assertEquals(10, $response->json()['meta']['total']); + + // Create 10 more + ProcessRequest::factory()->count(10)->create([ + 'user_id' => $this->user->getKey(), + ]); + + $response = $this->apiCall('GET', self::API_TEST_URL . '?per_page=15'); + + //Validate the header status code + $response->assertStatus(200); + + // Verify structure + $response->assertJsonStructure([ + 'data' => ['*' => self::STRUCTURE], + 'meta', + ]); + + // Verify page count + $this->assertEquals(15, $response->json()['meta']['count']); + // Verify total count + $this->assertEquals(20, $response->json()['meta']['total']); + + // Create 10 more for different users + ProcessRequest::factory()->count(10)->create(); + + $response = $this->apiCall('GET', self::API_TEST_URL . '?per_page=15'); + + //Validate the header status code + $response->assertStatus(200); + + // Verify structure + $response->assertJsonStructure([ + 'data' => ['*' => self::STRUCTURE], + 'meta', + ]); + + // Verify page count + $this->assertEquals(15, $response->json()['meta']['count']); + // Verify total count + $this->assertEquals(20, $response->json()['meta']['total']); + } + + public function testGetRequestToken() + { + $expectedResponse = [ + 'advanceStatus', + 'completed_at', + 'completed_at_formatted', + 'completed_by', + 'count', + 'created_at', + 'created_at_formatted', + 'repeat_message', + 'element_id', + 'element_name', + 'is_sequence_flow', + 'status', + 'status_translation', + 'user' => [ + 'id', + 'username', + 'firstname', + 'lastname', + 'fullname', + ], + 'user_id', + ]; + + // Create other User + $otherUser = User::factory()->create(); + + //create a request and a token + $request = ProcessRequest::factory()->create(['status' => 'ACTIVE']); + $token = ProcessRequestToken::factory()->create([ + 'process_request_id' => $request->id, + 'user_id' => $this->user, + ]); + + // Validate the status is correct + $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $token->element_id])); + $response->assertStatus(200); + + // Verify structure + $response->assertJsonStructure($expectedResponse); + + // Validate non existing process element + $nonExistentElementId = 999; + + $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $nonExistentElementId])); + $response->assertStatus(404); + + // Verify with other user without permissions + $this->user = $otherUser; + + $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $token->element_id])); + $response->assertStatus(403); + + $this->user->giveDirectPermission('view-all_requests'); + $this->user->refresh(); + + // Verify with other user with permissions + $response = $this->apiCall('GET', route('api.requests.getRequestToken', ['request' => $request->id, 'element_id' => $token->element_id])); + $response->assertStatus(200); + } + + public function testAdvancedFilter() + { + $hit = ProcessRequest::factory()->create([ + 'data' => ['foo' => 'bar'], + ]); + $miss = ProcessRequest::factory()->create([ + 'data' => ['foo' => 'baz'], + ]); + + $filterString = json_encode([ + [ + 'subject' => ['type' => 'Field', 'value' => 'data.foo'], + 'operator' => '=', + 'value' => 'bar', + ], + ]); + + $response = $this->apiCall('GET', self::API_TEST_URL, ['advanced_filter' => $filterString, 'include' => 'data']); + $json = $response->json(); + + $this->assertEquals($hit->id, $json['data'][0]['id']); + } + + // Test enableIsActionbyemail function + public function testEnableIsActionbyemail() + { + //create a token + $token = ProcessRequestToken::factory()->create([ + 'status' => 'ACTIVE', + ]); + $this->assertEquals($token->is_actionbyemail, 0); + + $res = (new ProcessRequestController)->enableIsActionbyemail($token->getKey()); + + $this->assertTrue($res); + } + + /** + * Test the screenRequested method of ProcessRequestController. + */ + public function testScreenRequested() + { + $request = ProcessRequest::factory()->create(); + ProcessRequestToken::factory()->create([ + 'process_request_id' => $request->id, + 'element_type' => 'userTask', + 'status' => 'CLOSED', + ]); + + $params = [ + 'page' => 1, + 'per_page' => 10, + 'order_by' => 'completed_at', + 'order_direction' => 'asc', + 'filter' => '', + ]; + $route = route('api.requests.detail.screen', ['request' => $request->id]); + $response = $this->apiCall('GET', $route, $params); + + $response->assertStatus(200); + // Assert empty because tokens does not have screens. + $data = $response->json()['data']; + $this->assertEmpty($data); + } /** * Get a list of Requests by Cases. From b7ae4d88f09d771f121962359c7e639967df009b Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker <123644082+luNunezProcessmaker@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:56:12 -0400 Subject: [PATCH 5/7] Update ProcessRequest.php --- ProcessMaker/Models/ProcessRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProcessMaker/Models/ProcessRequest.php b/ProcessMaker/Models/ProcessRequest.php index 3679ff9f3c..6ddb886dcc 100644 --- a/ProcessMaker/Models/ProcessRequest.php +++ b/ProcessMaker/Models/ProcessRequest.php @@ -1113,7 +1113,7 @@ public function scopeApplyPagination($query, $request) } /** - * Scope to filter by case_number through the processRequest relationship + * Scope to filter by case_number */ public function scopeFilterByCaseNumber($query, $request) { From ef814449abceee86a35b96744af2b4100e903ded Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker Date: Fri, 27 Sep 2024 13:23:23 -0400 Subject: [PATCH 6/7] feature/FOUR-19267 --- .../Http/Controllers/Api/ProcessRequestController.php | 2 +- tests/Feature/Api/ProcessRequestsTest.php | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index ec59b145af..a430d97d44 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -856,7 +856,7 @@ public function getRequestsByCase(Request $request, User $user = null) // Validate the inputs, including optional ones $request->validate([ 'case_number' => 'required|integer', - 'status' => 'nullable|string|in:ACTIVE,COMPLETED,ERROR,CANCELED', + 'status' => 'nullable|string|in:ACTIVE,COMPLETED,CANCELED', 'order_by' => 'nullable|string|in:id,name,status,user_id,initiated_at,participants', 'order_direction' => 'nullable|string|in:asc,desc', 'page' => 'nullable|integer|min:1', diff --git a/tests/Feature/Api/ProcessRequestsTest.php b/tests/Feature/Api/ProcessRequestsTest.php index efa1e76d7a..438e97daef 100644 --- a/tests/Feature/Api/ProcessRequestsTest.php +++ b/tests/Feature/Api/ProcessRequestsTest.php @@ -30,6 +30,7 @@ class ProcessRequestsTest extends TestCase public $withPermissions = true; const API_TEST_URL = '/requests'; + const API_REQUESTS_BY_CASE = '/requests-by-case'; const STRUCTURE = [ 'id', @@ -998,7 +999,7 @@ public function testRequestByCase() 'parent_request_id' => $request->id, ]); - $url = '/requests-by-case?case_number=' . $request->case_number; + $url = self::API_REQUESTS_BY_CASE . '?case_number=' . $request->case_number; $response = $this->apiCall('GET', $url); @@ -1019,10 +1020,8 @@ public function testRequestByCase() * Get a list of Requests by Cases. */ public function testRequestByCaseWithoutCaseNumber() - { - $url = '/requests-by-case'; - - $response = $this->apiCall('GET', $url); + { + $response = $this->apiCall('GET', self::API_REQUESTS_BY_CASE); //Validate the header status code $response->assertStatus(422); From 00ccbef53a998a86702538ed8bae2b68bea61ea8 Mon Sep 17 00:00:00 2001 From: luNunezProcessmaker <123644082+luNunezProcessmaker@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:52:50 -0400 Subject: [PATCH 7/7] Update ProcessRequestController.php --- ProcessMaker/Http/Controllers/Api/ProcessRequestController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php index a430d97d44..a57f89cd23 100644 --- a/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php +++ b/ProcessMaker/Http/Controllers/Api/ProcessRequestController.php @@ -856,7 +856,6 @@ public function getRequestsByCase(Request $request, User $user = null) // Validate the inputs, including optional ones $request->validate([ 'case_number' => 'required|integer', - 'status' => 'nullable|string|in:ACTIVE,COMPLETED,CANCELED', 'order_by' => 'nullable|string|in:id,name,status,user_id,initiated_at,participants', 'order_direction' => 'nullable|string|in:asc,desc', 'page' => 'nullable|integer|min:1',