diff --git a/app/Http/Controllers/api/FaqController.php b/app/Http/Controllers/api/FaqController.php new file mode 100644 index 0000000..de3e823 --- /dev/null +++ b/app/Http/Controllers/api/FaqController.php @@ -0,0 +1,114 @@ +json(['faqs' => Faq::all()]); + } + + /** + * Get an specific FAQ. + * + * @param Faq $id + * @return \Illuminate\Http\JsonResponse + */ + public function show($id) + { + $faq = Faq::find($id); + + return response()->json([ + 'id' => $faq->id, + 'title' => $faq->title, + 'description' => $faq->description, + 'created_at'=> $faq->created_at, + 'updated_at' => $faq->updated_at + ]); + } + + /** + * Save a new FAQ from request. + * + * @param Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function store(Request $request) + { + try { + $validatedData = $request->validate([ + 'title' => ['required', 'string', 'max:255'], + 'description' => ['required', 'string'], + ]); + + $faq = Faq::create($validatedData); + + return response()->json(['faq' => $faq], 201); + } catch (ValidationException $e) { + return response()->json(['errors' => $e->errors()], 422); + } + } + + /** + * Update specific FAQ. + * + * @param Request $request + * @param Faq $id + * @return \Illuminate\Http\JsonResponse + */ + public function update(Request $request, $id) + { + try { + $faqs = Faq::find($id); + + if (!$faqs) { + return response()->json(['error' => 'FAQ not found'], 404); + } + + $validatedData = $request->validate([ + 'title' => 'required|string|max:255', + 'description' => 'required|string', + ]); + + $faqs->title = $validatedData['title']; + $faqs->description = $validatedData['description']; + $faqs->save(); + + return response()->json(['message' => 'FAQ updated successfully']); + } catch (ValidationException $e) { + return response()->json(['errors' => $e->errors()], 422); + } + } + + /** + * Delete a specific FAQ. + * + * @param Request $request + * @param Faq $id + * @return \Illuminate\Http\JsonResponse + */ + public function destroy($id) + { + $faqs = Faq::find($id); + + if (!$faqs) { + return response()->json(['error' => 'FAQ not found'], 404); + } + + $faqs->delete(); + + return response()->json(['message' => 'FAQ deleted successfully']); + } +} diff --git a/app/Models/Faq.php b/app/Models/Faq.php new file mode 100644 index 0000000..1bdacde --- /dev/null +++ b/app/Models/Faq.php @@ -0,0 +1,21 @@ + + */ + protected $fillable = [ + 'title', + 'description' + ]; +} diff --git a/database/factories/FaqFactory.php b/database/factories/FaqFactory.php new file mode 100644 index 0000000..d1ab4db --- /dev/null +++ b/database/factories/FaqFactory.php @@ -0,0 +1,24 @@ + + */ +class FaqFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'title' => fake()->sentence, + 'description' => fake()->paragraph + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 39de5cb..3de6259 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -23,6 +23,8 @@ public function definition(): array 'email' => fake()->unique()->safeEmail(), 'dni' => $this->faker->regexify('[0-9]{8}[A-Z]'), 'password' => Hash::make('password'), + 'status' => 'ACTIVE', + 'role' => 'ADMIN', 'email_verified_at' => now(), 'remember_token' => Str::random(10), ]; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 7e20284..60f512c 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -15,5 +15,6 @@ public function run(): void // \App\Models\User::factory(10)->create(); \App\Models\User::factory(3)->create(); + \App\Models\Faq::factory(3)->create(); } } diff --git a/routes/api.php b/routes/api.php index 09200be..1ebd212 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,6 +3,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use App\Http\Controllers\api\UserController; +use App\Http\Controllers\api\FaqController; use App\Http\Controllers\api\AuthController; /* @@ -19,6 +20,10 @@ Route::post('/register', [UserController::class, 'store'])->name('register'); Route::post('/login', [AuthController::class, 'login'])->name('login'); -Route::middleware('auth:sanctum')->get('/user', function (Request $request) { - return $request->user(); +Route::middleware(['auth:api'])->prefix('faqs')->group(function () { + Route::get('/', [FaqController::class, 'index']); + Route::get('/{id}', [FaqController::class, 'show']); + Route::post('/', [FaqController::class, 'store']); + Route::put('/{id}', [FaqController::class, 'update']); + Route::delete('/{id}', [FaqController::class, 'destroy']); }); diff --git a/tests/Feature/FaqsTest.php b/tests/Feature/FaqsTest.php new file mode 100644 index 0000000..fa0c223 --- /dev/null +++ b/tests/Feature/FaqsTest.php @@ -0,0 +1,378 @@ +create(); + $token = $user->createToken('TestToken')->accessToken; + + $faqs = [ + [ + 'title' => fake()->sentence, + 'description' => fake()->paragraph, + ], + [ + 'title' => fake()->sentence, + 'description' => fake()->paragraph, + ], + ]; + + Faq::insert($faqs); + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->get('/api/faqs'); + + $response->assertStatus(200) + ->assertJson([ + 'faqs' => $faqs + ]); + } + + /** + * Test index method without token. + */ + public function test_index_without_token(): void + { + \Artisan::call('passport:install'); + + $faqs = [ + [ + 'title' => fake()->sentence, + 'description' => fake()->paragraph, + ], + [ + 'title' => fake()->sentence, + 'description' => fake()->paragraph, + ], + ]; + + Faq::insert($faqs); + + $response = $this->get('/api/faqs'); + + $response->assertStatus(302); + } + + /** + * Test show method. + */ + public function test_show() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $faq = Faq::factory()->create(); + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->get('/api/faqs/' . $faq->id); + + $response->assertStatus(200) + ->assertJson([ + 'id' => $faq->id, + 'title' => $faq->title, + 'description' => $faq->description + ]); + } + + /** + * Test show method without token. + */ + public function test_show_without_token() + { + \Artisan::call('passport:install'); + + $faq = Faq::factory()->create(); + + $response = $this->get('/api/faqs/' . $faq->id); + + $response->assertStatus(302); + } + + /** + * Test store method. + */ + public function test_store() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $data = [ + 'title' => 'Test FAQ', + 'description' => 'This is a test FAQ' + ]; + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->post('/api/faqs', $data); + + $response->assertStatus(201) + ->assertJson([ + 'faq' => $data + ]); + + $this->assertDatabaseHas('faqs', $data); + } + + /** + * Test store method without token. + */ + public function test_store_without_token() + { + \Artisan::call('passport:install'); + + $data = [ + 'title' => 'Test FAQ', + 'description' => 'This is a test FAQ' + ]; + + $response = $this->post('/api/faqs', $data); + + $response->assertStatus(302); + } + + /** + * Test store method with missing title and/or description. + */ + public function test_store_with_missing_fields() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + // Missing title + $data1 = [ + 'description' => 'This is a test FAQ' + ]; + + $response1 = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->post('/api/faqs', $data1); + + $response1->assertStatus(422) + ->assertJsonValidationErrors(['title']); + + // Missing description + $data2 = [ + 'title' => 'Test FAQ' + ]; + + $response2 = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->post('/api/faqs', $data2); + + $response2->assertStatus(422) + ->assertJsonValidationErrors(['description']); + + // Missing title and description + $data3 = []; + + $response3 = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->post('/api/faqs', $data3); + + $response3->assertStatus(422) + ->assertJsonValidationErrors(['title', 'description']); + } + + /** + * Test store method with a too long title. + */ + public function test_store_with_long_title() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $data = [ + 'title' => fake()->paragraph . fake()->paragraph . fake()->paragraph, + 'description' => 'This is a test FAQ' + ]; + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->post('/api/faqs', $data); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['title']); + } + + /** + * Test update method. + */ + public function test_update() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $faq = Faq::factory()->create(); + + $data = [ + 'title' => 'Updated FAQ', + 'description' => 'This is an updated FAQ' + ]; + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->put('/api/faqs/' . $faq->id, $data); + + $response->assertStatus(200) + ->assertJson([ + 'message' => 'FAQ updated successfully' + ]); + + $this->assertDatabaseHas('faqs', $data); + } + + /** + * Test update method without token. + */ + public function test_update_without_token() + { + \Artisan::call('passport:install'); + + $faq = Faq::factory()->create(); + + $data = [ + 'title' => 'Updated FAQ', + 'description' => 'This is an updated FAQ' + ]; + + $response = $this->put('/api/faqs/' . $faq->id, $data); + + $response->assertStatus(302); + } + + /** + * Test update method with a non-existent FAQ. + */ + public function test_update_with_non_existent_id() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $data = [ + 'title' => 'Updated FAQ', + 'description' => 'This is an updated FAQ' + ]; + + $nonExistentId = 9999; + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->put('/api/faqs/' . $nonExistentId, $data); + + $response->assertStatus(404); + } + + /** + * Test update method with a too long title. + */ + public function test_update_with_long_title() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $faq = Faq::factory()->create(); + + $data = [ + 'title' => fake()->paragraph . fake()->paragraph . fake()->paragraph, + 'description' => 'This is an updated FAQ' + ]; + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->put('/api/faqs/' . $faq->id, $data); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['title']); + } + + /** + * Test destroy method. + */ + public function test_destroy() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $faqs = Faq::factory(3)->create(); + $faq = $faqs->first(); + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->delete('/api/faqs/' . $faq->id); + + $response->assertStatus(200) + ->assertJson([ + 'message' => 'FAQ deleted successfully' + ]); + + $this->assertDatabaseMissing('faqs', ['id' => $faq->id]); + } + + /** + * Test the destroy method without token. + */ + public function test_destroy_without_token() + { + \Artisan::call('passport:install'); + + $faq = Faq::factory()->create(); + + $response = $this->delete('/api/faqs/' . $faq->id); + + $response->assertStatus(302); + + $this->assertDatabaseHas('faqs', ['id' => $faq->id]); + } + + /** + * Test destroy method with a non-existent FAQ. + */ + public function test_destroy_with_non_existent_id() + { + \Artisan::call('passport:install'); + + $user = User::factory()->create(); + $token = $user->createToken('TestToken')->accessToken; + + $nonExistentId = 9999; + + $response = $this->actingAs($user)->withHeaders([ + 'Authorization' => 'Bearer ' . $token, + ])->delete('/api/faqs/' . $nonExistentId); + + $response->assertStatus(404); + } +} \ No newline at end of file