Skip to content
This repository has been archived by the owner on Apr 23, 2024. It is now read-only.

Commit

Permalink
feat: Cart management (#19)
Browse files Browse the repository at this point in the history
Closes #7
  • Loading branch information
Iltotore authored Mar 25, 2024
1 parent 6bf652b commit f0345a3
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 0 deletions.
62 changes: 62 additions & 0 deletions app/Http/Controllers/CartController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;

class CartController extends Controller {

/**
* Add a product in given amount to cart.
*
* @param Request $request
* @return Response
*/
public function add(Request $request): Response {
$args = $request->validate([
"product" => ["required", "exists:products,id"],
"amount" => ["required", "numeric", "gt:0"]
]);

Auth::user()->addCartItem($args["product"], $args["amount"]);

return response(status: 200);
}

/**
* Set product amount in cart.
*
* @param Request $request
* @return Response
*/
public function set(Request $request): Response {
$args = $request->validate([
"product" => ["required", "exists:products,id"],
"amount" => ["required", "numeric", "gt:0"]
]);

Auth::user()->setCartItem($args["product"], $args["amount"]);

return response(status: 200);
}

/**
* Remove product from cart.
*
* @param Request $request
* @return Response
*/
public function remove(Request $request): Response {
$args = $request->validate([
"product" => ["required", "exists:products,id", ]
]);

Auth::user()->removeCartItem($args["product"]);

return response(status: 200);
}
}
66 changes: 66 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\DB;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
Expand Down Expand Up @@ -51,6 +52,71 @@ public function cart(): BelongsToMany {
->withPivot(["amount"]);
}

/**
* Get items in this user's cart.
*
* @return array the products added to cart with their amount
*/
public function getCartItems(): array {
return $this->cart()->getResults()->all();
}

/**
* Get quantity of given product in cart.
*
* @param Product|int $product the product type
* @return int the amount of `$product` in this user's cart
*/
public function getAmount(Product|int $product): int {
$result = $this->cart()->find($product);
return $result ? $result->pivot->amount : 0;
}

/**
* Add a product in given quantity to cart.
*
* @param Product|int $product the product type to add
* @param int $amount the amount of the given product to add
* @return void
*/
public function addCartItem(Product|int $product, int $amount): void {
$validAmount = max($amount, 0);
$existing = $this->cart()->find($product);
if($existing == null) $this->cart()->attach($product, ["amount" => $validAmount]);
else {
$existing->pivot->update(["amount" => DB::raw("amount+".$validAmount)]);
}
}

/**
* Set a product in given quantity in this user's cart.
*
* @param Product|int $product the product type to set
* @param int $amount the amount of the given product to set
* @return void
*/
public function setCartItem(Product|int $product, int $amount): void {
$validAmount = max($amount, 0);
$existing = $this->cart()->find($product);
if($existing == null) $this->cart()->attach($product, ["amount" => $validAmount]);
else {
$existing->pivot->update(["amount" => $validAmount]);
}
}

/**
* Remove a product from this user's cart, no matter how much is present.
*
* @param Product|int $product the product type to remove
* @return void
*/
public function removeCartItem(Product|int $product): void {
$this->cart()->detach($product);
}

/**
* The "login" column used for authentication.
*/
public function username() {
return "name";
}
Expand Down
5 changes: 5 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use App\Http\Controllers\AuthController;
use App\Http\Controllers\CartController;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth;

Expand All @@ -22,3 +23,7 @@
Route::post("/auth/login", [AuthController::class, "login"]);
Route::post("/auth/logout", [AuthController::class, "logout"]);
Route::post("/auth/register", [AuthController::class, "register"]);

Route::post("/cart/add", [CartController::class, "add"]);
Route::post("/cart/set", [CartController::class, "set"]);
Route::post("/cart/remove", [CartController::class, "remove"]);
152 changes: 152 additions & 0 deletions tests/Feature/CartTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

namespace Tests\Feature;

use App\Models\Product;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Auth;
use Tests\TestCase;

class CartTest extends TestCase {
use RefreshDatabase;

/**
* Return errors when sent data are either missing or invalid.
*/
public function test_add_invalid_data(): void {
$user = User::factory()->create();

Auth::login($user);

$missingData = $this->postJson("/cart/add");
$missingData->assertInvalid(["product", "amount"]);

$invalidData = $this->postJson(
"/cart/add",
[
"product" => 1,
"amount" => -5
]
);

$invalidData->assertInvalid(["product", "amount"]);
}

/**
* Successfully add item when valid arguments are given.
*/
public function test_add_successful() {
$user = User::factory()->create();
$product = Product::factory()->create();

Auth::login($user);

$response = $this->postJson(
"/cart/add",
[
"product" => $product->id,
"amount" => 5
]
);

$response->assertSuccessful();
$this->assertEquals(5, $user->cart()->find($product)->pivot->amount);

$response = $this->postJson(
"/cart/add",
[
"product" => $product->id,
"amount" => 5
]
);

$response->assertSuccessful();
$this->assertEquals(10, $user->cart()->find($product)->pivot->amount);
}

/**
* Return errors when sent data are either missing or invalid.
*/
public function test_set_invalid_data(): void {
$user = User::factory()->create();

Auth::login($user);

$missingData = $this->postJson("/cart/set");
$missingData->assertInvalid(["product", "amount"]);

$invalidData = $this->postJson(
"/cart/set",
[
"product" => 1,
"amount" => -5
]
);

$invalidData->assertInvalid(["product", "amount"]);
}

/**
* Successfully set item when valid arguments are given.
*/
public function test_set_successful() {
$user = User::factory()->create();
$product = Product::factory()->create();

Auth::login($user);

$response = $this->postJson(
"/cart/set",
[
"product" => $product->id,
"amount" => 5
]
);

$response->assertSuccessful();
$this->assertEquals(5, $user->cart()->find($product)->pivot->amount);
}

/**
* Return errors when sent data are either missing or invalid.
*/
public function test_remove_invalid_data(): void {
$user = User::factory()->create();

Auth::login($user);

$missingData = $this->postJson("/cart/remove");
$missingData->assertInvalid(["product"]);

$invalidData = $this->postJson(
"/cart/set",
[
"product" => 1
]
);

$invalidData->assertInvalid(["product"]);
}

/**
* Successfully remove item from cart when valid arguments are given.
*/
public function test_remove_successful() {
$user = User::factory()->create();
$product = Product::factory()->create();

Auth::login($user);

$response = $this->postJson(
"/cart/remove",
[
"product" => $product->id,
]
);

$response->assertSuccessful();
$this->assertEquals(null, $user->cart()->find($product));
}
}
77 changes: 77 additions & 0 deletions tests/Unit/CartTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Tests\Unit;

use App\Models\Product;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use function PHPUnit\Framework\assertEquals;
use function PHPUnit\Framework\assertTrue;

class CartTest extends TestCase {
use RefreshDatabase;

/**
* Get user's cart items.
*/
public function test_get_items() {
$products = Product::factory()->count(5)->create();
$user = User::factory()->create();

foreach ($products as $product) {
$user->cart()->attach($product, ["amount" => 1]);
}

foreach ($user->getCartItems() as $item) {
$exists = false;

foreach ($products as $product) {
if($product->id == $item->id) {
$exists = true;
break;
}
}

assertTrue($exists);
}
}

/**
* Add product to cart with given amount and increase it if it exists.
*/
public function test_add_item(): void {
$user = User::factory()->create();
$product = Product::factory()->create();

$user->addCartItem($product, 5);
$this->assertEquals(5, $user->getAmount($product));

$user->addCartItem($product, 5);
$this->assertEquals(10, $user->getAmount($product));
}

/**
* Set product with given amount in cart.
*/
public function test_set_item(): void {
$user = User::factory()->create();
$product = Product::factory()->create();

$user->setCartItem($product, 5);
$this->assertEquals(5, $user->getAmount($product));
}

/**
* Remove product from cart.
*/
public function test_remove_item(): void {
$user = User::factory()->create();
$product = Product::factory()->create();

$user->cart()->attach($product, ["amount" => 1]);

$user->removeCartItem($product);
assertEquals(0, $user->cart()->count());
}
}

0 comments on commit f0345a3

Please sign in to comment.