diff --git a/app/Http/Controllers/ProfilesApiController.php b/app/Http/Controllers/ProfilesApiController.php index a3d9aeb0..cb707810 100644 --- a/app/Http/Controllers/ProfilesApiController.php +++ b/app/Http/Controllers/ProfilesApiController.php @@ -59,9 +59,6 @@ public function index(ProfilesApiRequest $request): JsonResponse } if ($request->boolean('with_data')) { - if(count(array_filter($request->query())) <=1){ - return response()->json(['error' => 'Please use a filter when pulling data.'], 400); - } $profile = $profile->withApiData($request->input('data_type')); } diff --git a/app/Http/Requests/ProfilesApiRequest.php b/app/Http/Requests/ProfilesApiRequest.php index dd46bba1..9c10060c 100644 --- a/app/Http/Requests/ProfilesApiRequest.php +++ b/app/Http/Requests/ProfilesApiRequest.php @@ -2,10 +2,9 @@ namespace App\Http\Requests; -use App\Profile; use App\Rules\AllowedProfileDataType; use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Rule; +use Illuminate\Validation\ValidationException; class ProfilesApiRequest extends FormRequest { @@ -27,14 +26,22 @@ public function authorize() public function rules() { return [ - 'person' => 'sometimes|string', - 'search' => 'sometimes|string', - 'search_names' => 'sometimes|string', - 'info_contains' => 'sometimes|string', - 'from_school' => 'sometimes|string', - 'tag' => 'sometimes|string', + 'person' => ['sometimes', 'string', 'regex:/^[a-zA-Z0-9.;]+$/'], + 'search' => ['sometimes', 'string', 'regex:/^[a-zA-Z0-9\s,\.]*$/', 'min:3'], + 'search_names' => ['sometimes', 'string', 'regex:/^[a-zA-Z0-9\s,\.]*$/', 'min:3'], + 'info_contains' => ['sometimes', 'string', 'regex:/^[a-zA-Z0-9\s,\.]*$/', 'min:3'], + 'from_school' => [ + 'sometimes', + 'string', + 'regex:/^[a-zA-Z0-9\s;,\.]+$/', + ], + 'tag' => ['sometimes', 'string', 'alpha_num', 'min:3'], 'public' => 'sometimes|boolean', - 'with_data' => 'sometimes|boolean', + 'with_data' => [ + 'sometimes', + 'boolean', + $this->validateWithData(), + ], 'raw_data' => 'sometimes|boolean', 'data_type' => [ 'sometimes', @@ -43,4 +50,29 @@ public function rules() ], ]; } + + public function validateWithData() + { + return function ($attribute, $value, $fail) { + if ($value && empty($this->person)) { + $fail('Invalid parameter.'); + } + }; + } + + protected function passedValidation(): void + { + $allowedKeys = array_keys($this->rules()); + + $extraKeys = collect($this->all()) + ->keys() + ->diff($allowedKeys); + + if ($extraKeys->isNotEmpty()) { + throw ValidationException::withMessages([ + 'extra_parameters' => 'Invalid parameter.', + ]); + } + } + } diff --git a/app/Rules/AllowedSchools.php b/app/Rules/AllowedSchools.php new file mode 100644 index 00000000..4df2e577 --- /dev/null +++ b/app/Rules/AllowedSchools.php @@ -0,0 +1,27 @@ +pluck('short_name') + ->toArray(); + + if(!in_array($value, $allowed_schools)){ + $fail("Invalid value for school."); + } + + } +} diff --git a/tests/Feature/ApiTest.php b/tests/Feature/ApiTest.php index 9617694d..90e8f601 100644 --- a/tests/Feature/ApiTest.php +++ b/tests/Feature/ApiTest.php @@ -67,19 +67,6 @@ public function testApi() $response->assertJsonFragment($this->profileJsonFragment($profiles[$i])); } - //////////////////////////// - // All profiles with data // - //////////////////////////// - - // profiles.test/api/v1?with_data=1 - $response = $this->get(route('api.index', ['with_data' => 1])); - - $response - ->assertStatus(400) - ->assertJson([ - 'error' => "Please use a filter when pulling data.", - ]); - //////////////////////////////// // Certain profiles with data // //////////////////////////////// @@ -117,6 +104,112 @@ public function testApi() ->assertJsonFragment($this->profileInfoJsonFragment($profiles[0]->data->first())); } + /** + * Test validation fails for profiles with data when 'person' is missing + * + * Endpoint: profiles.test/api/v1?with_data=1 + */ + public function testWithDataFails() + { + $response = $this->get(route('api.index', ['with_data' => 1])); + + $response + ->assertStatus(422) + ->assertJsonValidationErrors('with_data') + ->assertJson([ + 'errors' => [ + 'with_data' => ["Invalid parameter."], + ], + ]); + } + + /** + * Test validation fails for 'person' that includes an invalid character + * + * Endpoint: profiles.test/api/v1?person=invalid#char + */ + public function testPersonFails() + { + $response = $this->get(route('api.index', ['person' => 'invalid#char'])); + + $response + ->assertStatus(422) + ->assertJsonValidationErrors('person'); + } + + /** + * Test validation fails for an invalid profile data type + * + * Endpoint: profiles.test/api/v1?data_type=invalid_type + */ + public function testDataTypeFails() + { + $response = $this->get(route('api.index', ['data_type' => 'invalid_type'])); + + $response + ->assertStatus(422) + ->assertJsonValidationErrors('data_type'); + } + + /** + * Test validation fails for an invalid value for from_school + * + * Endpoint: profiles.test/api/v1?from_school=Other + */ + public function testSchoolFails() + { + $response = $this->get(route('api.index', ['from_school' => 'Other:'])); + + $response + ->assertStatus(422) + ->assertJsonValidationErrors('from_school'); + } + + /** + * Test validation fails for: + * Non-alphanumeric characters in 'search' + * 'search_names' values shorter than 3 characters + * Non-string values in 'info_contains' + */ + public function testSearchFails() + { + $parameters = [ + 'search' => 'invalid_search_entry!', + 'search_names' => 'No', + 'info_contains' => ['not', 'a', 'string'], + ]; + + $response = $this->get(route('api.index', $parameters)); + + $response + ->assertStatus(422) + ->assertJsonValidationErrors('search') + ->assertJson([ + 'errors' => [ + 'search' => ["The search format is invalid."], + ], + ]); + + $response + ->assertJsonValidationErrors('search_names') + ->assertJson([ + 'errors' => [ + 'search_names' => ["The search names must be at least 3 characters."], + ], + ]); + + $response + ->assertJsonValidationErrors('info_contains') + ->assertJson([ + 'errors' => [ + 'info_contains' => [ + "The info contains must be a string.", + "The info contains format is invalid.", + ], + ], + ]); + } + /** * Get the proper Profile JSON fragment *