mirror of https://github.com/Devoalda/LaDo.git
feature(Todo API):
Added: - API Support for Todos - Todo API tests Modified: - API Authentication tests - Minor Updates to Policies - Minor Updates to Views
This commit is contained in:
parent
1cf52e32ed
commit
1d9397e738
|
@ -38,7 +38,8 @@ class ApiAuthController extends Controller
|
|||
return response()->json([
|
||||
'access_token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
]);
|
||||
], 201
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,11 +53,13 @@ class ApiAuthController extends Controller
|
|||
|
||||
$user = User::where('email', $request['email'])->firstOrFail();
|
||||
|
||||
$token = $user->createToken('auth_token')->plainTextToken;
|
||||
$token = $user->createToken('auth_token')
|
||||
->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'access_token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
'message' => 'Login successful, please remember to logout!'
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -64,4 +67,14 @@ class ApiAuthController extends Controller
|
|||
{
|
||||
return $request->user();
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
return response()->json([
|
||||
'message' => 'Logged out'
|
||||
], 200
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,16 +22,20 @@ use Illuminate\Support\Facades\Response;
|
|||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
protected mixed $project_api_route_pattern = 'api/*';
|
||||
/**
|
||||
* Display Listing of all Projects.
|
||||
* @throws AuthorizationException
|
||||
*/
|
||||
public function index(Request $request): View|Factory|Application|JsonResponse|ProjectResource
|
||||
{
|
||||
// Check if API Call, get userID from request
|
||||
if ($request->is('api/*')) {
|
||||
if ($request->expectsJson()) {
|
||||
$user = Auth::user();
|
||||
|
||||
$this->authorize('viewAny', Project::class);
|
||||
$projects = $user->projects()->paginate(4);
|
||||
|
||||
return new ProjectResource($projects);
|
||||
}
|
||||
|
||||
|
@ -77,13 +81,15 @@ class ProjectController extends Controller
|
|||
$data = $request->validated();
|
||||
|
||||
// Check if API Call, get userID from request
|
||||
if ($request->is('api/*')) {
|
||||
$user = User::find($request->user_id);
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'User not found'], 404);
|
||||
}
|
||||
if ($request->expectsJson()) {
|
||||
$user = Auth::user();
|
||||
|
||||
$this->authorize('create', Project::class);
|
||||
|
||||
$user->projects()->create($data);
|
||||
|
||||
$data = $user->projects()->latest()->first();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Project created successfully',
|
||||
'data' => $data,
|
||||
|
@ -103,11 +109,8 @@ class ProjectController extends Controller
|
|||
public function show(Request $request, $project_id): RedirectResponse|JsonResponse
|
||||
{
|
||||
// Check if API Call, get userID from request
|
||||
if ($request->is('api/*')) {
|
||||
$user = User::find($request->user_id);
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'User not found'], 404);
|
||||
}
|
||||
if ($request->expectsJson()) {
|
||||
$user = Auth::user();
|
||||
|
||||
$project = $user->projects()->find($project_id);
|
||||
|
||||
|
@ -117,6 +120,8 @@ class ProjectController extends Controller
|
|||
], 404);
|
||||
}
|
||||
|
||||
$this->authorize('view', $project);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Project retrieved successfully',
|
||||
'data' => $project,
|
||||
|
@ -152,11 +157,8 @@ class ProjectController extends Controller
|
|||
$data = $request->validatedWithCompletedAt();
|
||||
|
||||
// API Call
|
||||
if ($request->is('api/*')) {
|
||||
$user = User::find($request->user_id);
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'User not found'], 404);
|
||||
}
|
||||
if ($request->expectsJson()) {
|
||||
$user = Auth::user();
|
||||
|
||||
$project = $user->projects()->find($project_id);
|
||||
|
||||
|
@ -166,6 +168,8 @@ class ProjectController extends Controller
|
|||
], 404);
|
||||
}
|
||||
|
||||
$this->authorize('update', $project);
|
||||
|
||||
$project->update($data);
|
||||
|
||||
return response()->json([
|
||||
|
@ -201,12 +205,8 @@ class ProjectController extends Controller
|
|||
public function destroy($project_id, Request $request): RedirectResponse|JsonResponse
|
||||
{
|
||||
// Check if API Call and $project_id is provided
|
||||
if ($request->is('api/*')) {
|
||||
$user_id = $request->user_id;
|
||||
$user = User::find($user_id);
|
||||
if (!$user) {
|
||||
return response()->json(['error' => 'User not found'], 404);
|
||||
}
|
||||
if ($request->expectsJson() && $project_id) {
|
||||
$user = Auth::user();
|
||||
|
||||
$project = $user->projects()->find($project_id);
|
||||
|
||||
|
@ -217,6 +217,8 @@ class ProjectController extends Controller
|
|||
], 404);
|
||||
}
|
||||
|
||||
$this->authorize('delete', $project);
|
||||
|
||||
$project->delete();
|
||||
|
||||
return response()->json([
|
||||
|
|
|
@ -4,13 +4,17 @@ namespace App\Http\Controllers;
|
|||
|
||||
use App\Http\Requests\Project\StoreTodoRequest;
|
||||
use App\Http\Requests\Project\UpdateTodoRequest;
|
||||
use App\Http\Resources\TodoResource;
|
||||
use App\Models\Todo;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProjectTodoController extends Controller
|
||||
{
|
||||
|
@ -19,13 +23,23 @@ class ProjectTodoController extends Controller
|
|||
/**
|
||||
* Display a listing of all Todos for a Project.
|
||||
*/
|
||||
public function index($project_id): Factory|Application|View|\Illuminate\Contracts\Foundation\Application|RedirectResponse
|
||||
public function index(Request $request, $project_id):
|
||||
Factory|Application|View|\Illuminate\Contracts\Foundation\Application|RedirectResponse|TodoResource
|
||||
{
|
||||
$user = User::find(auth()->user()->id);
|
||||
$projects = $user->projects;
|
||||
$user = Auth::user();
|
||||
$projects = $user->projects();
|
||||
$project = $projects->find($project_id);
|
||||
|
||||
if (!$project || $project->user->id !== auth()->user()->id)
|
||||
if ($request->expectsJson()) {
|
||||
$this->authorize('viewAny', [Todo::class, $project]);
|
||||
|
||||
$todos = $project->todos()->paginate(4);
|
||||
|
||||
return new TodoResource($todos);
|
||||
}
|
||||
|
||||
|
||||
if (!$project || $project->user->id !== $user->id)
|
||||
return back()
|
||||
->with('error', 'Project not found');
|
||||
|
||||
|
@ -43,7 +57,7 @@ class ProjectTodoController extends Controller
|
|||
*/
|
||||
public function create($project_id): Factory|View|Application
|
||||
{
|
||||
$project = auth()->user()->projects->find($project_id);
|
||||
$project = Auth::user()->projects->find($project_id);
|
||||
return view('todo.create', [
|
||||
'project' => $project,
|
||||
]);
|
||||
|
@ -52,33 +66,55 @@ class ProjectTodoController extends Controller
|
|||
/**
|
||||
* Store a newly created Todo in storage.
|
||||
*/
|
||||
public function store($project_id, StoreTodoRequest $request): RedirectResponse
|
||||
public function store($project_id, StoreTodoRequest $request): RedirectResponse|JsonResponse
|
||||
{
|
||||
$user = User::find(auth()->user()->id);
|
||||
$validatedData = $request->validated();
|
||||
$user = Auth::user();
|
||||
$project = $user->projects->find($project_id);
|
||||
|
||||
$this->authorize('create', [Todo::class, $user]);
|
||||
|
||||
$validatedData = $request->validated();
|
||||
|
||||
$project = $user->projects->find($project_id);
|
||||
// Add the Todo to the Project
|
||||
$project->todos()->save(new Todo($validatedData));
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'message' => 'Todo created successfully.',
|
||||
'data' => $project->todos()->latest()->first(),
|
||||
], 201);
|
||||
}
|
||||
|
||||
return redirect()->route('project.todo.index', $project_id)
|
||||
->with('success', 'Todo created successfully.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show($project_id, Todo $todo)
|
||||
public function show(Request $request, $project_id)
|
||||
{
|
||||
$user = User::find(auth()->user()->id);
|
||||
$projects = $user->projects;
|
||||
$project = $projects->find($project_id);
|
||||
if ($request->expectsJson()) {
|
||||
$user = Auth::user();
|
||||
$project = $user->projects->find($project_id);
|
||||
$todo = $project->todos->find($request->todo_id);
|
||||
|
||||
$this->authorize('view', [Todo::class, $project, $todo]);
|
||||
|
||||
return view('todo.show', compact('project', 'todo'));
|
||||
return response()->json([
|
||||
'message' => 'Todo retrieved successfully.',
|
||||
'data' => $todo,
|
||||
], 200);
|
||||
}
|
||||
|
||||
return redirect()->route('project.todo.index', $project_id);
|
||||
// $user = Auth::user();
|
||||
// $projects = $user->projects;
|
||||
// $project = $projects->find($project_id);
|
||||
//
|
||||
// $this->authorize('view', [Todo::class, $project, $project->todos->find($request->todo_id)]);
|
||||
//
|
||||
// return view('todo.show', compact('project', 'todo'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,37 +135,49 @@ class ProjectTodoController extends Controller
|
|||
public function update($project_id, UpdateTodoRequest $request, Todo $todo)
|
||||
{
|
||||
$project = auth()->user()->projects->find($project_id);
|
||||
|
||||
$this->authorize('update', [Todo::class, $project, $todo]);
|
||||
|
||||
// Update other fields
|
||||
$todo->fill($request->validated());
|
||||
|
||||
$todo->due_start = $request->due_start ?
|
||||
strtotime(Carbon::parse($request->due_start)) :
|
||||
($todo->due_start ?: null);
|
||||
$dueStart = $request->due_start ? strtotime(Carbon::parse($request->due_start)) : null;
|
||||
$dueEnd = $request->due_end ? strtotime(Carbon::parse($request->due_end)) : null;
|
||||
|
||||
$todo->due_end = $request->due_end ?
|
||||
strtotime(Carbon::parse($request->due_end)) :
|
||||
($todo->due_end ?:
|
||||
($todo->due_start ? strtotime(Carbon::parse($todo->due_start)) : null));
|
||||
if ($dueEnd === null && $dueStart !== null) {
|
||||
$dueEnd = strtotime(Carbon::parse($todo->due_start));
|
||||
}
|
||||
|
||||
$todo->due_start = $dueStart;
|
||||
$todo->due_end = $dueEnd;
|
||||
|
||||
$todo->save();
|
||||
|
||||
return back()
|
||||
->with('success', 'Todo updated successfully');
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json([
|
||||
'message' => 'Todo updated successfully',
|
||||
'data' => $todo,
|
||||
], 200);
|
||||
}
|
||||
|
||||
return back()->with('success', 'Todo updated successfully');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy($project_id, Todo $todo): RedirectResponse
|
||||
public function destroy($project_id, Request $request, Todo $todo): RedirectResponse|JsonResponse
|
||||
{
|
||||
$this->authorize('delete', [Todo::class, $todo]);
|
||||
|
||||
$todo->delete();
|
||||
|
||||
if (request()->expectsJson()) {
|
||||
return response()->json([
|
||||
'message' => 'Todo deleted successfully',
|
||||
], 200);
|
||||
}
|
||||
|
||||
return redirect()->route('project.todo.index', $project_id)
|
||||
->with('success', 'Todo deleted successfully');
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ class ProjectResource extends ResourceCollection
|
|||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'links' => [
|
||||
'self' => 'link-value',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class TodoResource extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
'links' => [
|
||||
'self' => 'link-value',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ class ProjectPolicy
|
|||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
//
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,7 @@ class TodoPolicy
|
|||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -46,7 +46,7 @@ return [
|
|||
|
|
||||
*/
|
||||
|
||||
'expiration' => null,
|
||||
'expiration' => 525600,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@foreach($projects as $project)
|
||||
<div class="relative">
|
||||
<a href="{{ route('project.todo.index', $project) }}" class="card-link">
|
||||
<a href="{{ route('project.todo.index', $project->id) }}"
|
||||
class="card-link">
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6 hover:shadow-md transition duration-300 ease-in-out transform hover:-translate-y-1">
|
||||
<div class="text-gray-800 dark:text-gray-100">
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
<!-- Completed Todos Section -->
|
||||
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg">
|
||||
<h2 class="text-2xl font-semibold mb-4 text-green-500 dark:text-green-400 px-6 pt-6">
|
||||
Completed Today ({{ $completed->count() }})
|
||||
Completed ({{ $completed->count() }})
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@foreach ($completed as $todo)
|
||||
|
@ -140,8 +140,16 @@
|
|||
<p class="text-sm text-green-600">{{ $timeRemaining }} ago</p>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<!-- Completed at time -->
|
||||
@if ($todo->completed_at)
|
||||
<!-- Print at the right side of the div -->
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 absolute top-0 right-0 mr-6">
|
||||
Completed {{ \Carbon\Carbon::parse($todo->completed_at)->diffForHumans() }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use App\Http\Controllers\ProjectController;
|
||||
use App\Http\Controllers\Auth\ApiAuthController;
|
||||
use App\Http\Controllers\ProjectTodoController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
|
@ -20,13 +21,15 @@ Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
|
|||
return $request->user();
|
||||
});
|
||||
|
||||
// Auth Group to ApiAuthController
|
||||
Route::post('/register', [ApiAuthController::class, 'register']);
|
||||
|
||||
Route::post('/login', [ApiAuthController::class, 'login']);
|
||||
|
||||
|
||||
// Resources route to /api/projects
|
||||
Route::middleware('auth:sanctum')->group( function () {
|
||||
Route::post('/logout', [ApiAuthController::class, 'logout']);
|
||||
Route::get('/me', [ApiAuthController::class, 'me']);
|
||||
Route::apiResource('projects', ProjectController::class);
|
||||
Route::apiResource('project', ProjectController::class);
|
||||
Route::apiResource('project.todo', ProjectTodoController::class);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\API;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuthTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected array $user = [
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@mail.com',
|
||||
'password' => 'password1234',
|
||||
];
|
||||
protected array $auth_struct = [
|
||||
'access_token',
|
||||
'token_type',
|
||||
];
|
||||
|
||||
protected string $registerRoute = '/api/register';
|
||||
protected string $loginRoute = '/api/login';
|
||||
protected string $meRoute = '/api/me';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->withHeaders([
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function registerAndLoginUser(): string
|
||||
{
|
||||
$this->postJson($this->registerRoute, $this->user);
|
||||
|
||||
$response = $this->postJson($this->loginRoute, [
|
||||
'email' => $this->user['email'],
|
||||
'password' => $this->user['password'],
|
||||
]);
|
||||
|
||||
return $response['access_token'];
|
||||
}
|
||||
|
||||
public function test_user_can_register_with_api(): void
|
||||
{
|
||||
$response = $this->postJson($this->registerRoute, $this->user);
|
||||
|
||||
$response->assertCreated()->assertJsonStructure($this->auth_struct);
|
||||
}
|
||||
|
||||
public function test_user_can_authenticate_with_api(): void
|
||||
{
|
||||
$this->test_user_can_register_with_api();
|
||||
|
||||
$response = $this->postJson($this->loginRoute, [
|
||||
'email' => $this->user['email'],
|
||||
'password' => $this->user['password'],
|
||||
]);
|
||||
|
||||
$response->assertOk()->assertJsonStructure($this->auth_struct);
|
||||
}
|
||||
|
||||
public function test_user_can_view_me(): void
|
||||
{
|
||||
$token = $this->registerAndLoginUser();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
])->getJson($this->meRoute);
|
||||
|
||||
$response->assertOk()->assertJsonStructure([
|
||||
'id',
|
||||
'name',
|
||||
'email',
|
||||
'email_verified_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\API;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProjectTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected array $user = [
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@test.com',
|
||||
'password' => 'password1234'
|
||||
];
|
||||
|
||||
private string $project_route = '/api/project';
|
||||
private $access_token;
|
||||
private $user_id;
|
||||
private $project_id;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Register user
|
||||
$this->registerUser();
|
||||
|
||||
// Authenticate user
|
||||
$this->authenticateUser();
|
||||
}
|
||||
|
||||
protected function registerUser(): void
|
||||
{
|
||||
$response = $this->postJson('/api/register', $this->user);
|
||||
$response->assertCreated();
|
||||
}
|
||||
|
||||
protected function authenticateUser(): void
|
||||
{
|
||||
$response = $this->postJson('/api/login', [
|
||||
'email' => $this->user['email'],
|
||||
'password' => $this->user['password'],
|
||||
]);
|
||||
$response->assertOk();
|
||||
|
||||
$this->access_token = $response['access_token'];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->access_token,
|
||||
])->getJson('/api/me');
|
||||
$response->assertOk();
|
||||
|
||||
$this->user_id = $response['id'];
|
||||
}
|
||||
|
||||
public function test_user_can_view_all_projects(): void
|
||||
{
|
||||
$response = $this->getJson($this->project_route);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => $this->getProjectJsonStructure(),
|
||||
],
|
||||
'links' => [
|
||||
'first',
|
||||
'last',
|
||||
'prev',
|
||||
'next',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_can_create_project(): void
|
||||
{
|
||||
$response = $this->postJson($this->project_route, [
|
||||
'name' => 'Test Project',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
|
||||
|
||||
$response->assertCreated()
|
||||
->assertJsonStructure([
|
||||
'data' => $this->getProjectJsonStructure(),
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('projects', [
|
||||
'name' => 'Test Project',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('project_user', [
|
||||
'user_id' => $this->user_id,
|
||||
'project_id' => $response['data']['id'],
|
||||
]);
|
||||
|
||||
$this->project_id = $response['data']['id'];
|
||||
}
|
||||
|
||||
public function test_user_can_get_created_project_from_api(): void
|
||||
{
|
||||
$this->test_user_can_create_project();
|
||||
|
||||
$response = $this->getJson($this->project_route . '/' . $this->project_id);
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
public function test_user_can_delete_created_project_from_api(): void
|
||||
{
|
||||
$this->test_user_can_create_project();
|
||||
|
||||
$response = $this->deleteJson($this->project_route . '/' . $this->project_id);
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
|
||||
protected function getProjectJsonStructure()
|
||||
{
|
||||
return [
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\API;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Todo;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TodoTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected array $user = [
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@test.com',
|
||||
'password' => 'password1234'
|
||||
];
|
||||
|
||||
private string $project_route = '/api/project/';
|
||||
private $access_token;
|
||||
private $user_id;
|
||||
private $project_id;
|
||||
private $todo_id;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Register user
|
||||
$this->registerUser();
|
||||
|
||||
// Authenticate user
|
||||
$this->authenticateUser();
|
||||
}
|
||||
|
||||
protected function registerUser(): void
|
||||
{
|
||||
$response = $this->postJson('/api/register', $this->user);
|
||||
$response->assertCreated();
|
||||
}
|
||||
|
||||
protected function authenticateUser(): void
|
||||
{
|
||||
$response = $this->postJson('/api/login', [
|
||||
'email' => $this->user['email'],
|
||||
'password' => $this->user['password'],
|
||||
]);
|
||||
$response->assertOk();
|
||||
|
||||
$this->access_token = $response['access_token'];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $this->access_token,
|
||||
])->getJson('/api/me');
|
||||
$response->assertOk();
|
||||
|
||||
$this->user_id = $response['id'];
|
||||
}
|
||||
|
||||
public function create_project_and_todo(): void
|
||||
{
|
||||
$this->actingAs(User::find($this->user_id));
|
||||
|
||||
// Create a project through POST and store it in the project property
|
||||
$response = $this->post(route('project.store'), [
|
||||
'name' => 'Test Project',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
$response->assertRedirect(route('project.index'));
|
||||
$this->assertDatabaseHas('projects', [
|
||||
'name' => 'Test Project',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('project_user', [
|
||||
'project_id' => Project::where('name', 'Test Project')->first()->id,
|
||||
'user_id' => $this->user_id,
|
||||
]);
|
||||
|
||||
$this->project_id = Project::where('name', 'Test Project')->first()->id;
|
||||
|
||||
$response = $this->post(route('project.todo.store', $this->project_id), [
|
||||
'title' => 'Test Todo',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
|
||||
$response->assertRedirect(route('project.todo.index', $this->project_id));
|
||||
|
||||
$this->todo_id = Todo::where('title', 'Test Todo')->first()->id;
|
||||
|
||||
$this->assertDatabaseHas('todos', [
|
||||
'title' => 'Test Todo',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('project_todo', [
|
||||
'project_id' => $this->project_id,
|
||||
'todo_id' => $this->todo_id,
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function test_user_can_see_todo_of_project(): void
|
||||
{
|
||||
$this->create_project_and_todo();
|
||||
|
||||
$response = $this->getJson($this->project_route . $this->project_id . '/todo');
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
],
|
||||
'links' => [
|
||||
'first',
|
||||
'last',
|
||||
'prev',
|
||||
'next',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_can_create_todo(): void
|
||||
{
|
||||
$this->create_project_and_todo();
|
||||
|
||||
$response = $this->postJson($this->project_route . $this->project_id . '/todo', [
|
||||
'title' => 'Test Todo',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
|
||||
$response->assertCreated()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('todos', [
|
||||
'title' => 'Test Todo',
|
||||
'description' => 'Test Description',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('project_todo', [
|
||||
'project_id' => $this->project_id,
|
||||
'todo_id' => $response['data']['id'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_can_edit_todo(): void
|
||||
{
|
||||
$this->create_project_and_todo();
|
||||
|
||||
$response = $this->putJson($this->project_route . $this->project_id . '/todo/' . $this->todo_id, [
|
||||
'title' => 'Updated Todo',
|
||||
'description' => 'Updated Description',
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_complete_todo()
|
||||
{
|
||||
$this->create_project_and_todo();
|
||||
|
||||
$response = $this->putJson($this->project_route . $this->project_id . '/todo/' . $this->todo_id,
|
||||
[
|
||||
'completed_at' => 'on',
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'completed_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_can_uncomplete_todo(): void
|
||||
{
|
||||
$this->create_project_and_todo();
|
||||
|
||||
$response = $this->putJson($this->project_route . $this->project_id . '/todo/' . $this->todo_id,
|
||||
[
|
||||
'completed_at' => 'on',
|
||||
]
|
||||
);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'completed_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
]);
|
||||
|
||||
$response = $this->putJson($this->project_route . $this->project_id . '/todo/' . $this->todo_id,
|
||||
[]
|
||||
);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonStructure([
|
||||
'data' => [
|
||||
'id',
|
||||
'title',
|
||||
'description',
|
||||
'completed_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
],
|
||||
]);
|
||||
|
||||
// Check database for "completed_at" is null
|
||||
$this->assertDatabaseHas('todos', [
|
||||
'id' => $this->todo_id,
|
||||
'completed_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_user_can_delete_todo(): void
|
||||
{
|
||||
$this->create_project_and_todo();
|
||||
|
||||
$response = $this->deleteJson($this->project_route . $this->project_id . '/todo/' . $this->todo_id);
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue