feat: Add unit test to basket v1 api call + refactor basket update + handle empty basket return
This commit is contained in:
parent
e9fa15d0f8
commit
f542100fca
12 changed files with 300 additions and 94 deletions
|
|
@ -3,13 +3,10 @@
|
|||
namespace App\Http\Controllers\Api\v1;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\Campagne;
|
||||
use App\Models\Coupon;
|
||||
use App\Enums\LogActions;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Basket;
|
||||
use App\Models\BasketContent;
|
||||
use App\Models\Customers;
|
||||
use App\Traits\CouponTrait;
|
||||
use App\Traits\ThanksTrait;
|
||||
|
|
@ -128,36 +125,7 @@ class BasketController extends Controller
|
|||
$customer = Customers::where('hashcode', $request->customer_hashcode)->first();
|
||||
|
||||
// Retrieve or create basket
|
||||
$basket = Basket::where(['campagne_id' => $refcamp, 'customer_id' => $customer->id])->first();
|
||||
|
||||
if ($basket) {
|
||||
$camp = Campagne::where('camp_id', $basket->campagne_id)->first();
|
||||
foreach ($coupons as $couponCode => $quantity) {
|
||||
$cp = $camp->coupons()->where('coupon_code', $couponCode)->first();
|
||||
if ($cp) {
|
||||
$basketContent = BasketContent::where(['basket_id' => $basket->id, 'coupon_id' => $cp->coupon_id])->first();
|
||||
if ($basketContent) {
|
||||
$basketContent->quantity += $quantity;
|
||||
$basketContent->save();
|
||||
} else {
|
||||
$this->addContentToBasket($basket, $cp, $quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$basket = new Basket();
|
||||
$basket->uuid = (string) Str::uuid();
|
||||
$basket->campagne()->associate($refcamp);
|
||||
$basket->customer()->associate($customer);
|
||||
$basket->save();
|
||||
|
||||
foreach ($coupons as $couponCode => $quantity) {
|
||||
$cp = $campagne->coupons()->where('coupon_code', $couponCode)->first();
|
||||
if ($cp) {
|
||||
$this->addContentToBasket($basket, $cp, $quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
$basket = Basket::updateContent($campagne, $coupons, $customer);
|
||||
|
||||
// Log success
|
||||
$this->writeLogSuccessBasketCreated(
|
||||
|
|
@ -173,15 +141,4 @@ class BasketController extends Controller
|
|||
'Panier' => $basket
|
||||
]);
|
||||
}
|
||||
|
||||
private function addContentToBasket(Basket $basket, Coupon $coupon, int $quantity): void
|
||||
{
|
||||
if ($quantity > 0) {
|
||||
$newBasketContent = new BasketContent();
|
||||
$newBasketContent->basket_id = $basket->id;
|
||||
$newBasketContent->coupon_id = $coupon->coupon_id;
|
||||
$newBasketContent->quantity = $quantity;
|
||||
$newBasketContent->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ use App\Http\Controllers\Controller;
|
|||
use App\Http\Requests\Coupons\PrintRequest;
|
||||
use App\Http\Resources\StatusResource;
|
||||
use App\Models\Customers;
|
||||
use App\Models\PrintedCoupons;
|
||||
use App\Models\Statistique;
|
||||
use App\Traits\CouponTrait;
|
||||
use App\Traits\GenerateBR;
|
||||
use PDF;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ class BasketController extends Controller
|
|||
return $content->quantity - $content->printed;
|
||||
});
|
||||
|
||||
if (!$totalQuantity) {
|
||||
return view('fo.basket.empty', compact('basket'));
|
||||
}
|
||||
|
||||
return view('fo.basket.show', compact('basket', 'totalQuantity'));
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +82,10 @@ class BasketController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
if (empty($coupons)) {
|
||||
return redirect()->route('fo.basket.show', $basket);
|
||||
}
|
||||
|
||||
// $coupons contains all printed coupons
|
||||
$pdf = PDF::loadView('pdf.coupons', ['coupons' => $coupons]);
|
||||
return $pdf->download(sprintf("coupons_%s_%s_%s.pdf", $crmInfos->firstname, $crmInfos->lastname, date('d-m-Y')));
|
||||
|
|
|
|||
|
|
@ -7,11 +7,16 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Basket extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'campagne_id'
|
||||
];
|
||||
|
||||
/**
|
||||
* Customer model relation
|
||||
* @return BelongsTo
|
||||
|
|
@ -38,4 +43,40 @@ class Basket extends Model
|
|||
{
|
||||
return $this->hasMany(BasketContent::class);
|
||||
}
|
||||
|
||||
public static function updateContent(Campagne $campagne, array $coupons, Customers $customer)
|
||||
{
|
||||
$basket = self::where(['campagne_id' => $campagne->camp_id, 'customer_id' => $customer->id])->first();
|
||||
|
||||
if (!$basket) {
|
||||
$basket = new Basket();
|
||||
$basket->uuid = (string) Str::uuid();
|
||||
$basket->campagne()->associate($campagne);
|
||||
$basket->customer()->associate($customer);
|
||||
$basket->save();
|
||||
}
|
||||
|
||||
foreach ($coupons as $couponCode => $quantity) {
|
||||
$associatedCoupon = $campagne->coupons()->where('coupon_code', $couponCode)->first();
|
||||
|
||||
if ($associatedCoupon) {
|
||||
$basketContent = BasketContent::where(['basket_id' => $basket->id, 'coupon_id' => $associatedCoupon->coupon_id])->first();
|
||||
if ($basketContent) {
|
||||
$basketContent->quantity += $quantity;
|
||||
$basketContent->save();
|
||||
} else {
|
||||
if ($quantity > 0) {
|
||||
$newBasketContent = new BasketContent();
|
||||
$newBasketContent->basket_id = $basket->id;
|
||||
$newBasketContent->coupon_id = $associatedCoupon->coupon_id;
|
||||
$newBasketContent->quantity = $quantity;
|
||||
$newBasketContent->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $basket;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Models\Panier;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Customers extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* @var string $primaryKey
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -26,26 +26,26 @@ trait LogTrait
|
|||
/**
|
||||
* Write error log when there's a wrong or missing parameter in the request.
|
||||
*
|
||||
* @param string $refcamp
|
||||
* @param string $refadherent
|
||||
* @param string $refsite
|
||||
* @param string $panier
|
||||
* @param string|null $refcamp
|
||||
* @param string|null $refadherent
|
||||
* @param string|null $refsite
|
||||
* @param string|null $panier
|
||||
* @return void
|
||||
*/
|
||||
public function writeLogErrorWrongRequestParameters(
|
||||
string $refcamp,
|
||||
string $refadherent,
|
||||
string $refsite,
|
||||
string $panier
|
||||
$refcamp,
|
||||
$refadherent,
|
||||
$refsite,
|
||||
$panier
|
||||
) {
|
||||
$log = new Log();
|
||||
$log->log_action = $this->logAction->value;
|
||||
$log->log_source = $this->logSource;
|
||||
$log->log_params = json_encode([
|
||||
'refcamp' => $refcamp,
|
||||
'refadherent' => $refadherent,
|
||||
'refsite' => $refsite,
|
||||
'panier' => $panier
|
||||
'refcamp' => $refcamp ?? 'NO DATA',
|
||||
'refadherent' => $refadherent ?? 'NO DATA',
|
||||
'refsite' => $refsite ?? 'NO DATA',
|
||||
'panier' => $panier ?? 'NO DATA'
|
||||
]);
|
||||
$log->log_statut = 'NOK';
|
||||
$log->log_message = "Des paramètres de la requête sont mal renseignés ou non présents";
|
||||
|
|
|
|||
25
database/factories/CustomersFactory.php
Normal file
25
database/factories/CustomersFactory.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class CustomersFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'id' => $this->faker->randomDigit(),
|
||||
'hashcode' => $this->faker->uuid(),
|
||||
'email' => $this->faker->unique()->safeEmail(),
|
||||
'country' => $this->faker->randomElement([56,442, 250]), // ISO Country value for Belgium, Luxembourg and France
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
12
resources/views/fo/basket/empty.blade.php
Normal file
12
resources/views/fo/basket/empty.blade.php
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
@extends('fo.layout')
|
||||
@section('title', $basket->campagne->site->site_libelle . ' - ' . __('Bons de Réduction'))
|
||||
|
||||
@section('content')
|
||||
<div class="container mt-5 col-6">
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ asset($basket->campagne->site->site_logo) }}" alt="{{ $basket->campagne->site->site_libelle }}" class="img-fluid" style="max-width: 340px;">
|
||||
<br><br>
|
||||
<h1>{{ __('Coupon(s) non disponible') }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -1,28 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ config('app.locale') }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ $basket->campagne->site->site_libelle }} - {{ __('Bons de Réduction') }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap 5 CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5 col-6">
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ asset($basket->campagne->site->site_logo) }}" alt="{{ $basket->campagne->site->site_libelle }}" class="img-fluid" style="max-width: 340px;">
|
||||
</div>
|
||||
@extends('fo.layout')
|
||||
@section('title', $basket->campagne->site->site_libelle . ' - ' . __('Bons de Réduction'))
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Nombre de coupons :</h5>
|
||||
<p class="card-text fs-3 fw-bold text-primary">{{ $totalQuantity }}</p>
|
||||
</div>
|
||||
@section('content')
|
||||
<div class="container mt-5 col-6">
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ asset($basket->campagne->site->site_logo) }}" alt="{{ $basket->campagne->site->site_libelle }}" class="img-fluid" style="max-width: 340px;">
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Nombre de coupons :</h5>
|
||||
<p class="card-text fs-3 fw-bold text-primary">{{ $totalQuantity }}</p>
|
||||
</div>
|
||||
@if($totalQuantity)
|
||||
<div class="mb-3">
|
||||
<h4>Liste des coupons :</h4>
|
||||
<ul class="list-group">
|
||||
</div>
|
||||
@if($totalQuantity)
|
||||
<div class="mb-3">
|
||||
<h4>Liste des coupons :</h4>
|
||||
<ul class="list-group">
|
||||
@foreach ($basket->contents as $content)
|
||||
@php
|
||||
$remain = $content->quantity - $content->printed;
|
||||
|
|
@ -34,16 +28,13 @@
|
|||
</li>
|
||||
@endif
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<a class="btn btn-primary" href="{{ route('fo.basket.print', $basket) }}">{{ __('Récupérer') }}</a>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Bootstrap JS (optionnel) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<div class="text-center">
|
||||
<a class="btn btn-primary" href="{{ route('fo.basket.print', $basket) }}">{{ __('Récupérer') }}</a>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
15
resources/views/fo/layout.blade.php
Normal file
15
resources/views/fo/layout.blade.php
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ config('app.locale') }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>@yield('title')</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- Bootstrap 5 CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
@yield('content')
|
||||
<!-- Bootstrap JS (optionnel) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -11,11 +11,14 @@
|
|||
|
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('v1')
|
||||
->name('api.v1.')
|
||||
->group(function () {
|
||||
Route::get('/customer/mur', 'Api\v1\CustomerController@mur');
|
||||
Route::get('/customer/basket', 'Api\v1\CustomerController@basket');
|
||||
Route::get('/customer/basket/update', 'Api\v1\BasketController@updateBasket');
|
||||
Route::get('/customer/basket/update', 'Api\v1\BasketController@updateBasket')->name('basket.update');
|
||||
Route::resource('customer', 'Api\v1\CustomerController');
|
||||
Route::get('/mur', 'Api\v1\MurController@show');
|
||||
Route::get('/coupons/print', 'Api\v1\CouponsController@print');
|
||||
|
|
|
|||
153
tests/Feature/Api/V1/BasketTest.php
Normal file
153
tests/Feature/Api/V1/BasketTest.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\V1;
|
||||
|
||||
use App\Models\Basket;
|
||||
use App\Models\Campagne;
|
||||
use App\Models\Coupon;
|
||||
use App\Models\Customers;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BasketTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_updateBasket_missingRequiredParameters_returnsError()
|
||||
{
|
||||
$enpoint = route('api.v1.basket.update');
|
||||
|
||||
$response = $this->json('GET', $enpoint, [
|
||||
// missing required params intentionally
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'Statut' => 'KO',
|
||||
'Code' => 101,
|
||||
'Message' => 'Des paramètres de la requête sont mal renseignés ou non présents',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_updateBasket_invalidCampaign_returnsError()
|
||||
{
|
||||
$enpoint = route('api.v1.basket.update');
|
||||
|
||||
$response = $this->json('GET', $enpoint, [
|
||||
'customer_hashcode' => 'hash123',
|
||||
'refcamp' => 999999, // non existent campaign id
|
||||
'refsite' => 1,
|
||||
'panier' => '1)1000',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'Statut' => 'KO',
|
||||
'Code' => 103,
|
||||
'Message' => "La campagne n'existe pas ou n'est pas un panier",
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_updateBasket_successfulBasketCreation_returnsSuccess()
|
||||
{
|
||||
$enpoint = route('api.v1.basket.update');
|
||||
|
||||
// Create a fake campaign
|
||||
$campaign = Campagne::factory()->create([
|
||||
'camp_published' => 1,
|
||||
'camp_type' => 1,
|
||||
'camp_siteId' => 1,
|
||||
]);
|
||||
|
||||
// Create a fake customer
|
||||
$customer = Customers::factory()->create([
|
||||
'hashcode' => 'hash123',
|
||||
]);
|
||||
|
||||
// Create a coupon in the campaign
|
||||
$coupon = Coupon::factory()->create();
|
||||
$campaign->coupons()->attach($coupon);
|
||||
|
||||
$response = $this->json('GET', $enpoint, [
|
||||
'customer_hashcode' => 'hash123',
|
||||
'refcamp' => $campaign->camp_id,
|
||||
'refsite' => $campaign->camp_siteId,
|
||||
'panier' => sprintf("(%d)%s", 2, $coupon->coupon_code),
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'Statut' => 'OK',
|
||||
]);
|
||||
|
||||
// Assert basket created for the customer and campaign
|
||||
$this->assertDatabaseHas('baskets', [
|
||||
'campagne_id' => $campaign->camp_id,
|
||||
'customer_id' => $customer->id,
|
||||
]);
|
||||
|
||||
$basket = Basket::where(['campagne_id' => $campaign->camp_id, 'customer_id' => $customer->id])->first();
|
||||
|
||||
$this->assertDatabaseHas('basket_contents', [
|
||||
'quantity' => 2,
|
||||
'printed' => 0,
|
||||
'basket_id' => $basket->id,
|
||||
'coupon_id' => $coupon->coupon_id
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_updateBasket_successfulBasketUpdate_returnsSuccess()
|
||||
{
|
||||
$enpoint = route('api.v1.basket.update');
|
||||
|
||||
// Create a fake campaign
|
||||
$campaign = Campagne::factory()->create([
|
||||
'camp_published' => 1,
|
||||
'camp_type' => 1,
|
||||
'camp_siteId' => 1,
|
||||
]);
|
||||
|
||||
// Create a fake customer
|
||||
$customer = Customers::factory()->create([
|
||||
'hashcode' => 'hash123',
|
||||
]);
|
||||
|
||||
// Create a coupon in the campaign
|
||||
$coupon = Coupon::factory()->create();
|
||||
$campaign->coupons()->attach($coupon);
|
||||
|
||||
// Create a basket
|
||||
$basket = new Basket();
|
||||
$basket->uuid = (string) Str::uuid();
|
||||
$basket->campagne()->associate($campaign);
|
||||
$basket->customer()->associate($customer);
|
||||
$basket->save();
|
||||
|
||||
$response = $this->json('GET', $enpoint, [
|
||||
'customer_hashcode' => 'hash123',
|
||||
'refcamp' => $campaign->camp_id,
|
||||
'refsite' => $campaign->camp_siteId,
|
||||
'panier' => sprintf("(%d)%s", 2, $coupon->coupon_code),
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'Statut' => 'OK',
|
||||
]);
|
||||
|
||||
// Assert basket created for the customer and campaign
|
||||
$this->assertDatabaseHas('baskets', [
|
||||
'campagne_id' => $campaign->camp_id,
|
||||
'customer_id' => $customer->id,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('basket_contents', [
|
||||
'quantity' => 2,
|
||||
'printed' => 0,
|
||||
'basket_id' => $basket->id,
|
||||
'coupon_id' => $coupon->coupon_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue