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:
devoalda 2023-08-12 11:15:31 +08:00
parent 1cf52e32ed
commit 1d9397e738
14 changed files with 638 additions and 58 deletions

View File

@ -38,7 +38,8 @@ class ApiAuthController extends Controller
return response()->json([ return response()->json([
'access_token' => $token, 'access_token' => $token,
'token_type' => 'Bearer', 'token_type' => 'Bearer',
]); ], 201
);
} }
} }
@ -52,11 +53,13 @@ class ApiAuthController extends Controller
$user = User::where('email', $request['email'])->firstOrFail(); $user = User::where('email', $request['email'])->firstOrFail();
$token = $user->createToken('auth_token')->plainTextToken; $token = $user->createToken('auth_token')
->plainTextToken;
return response()->json([ return response()->json([
'access_token' => $token, 'access_token' => $token,
'token_type' => 'Bearer', 'token_type' => 'Bearer',
'message' => 'Login successful, please remember to logout!'
]); ]);
} }
@ -64,4 +67,14 @@ class ApiAuthController extends Controller
{ {
return $request->user(); return $request->user();
} }
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json([
'message' => 'Logged out'
], 200
);
}
} }

View File

@ -22,16 +22,20 @@ use Illuminate\Support\Facades\Response;
class ProjectController extends Controller class ProjectController extends Controller
{ {
protected mixed $project_api_route_pattern = 'api/*';
/** /**
* Display Listing of all Projects. * Display Listing of all Projects.
* @throws AuthorizationException
*/ */
public function index(Request $request): View|Factory|Application|JsonResponse|ProjectResource public function index(Request $request): View|Factory|Application|JsonResponse|ProjectResource
{ {
// Check if API Call, get userID from request // Check if API Call, get userID from request
if ($request->is('api/*')) { if ($request->expectsJson()) {
$user = Auth::user(); $user = Auth::user();
$this->authorize('viewAny', Project::class);
$projects = $user->projects()->paginate(4); $projects = $user->projects()->paginate(4);
return new ProjectResource($projects); return new ProjectResource($projects);
} }
@ -77,13 +81,15 @@ class ProjectController extends Controller
$data = $request->validated(); $data = $request->validated();
// Check if API Call, get userID from request // Check if API Call, get userID from request
if ($request->is('api/*')) { if ($request->expectsJson()) {
$user = User::find($request->user_id); $user = Auth::user();
if (!$user) {
return response()->json(['error' => 'User not found'], 404); $this->authorize('create', Project::class);
}
$user->projects()->create($data); $user->projects()->create($data);
$data = $user->projects()->latest()->first();
return response()->json([ return response()->json([
'message' => 'Project created successfully', 'message' => 'Project created successfully',
'data' => $data, 'data' => $data,
@ -103,11 +109,8 @@ class ProjectController extends Controller
public function show(Request $request, $project_id): RedirectResponse|JsonResponse public function show(Request $request, $project_id): RedirectResponse|JsonResponse
{ {
// Check if API Call, get userID from request // Check if API Call, get userID from request
if ($request->is('api/*')) { if ($request->expectsJson()) {
$user = User::find($request->user_id); $user = Auth::user();
if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}
$project = $user->projects()->find($project_id); $project = $user->projects()->find($project_id);
@ -117,6 +120,8 @@ class ProjectController extends Controller
], 404); ], 404);
} }
$this->authorize('view', $project);
return response()->json([ return response()->json([
'message' => 'Project retrieved successfully', 'message' => 'Project retrieved successfully',
'data' => $project, 'data' => $project,
@ -152,11 +157,8 @@ class ProjectController extends Controller
$data = $request->validatedWithCompletedAt(); $data = $request->validatedWithCompletedAt();
// API Call // API Call
if ($request->is('api/*')) { if ($request->expectsJson()) {
$user = User::find($request->user_id); $user = Auth::user();
if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}
$project = $user->projects()->find($project_id); $project = $user->projects()->find($project_id);
@ -166,6 +168,8 @@ class ProjectController extends Controller
], 404); ], 404);
} }
$this->authorize('update', $project);
$project->update($data); $project->update($data);
return response()->json([ return response()->json([
@ -201,12 +205,8 @@ class ProjectController extends Controller
public function destroy($project_id, Request $request): RedirectResponse|JsonResponse public function destroy($project_id, Request $request): RedirectResponse|JsonResponse
{ {
// Check if API Call and $project_id is provided // Check if API Call and $project_id is provided
if ($request->is('api/*')) { if ($request->expectsJson() && $project_id) {
$user_id = $request->user_id; $user = Auth::user();
$user = User::find($user_id);
if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}
$project = $user->projects()->find($project_id); $project = $user->projects()->find($project_id);
@ -217,6 +217,8 @@ class ProjectController extends Controller
], 404); ], 404);
} }
$this->authorize('delete', $project);
$project->delete(); $project->delete();
return response()->json([ return response()->json([

View File

@ -4,13 +4,17 @@ namespace App\Http\Controllers;
use App\Http\Requests\Project\StoreTodoRequest; use App\Http\Requests\Project\StoreTodoRequest;
use App\Http\Requests\Project\UpdateTodoRequest; use App\Http\Requests\Project\UpdateTodoRequest;
use App\Http\Resources\TodoResource;
use App\Models\Todo; use App\Models\Todo;
use App\Models\User; use App\Models\User;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
class ProjectTodoController extends Controller class ProjectTodoController extends Controller
{ {
@ -19,13 +23,23 @@ class ProjectTodoController extends Controller
/** /**
* Display a listing of all Todos for a Project. * 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); $user = Auth::user();
$projects = $user->projects; $projects = $user->projects();
$project = $projects->find($project_id); $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() return back()
->with('error', 'Project not found'); ->with('error', 'Project not found');
@ -43,7 +57,7 @@ class ProjectTodoController extends Controller
*/ */
public function create($project_id): Factory|View|Application 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', [ return view('todo.create', [
'project' => $project, 'project' => $project,
]); ]);
@ -52,33 +66,55 @@ class ProjectTodoController extends Controller
/** /**
* Store a newly created Todo in storage. * 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]); $this->authorize('create', [Todo::class, $user]);
$validatedData = $request->validated();
$project = $user->projects->find($project_id);
// Add the Todo to the Project // Add the Todo to the Project
$project->todos()->save(new Todo($validatedData)); $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) return redirect()->route('project.todo.index', $project_id)
->with('success', 'Todo created successfully.'); ->with('success', 'Todo created successfully.');
} }
/** /**
* Display the specified resource. * Display the specified resource.
*/ */
public function show($project_id, Todo $todo) public function show(Request $request, $project_id)
{ {
$user = User::find(auth()->user()->id); if ($request->expectsJson()) {
$projects = $user->projects; $user = Auth::user();
$project = $projects->find($project_id); $project = $user->projects->find($project_id);
$todo = $project->todos->find($request->todo_id);
$this->authorize('view', [Todo::class, $project, $todo]); $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) public function update($project_id, UpdateTodoRequest $request, Todo $todo)
{ {
$project = auth()->user()->projects->find($project_id); $project = auth()->user()->projects->find($project_id);
$this->authorize('update', [Todo::class, $project, $todo]); $this->authorize('update', [Todo::class, $project, $todo]);
// Update other fields // Update other fields
$todo->fill($request->validated()); $todo->fill($request->validated());
$todo->due_start = $request->due_start ? $dueStart = $request->due_start ? strtotime(Carbon::parse($request->due_start)) : null;
strtotime(Carbon::parse($request->due_start)) : $dueEnd = $request->due_end ? strtotime(Carbon::parse($request->due_end)) : null;
($todo->due_start ?: null);
$todo->due_end = $request->due_end ? if ($dueEnd === null && $dueStart !== null) {
strtotime(Carbon::parse($request->due_end)) : $dueEnd = strtotime(Carbon::parse($todo->due_start));
($todo->due_end ?: }
($todo->due_start ? strtotime(Carbon::parse($todo->due_start)) : null));
$todo->due_start = $dueStart;
$todo->due_end = $dueEnd;
$todo->save(); $todo->save();
return back() if ($request->expectsJson()) {
->with('success', 'Todo updated successfully'); return response()->json([
'message' => 'Todo updated successfully',
'data' => $todo,
], 200);
}
return back()->with('success', 'Todo updated successfully');
} }
/** /**
* Remove the specified resource from storage. * 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]); $this->authorize('delete', [Todo::class, $todo]);
$todo->delete(); $todo->delete();
if (request()->expectsJson()) {
return response()->json([
'message' => 'Todo deleted successfully',
], 200);
}
return redirect()->route('project.todo.index', $project_id) return redirect()->route('project.todo.index', $project_id)
->with('success', 'Todo deleted successfully'); ->with('success', 'Todo deleted successfully');
} }

View File

@ -16,6 +16,9 @@ class ProjectResource extends ResourceCollection
{ {
return [ return [
'data' => $this->collection, 'data' => $this->collection,
'links' => [
'self' => 'link-value',
],
]; ];
} }
} }

View File

@ -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',
],
];
}
}

View File

@ -13,7 +13,7 @@ class ProjectPolicy
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
// return true;
} }
/** /**

View File

@ -14,7 +14,7 @@ class TodoPolicy
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
return false; return true;
} }
/** /**

View File

@ -46,7 +46,7 @@ return [
| |
*/ */
'expiration' => null, 'expiration' => 525600,
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View File

@ -1,6 +1,7 @@
@foreach($projects as $project) @foreach($projects as $project)
<div class="relative"> <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 <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"> 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"> <div class="text-gray-800 dark:text-gray-100">

View File

@ -77,7 +77,7 @@
<!-- Completed Todos Section --> <!-- Completed Todos Section -->
<div class="bg-white dark:bg-gray-800 shadow-sm rounded-lg"> <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"> <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> </h2>
<div class="space-y-4"> <div class="space-y-4">
@foreach ($completed as $todo) @foreach ($completed as $todo)
@ -140,8 +140,16 @@
<p class="text-sm text-green-600">{{ $timeRemaining }} ago</p> <p class="text-sm text-green-600">{{ $timeRemaining }} ago</p>
@endif @endif
@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> </div>
@endif
</a> </a>
@endforeach @endforeach
</div> </div>

View File

@ -2,6 +2,7 @@
use App\Http\Controllers\ProjectController; use App\Http\Controllers\ProjectController;
use App\Http\Controllers\Auth\ApiAuthController; use App\Http\Controllers\Auth\ApiAuthController;
use App\Http\Controllers\ProjectTodoController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -20,13 +21,15 @@ Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return $request->user(); return $request->user();
}); });
// Auth Group to ApiAuthController
Route::post('/register', [ApiAuthController::class, 'register']); Route::post('/register', [ApiAuthController::class, 'register']);
Route::post('/login', [ApiAuthController::class, 'login']); Route::post('/login', [ApiAuthController::class, 'login']);
// Resources route to /api/projects // Resources route to /api/projects
Route::middleware('auth:sanctum')->group( function () { Route::middleware('auth:sanctum')->group( function () {
Route::post('/logout', [ApiAuthController::class, 'logout']);
Route::get('/me', [ApiAuthController::class, 'me']); Route::get('/me', [ApiAuthController::class, 'me']);
Route::apiResource('projects', ProjectController::class); Route::apiResource('project', ProjectController::class);
Route::apiResource('project.todo', ProjectTodoController::class);
}); });

View File

@ -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',
]);
}
}

View File

@ -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',
];
}
}

View File

@ -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();
}
}