10.April 2026
This commit is contained in:
parent
a00c42e770
commit
f58c709945
208 changed files with 19280 additions and 2914 deletions
119
tests/Feature/AboHelperSetAboActiveClearsHoldTest.php
Normal file
119
tests/Feature/AboHelperSetAboActiveClearsHoldTest.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Nach fehlgeschlagener Cron-Zahlung kann UserAbo status 3 (abo_hold) sein.
|
||||
* Späterer Payone-paid (setAboActive) muss das Abo wieder auf abo_okay (2) setzen.
|
||||
*/
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\AboHelper;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
it('setzt UserAbo von abo_hold auf abo_okay wenn Zahlung als bezahlt bestaetigt wird', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Standard',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'shop-owner-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 8),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'customer-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'cust@example.com',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => null,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => 'abo@example.com',
|
||||
'payone_userid' => 999001,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 3,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 10,
|
||||
'is_abo' => true,
|
||||
'paid' => false,
|
||||
'txaction' => 'prev',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'status' => 3,
|
||||
'paid' => false,
|
||||
]);
|
||||
|
||||
AboHelper::setAboActive($shoppingOrder, 2, true);
|
||||
|
||||
$userAbo->refresh();
|
||||
|
||||
expect($userAbo->status)->toBe(2);
|
||||
});
|
||||
100
tests/Feature/AboRepositoryDateChangeTest.php
Normal file
100
tests/Feature/AboRepositoryDateChangeTest.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
use App\Models\UserAbo;
|
||||
use App\Repositories\AboRepository;
|
||||
use Carbon\Carbon;
|
||||
|
||||
uses(Tests\TestCase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->repository = new AboRepository;
|
||||
});
|
||||
|
||||
/**
|
||||
* Szenario: Heute = 15. März, next_date = 5. April (März-Ausführung bereits erfolgt).
|
||||
* Änderung auf Tag 20 → neues Datum soll der 20. April sein, NICHT der 20. März.
|
||||
*/
|
||||
it('erlaubt Datumsänderung auf den 20. wenn next_date im April liegt und heute der 15. März ist', function () {
|
||||
Carbon::setTestNow('2026-03-15');
|
||||
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = '2026-04-05';
|
||||
|
||||
$this->repository->setModel($abo);
|
||||
|
||||
$newNextDate = invadePrivateMethod($this->repository, 'calculateNewNextDate', [20]);
|
||||
|
||||
expect($newNextDate->format('Y-m-d'))->toBe('2026-04-20');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
/**
|
||||
* Szenario: Heute = 15. März, next_date = 25. März (im selben Monat, noch nicht ausgeführt).
|
||||
* Änderung auf Tag 20 → 20. März ist nur 5 Tage entfernt → Referenz bleibt now().
|
||||
* setNextDate gibt 20. März zurück (5 Tage → zu nah).
|
||||
*/
|
||||
it('berechnet neues Datum im selben Monat wenn next_date noch im März liegt', function () {
|
||||
Carbon::setTestNow('2026-03-15');
|
||||
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = '2026-03-25';
|
||||
|
||||
$this->repository->setModel($abo);
|
||||
|
||||
$newNextDate = invadePrivateMethod($this->repository, 'calculateNewNextDate', [20]);
|
||||
|
||||
expect($newNextDate->format('Y-m-d'))->toBe('2026-03-20');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
/**
|
||||
* Szenario: Heute = 15. März, next_date = 5. April.
|
||||
* Änderung auf Tag 5 → neues Datum soll 5. April sein (identisch mit aktuell).
|
||||
*/
|
||||
it('setzt Datum auf den 5. April wenn Tag 5 gewählt und next_date im April liegt', function () {
|
||||
Carbon::setTestNow('2026-03-15');
|
||||
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = '2026-04-05';
|
||||
|
||||
$this->repository->setModel($abo);
|
||||
|
||||
$newNextDate = invadePrivateMethod($this->repository, 'calculateNewNextDate', [5]);
|
||||
|
||||
expect($newNextDate->format('Y-m-d'))->toBe('2026-04-05');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
/**
|
||||
* Szenario: Heute = 15. März, kein next_date gesetzt → Referenz ist now().
|
||||
*/
|
||||
it('verwendet heute als Referenz wenn kein next_date gesetzt ist', function () {
|
||||
Carbon::setTestNow('2026-03-15');
|
||||
|
||||
$abo = new UserAbo;
|
||||
$abo->next_date = null;
|
||||
|
||||
$this->repository->setModel($abo);
|
||||
|
||||
$newNextDate = invadePrivateMethod($this->repository, 'calculateNewNextDate', [20]);
|
||||
|
||||
expect($newNextDate->format('Y-m-d'))->toBe('2026-03-20');
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
/**
|
||||
* Hilfsfunktion zum Aufruf privater Methoden.
|
||||
*
|
||||
* @param array<mixed> $args
|
||||
*/
|
||||
function invadePrivateMethod(object $object, string $methodName, array $args = []): mixed
|
||||
{
|
||||
$reflection = new ReflectionMethod($object, $methodName);
|
||||
$reflection->setAccessible(true);
|
||||
|
||||
return $reflection->invoke($object, ...$args);
|
||||
}
|
||||
71
tests/Feature/BusinessPlan/SelfSponsoredUserTreeCalcTest.php
Normal file
71
tests/Feature/BusinessPlan/SelfSponsoredUserTreeCalcTest.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\UserAccount;
|
||||
use App\Models\UserBusiness;
|
||||
use App\Models\UserLevel;
|
||||
use App\Services\BusinessPlan\TreeCalcBot;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(Tests\TestCase::class);
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('TreeCalcBot initBusinesslUserDetail terminiert bei Selbst-Sponsor ohne Downline-Rekursion', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'AT',
|
||||
'phone' => '43',
|
||||
'en' => 'Austria',
|
||||
'de' => 'Österreich',
|
||||
'es' => 'Austria',
|
||||
'fr' => 'Autriche',
|
||||
'it' => 'Austria',
|
||||
'ru' => 'Austria',
|
||||
]);
|
||||
|
||||
$level = UserLevel::create([
|
||||
'name' => 'Berater',
|
||||
'margin' => 30,
|
||||
'margin_shop' => 30,
|
||||
'qual_kp' => 350,
|
||||
'qual_pp' => 1000,
|
||||
'pos' => 2,
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$account = UserAccount::create([
|
||||
'm_account' => random_int(900_000, 999_999),
|
||||
'first_name' => 'Self',
|
||||
'last_name' => 'Sponsor',
|
||||
'country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$user = User::forceCreate([
|
||||
'email' => 'self-sponsor-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'account_id' => $account->id,
|
||||
'm_level' => $level->id,
|
||||
'm_sponsor' => null,
|
||||
'payment_account' => '2030-12-31 00:00:00',
|
||||
'payment_shop' => '2030-12-31 00:00:00',
|
||||
'active_date' => '2020-01-01 00:00:00',
|
||||
'admin' => 0,
|
||||
'confirmed' => 1,
|
||||
]);
|
||||
|
||||
$user->update(['m_sponsor' => $user->id]);
|
||||
|
||||
$month = 6;
|
||||
$year = 2099;
|
||||
|
||||
expect(
|
||||
UserBusiness::where('user_id', $user->id)->where('month', $month)->where('year', $year)->exists()
|
||||
)->toBeFalse();
|
||||
|
||||
$bot = new TreeCalcBot($month, $year, 'member');
|
||||
$bot->initBusinesslUserDetail($user->fresh(['account', 'user_level', 'user_sponsor']));
|
||||
|
||||
expect($bot->business_user->businessUserItems)->toBeEmpty();
|
||||
});
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
use App\Console\Commands\BusinessStoreOptimized;
|
||||
use App\Models\UserLevel;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(Tests\TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function makeCommand(): BusinessStoreOptimized
|
||||
{
|
||||
$command = new BusinessStoreOptimized;
|
||||
$input = new \Symfony\Component\Console\Input\ArrayInput([]);
|
||||
$output = new \Illuminate\Console\OutputStyle($input, new \Symfony\Component\Console\Output\NullOutput);
|
||||
$command->setOutput($output);
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
function callAssignMethod(BusinessStoreOptimized $command): void
|
||||
{
|
||||
$method = (new ReflectionClass($command))->getMethod('assignMonthlyQualKpBonusPoints');
|
||||
$method->setAccessible(true);
|
||||
$method->invoke($command);
|
||||
}
|
||||
|
||||
function makeUserLevel(int $qualKp, int $pos = 7): UserLevel
|
||||
{
|
||||
return UserLevel::create([
|
||||
'name' => 'Test Level '.$pos,
|
||||
'qual_kp' => $qualKp,
|
||||
'active' => 1,
|
||||
'pos' => $pos,
|
||||
'default' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
function makeUser(int $levelId, ?int $forceId = null): User
|
||||
{
|
||||
$attributes = [
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'm_level' => $levelId,
|
||||
];
|
||||
|
||||
if ($forceId !== null) {
|
||||
$attributes['id'] = $forceId;
|
||||
}
|
||||
|
||||
return User::forceCreate($attributes);
|
||||
}
|
||||
|
||||
it('assigns qual_kp points to user 486 when their level has qual_kp > 0', function () {
|
||||
$level = makeUserLevel(690);
|
||||
makeUser($level->id, 486);
|
||||
|
||||
callAssignMethod(makeCommand());
|
||||
|
||||
expect(UserSalesVolume::where('user_id', 486)
|
||||
->where('month', date('m'))
|
||||
->where('year', date('Y'))
|
||||
->where('info', 'qual_kp_bonus')
|
||||
->where('points', 690)
|
||||
->exists()
|
||||
)->toBeTrue();
|
||||
});
|
||||
|
||||
it('does not assign bonus points to users not in the bonus list', function () {
|
||||
$level = makeUserLevel(690);
|
||||
$otherUser = makeUser($level->id);
|
||||
|
||||
callAssignMethod(makeCommand());
|
||||
|
||||
expect(UserSalesVolume::where('user_id', $otherUser->id)->where('info', 'qual_kp_bonus')->exists())
|
||||
->toBeFalse();
|
||||
});
|
||||
|
||||
it('skips user 486 when their level has qual_kp of zero', function () {
|
||||
$level = makeUserLevel(0);
|
||||
makeUser($level->id, 486);
|
||||
|
||||
callAssignMethod(makeCommand());
|
||||
|
||||
expect(UserSalesVolume::where('user_id', 486)->where('info', 'qual_kp_bonus')->exists())
|
||||
->toBeFalse();
|
||||
});
|
||||
|
||||
it('skips user 486 when they have no level assigned', function () {
|
||||
User::forceCreate([
|
||||
'id' => 486,
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'm_level' => null,
|
||||
]);
|
||||
|
||||
callAssignMethod(makeCommand());
|
||||
|
||||
expect(UserSalesVolume::where('user_id', 486)->where('info', 'qual_kp_bonus')->exists())
|
||||
->toBeFalse();
|
||||
});
|
||||
|
||||
it('is idempotent and does not create duplicate entries for the same month', function () {
|
||||
$level = makeUserLevel(690);
|
||||
makeUser($level->id, 486);
|
||||
|
||||
$command = makeCommand();
|
||||
callAssignMethod($command);
|
||||
callAssignMethod($command);
|
||||
|
||||
expect(UserSalesVolume::where('user_id', 486)
|
||||
->where('info', 'qual_kp_bonus')
|
||||
->count()
|
||||
)->toBe(1);
|
||||
});
|
||||
316
tests/Feature/Incentive/AboRenewalSalesVolumeIncentiveTest.php
Normal file
316
tests/Feature/Incentive/AboRenewalSalesVolumeIncentiveTest.php
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Validiert IncentiveTracker::trackSalesVolume für Umsatz aus einer weiteren Abo-Bestellung
|
||||
* (gleicher Kunde/shopping_user_id wie beim Neuabo). Rechnung und UserSalesVolume entstehen
|
||||
* über Payment::paymentStatusPaidAction / InvoiceRepository::createAndSalesVolume (Payone-API),
|
||||
* nicht über user:make_abo_order.
|
||||
*/
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewAbo;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\IncentivePointsLog;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\Incentive\IncentiveTracker;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-15 10:00:00'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('addiert Umsatzpunkte beim Berater wenn UserSalesVolume zur Abo-Verlängerung gehört (trackSalesVolume Pfad Neuabo)', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Standard',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'shop-owner-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 8),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'customer-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'cust@example.com',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => null,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => 'abo@example.com',
|
||||
'payone_userid' => 999001,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $consultant->id,
|
||||
]);
|
||||
|
||||
IncentiveNewAbo::create([
|
||||
'participant_id' => $participant->id,
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'activated_at' => Carbon::now()->subMonth(),
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 75,
|
||||
'is_abo' => true,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
$userSalesVolume = UserSalesVolume::create([
|
||||
'user_id' => $customer->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'date' => '15.05.2026',
|
||||
'points' => 75,
|
||||
'total_net' => 90,
|
||||
'status_points' => 1,
|
||||
'status' => 2,
|
||||
'message' => 'Abo Verlängerung Test',
|
||||
]);
|
||||
|
||||
IncentiveTracker::trackSalesVolume($userSalesVolume);
|
||||
|
||||
$participant->refresh();
|
||||
|
||||
expect(
|
||||
IncentivePointsLog::where('participant_id', $participant->id)
|
||||
->where('type', 'abo')
|
||||
->where('user_sales_volume_id', $userSalesVolume->id)
|
||||
->where('points_accumulated', 75)
|
||||
->exists()
|
||||
)->toBeTrue()
|
||||
->and($participant->total_points)->toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('addiert Umsatzpunkte bei Verlaengerung auch wenn shopping_order einen replizierten ShoppingUser hat', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'CH',
|
||||
'phone' => '41',
|
||||
'en' => 'Switzerland',
|
||||
'de' => 'Schweiz',
|
||||
'es' => 'Suiza',
|
||||
'fr' => 'Suisse',
|
||||
'it' => 'Svizzera',
|
||||
'ru' => 'Швейцария',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Std-CH',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'so-ch-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TSCH'.substr(uniqid('', true), 0, 6),
|
||||
'slug' => 'tsch-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-ch-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'customer-ch-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUserStamm = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'cust-ch-'.uniqid('', true).'@example.com',
|
||||
]);
|
||||
|
||||
$shoppingUserReplica = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => $shoppingUserStamm->billing_email,
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => null,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUserStamm->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => $shoppingUserStamm->billing_email,
|
||||
'payone_userid' => 999101,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $consultant->id,
|
||||
]);
|
||||
|
||||
IncentiveNewAbo::create([
|
||||
'participant_id' => $participant->id,
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'activated_at' => Carbon::now()->subMonth(),
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUserReplica->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 40,
|
||||
'is_abo' => true,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
expect($shoppingOrder->shopping_user_id)->not->toBe($userAbo->shopping_user_id);
|
||||
|
||||
$userSalesVolume = UserSalesVolume::create([
|
||||
'user_id' => $customer->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'date' => '15.05.2026',
|
||||
'points' => 40,
|
||||
'total_net' => 90,
|
||||
'status_points' => 1,
|
||||
'status' => 2,
|
||||
'message' => 'Abo Verlängerung Replikat',
|
||||
]);
|
||||
|
||||
IncentiveTracker::trackSalesVolume($userSalesVolume);
|
||||
|
||||
$participant->refresh();
|
||||
|
||||
expect(
|
||||
IncentivePointsLog::where('participant_id', $participant->id)
|
||||
->where('type', 'abo')
|
||||
->where('user_sales_volume_id', $userSalesVolume->id)
|
||||
->where('points_accumulated', 40)
|
||||
->exists()
|
||||
)->toBeTrue();
|
||||
});
|
||||
120
tests/Feature/Incentive/ConsultantOwnAboIncentiveTest.php
Normal file
120
tests/Feature/Incentive/ConsultantOwnAboIncentiveTest.php
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewAbo;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\Incentive\IncentiveTracker;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-15 10:00:00'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('trackAboActivated erfasst Berater-Eigenabo (is_for me) wie ein Kundenabo', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create(['name' => 'S', 'active' => true]);
|
||||
$shippingCountry = ShippingCountry::create(['shipping_id' => $shipping->id, 'country_id' => $country->id]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'co-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $consultant->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 6),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $consultant->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'me-'.uniqid('', true).'@example.com',
|
||||
'is_from' => 'wizard',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => $consultant->id,
|
||||
'member_id' => null,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $shoppingUser->billing_email,
|
||||
'payone_userid' => 999002,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $consultant->id,
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 50,
|
||||
'is_abo' => true,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
IncentiveTracker::trackAboActivated($shoppingOrder->fresh());
|
||||
|
||||
expect(
|
||||
IncentiveNewAbo::where('participant_id', $participant->id)
|
||||
->where('user_abo_id', $userAbo->id)
|
||||
->exists()
|
||||
)->toBeTrue();
|
||||
});
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewAbo;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\IncentivePointsLog;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-15 10:00:00'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('zieht Eigenabo vor Qualifikationsbeginn bei incentive:calculate ohne skip-repair nach', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'co-calc-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $consultant->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'me-'.uniqid('', true).'@example.com',
|
||||
'is_from' => 'wizard',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => $consultant->id,
|
||||
'member_id' => null,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $shoppingUser->billing_email,
|
||||
'payone_userid' => 999002,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$userAbo->forceFill([
|
||||
'created_at' => Carbon::parse('2026-01-15 12:00:00'),
|
||||
'updated_at' => Carbon::parse('2026-01-15 12:00:00'),
|
||||
])->save();
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
'points_abo_onetime' => 400,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $consultant->id,
|
||||
]);
|
||||
|
||||
$exitCode = Artisan::call('incentive:calculate', [
|
||||
'incentive_id' => (string) $incentive->id,
|
||||
]);
|
||||
|
||||
expect($exitCode)->toBe(0);
|
||||
|
||||
$participant->refresh();
|
||||
|
||||
expect(
|
||||
IncentiveNewAbo::query()
|
||||
->where('participant_id', $participant->id)
|
||||
->where('user_abo_id', $userAbo->id)
|
||||
->exists()
|
||||
)->toBeTrue();
|
||||
|
||||
$log = IncentivePointsLog::query()
|
||||
->where('participant_id', $participant->id)
|
||||
->where('type', 'abo')
|
||||
->where('source_id', $userAbo->id)
|
||||
->first();
|
||||
|
||||
expect($log)->not->toBeNull()
|
||||
->and($log->month)->toBe(4)
|
||||
->and($log->year)->toBe(2026)
|
||||
->and($log->points_onetime)->toBe(400);
|
||||
|
||||
expect($participant->total_points)->toBe(400);
|
||||
});
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Detailansicht: Akkumulierte Abo-Punkte muessen dem richtigen Neuabo zugeordnet werden,
|
||||
* auch wenn die Verlaengerungsbestellung einen anderen shopping_user_id hat (Replikat).
|
||||
*/
|
||||
|
||||
use App\Http\Controllers\Admin\IncentiveController;
|
||||
use App\Models\Country;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewAbo;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\IncentivePointsLog;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Models\UserShop;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-15 12:00:00'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('ordnet akkumulierte Abo-Punkte dem Neuabo ueber user_abo_id zu (Replikat-ShoppingUser)', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Standard',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'shop-owner-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 8),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'customer-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUserStamm = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'cust-detail-'.uniqid('', true).'@example.com',
|
||||
]);
|
||||
|
||||
$shoppingUserReplica = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => $shoppingUserStamm->billing_email,
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => null,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUserStamm->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => $shoppingUserStamm->billing_email,
|
||||
'payone_userid' => 999201,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
'points_abo_onetime' => 100,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $consultant->id,
|
||||
]);
|
||||
|
||||
$newAbo = IncentiveNewAbo::create([
|
||||
'participant_id' => $participant->id,
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'activated_at' => Carbon::parse('2026-04-10'),
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUserReplica->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 42,
|
||||
'is_abo' => true,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
$userSalesVolume = UserSalesVolume::create([
|
||||
'user_id' => $customer->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'date' => '15.05.2026',
|
||||
'points' => 42,
|
||||
'total_net' => 90,
|
||||
'status_points' => 1,
|
||||
'status' => 2,
|
||||
'message' => 'Verlängerung',
|
||||
]);
|
||||
|
||||
IncentivePointsLog::create([
|
||||
'participant_id' => $participant->id,
|
||||
'type' => 'abo',
|
||||
'source_type' => UserSalesVolume::class,
|
||||
'source_id' => $userSalesVolume->id,
|
||||
'source_label' => 'SV Test',
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'points_onetime' => 0,
|
||||
'points_accumulated' => 42,
|
||||
'user_sales_volume_id' => $userSalesVolume->id,
|
||||
'incentive_new_abo_id' => $newAbo->id,
|
||||
'is_storno' => false,
|
||||
]);
|
||||
|
||||
$data = IncentiveController::buildParticipantDetailData($participant->fresh(['incentive', 'user']));
|
||||
|
||||
$aboRow = $data['abo_sources']->first();
|
||||
expect($aboRow)->not->toBeNull()
|
||||
->and($aboRow['id'])->toBe($newAbo->id)
|
||||
->and(array_sum($aboRow['monthly']))->toBe(42);
|
||||
});
|
||||
181
tests/Feature/Incentive/IncentiveParticipantRankOrderingTest.php
Normal file
181
tests/Feature/Incentive/IncentiveParticipantRankOrderingTest.php
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\User\IncentiveController;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('sortiert Teilnehmer mit Rang 1,2,… und ohne Rang zuletzt', function () {
|
||||
$incentive = Incentive::factory()->create();
|
||||
|
||||
$makeUser = fn () => User::forceCreate([
|
||||
'email' => 't-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userNoRank = $makeUser();
|
||||
$userRank2 = $makeUser();
|
||||
$userRank1 = $makeUser();
|
||||
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $userNoRank->id,
|
||||
'rank' => null,
|
||||
]);
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $userRank2->id,
|
||||
'rank' => 2,
|
||||
]);
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $userRank1->id,
|
||||
'rank' => 1,
|
||||
]);
|
||||
|
||||
$orderedUserIds = IncentiveParticipant::query()
|
||||
->where('incentive_id', $incentive->id)
|
||||
->orderByIncentiveLeaderboard()
|
||||
->pluck('user_id')
|
||||
->all();
|
||||
|
||||
expect($orderedUserIds)->toBe([$userRank1->id, $userRank2->id, $userNoRank->id]);
|
||||
});
|
||||
|
||||
it('sortiert qualifizierte Teilnehmer vor nicht qualifizierten, unabhängig von Punkten und Rang', function () {
|
||||
$incentive = Incentive::factory()->create();
|
||||
|
||||
$makeUser = fn () => User::forceCreate([
|
||||
'email' => 't-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userNotQualified = $makeUser();
|
||||
$userQualified = $makeUser();
|
||||
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $userNotQualified->id,
|
||||
'is_qualified' => false,
|
||||
'total_points' => 10_000,
|
||||
'rank' => 1,
|
||||
]);
|
||||
IncentiveParticipant::factory()->qualified()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $userQualified->id,
|
||||
'total_points' => 500,
|
||||
'rank' => 2,
|
||||
]);
|
||||
|
||||
$orderedUserIds = IncentiveParticipant::query()
|
||||
->where('incentive_id', $incentive->id)
|
||||
->orderByIncentiveLeaderboard()
|
||||
->pluck('user_id')
|
||||
->all();
|
||||
|
||||
expect($orderedUserIds)->toBe([$userQualified->id, $userNotQualified->id]);
|
||||
});
|
||||
|
||||
it('sortiert bei Punktgleichstand Teilnehmer mit Klarnamen (bestaetigte Teilnahme) vor anonymen', function () {
|
||||
$incentive = Incentive::factory()->create();
|
||||
|
||||
$makeUser = fn () => User::forceCreate([
|
||||
'email' => 't-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$anonymous = IncentiveParticipant::factory()->unconfirmed()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $makeUser()->id,
|
||||
'total_points' => 500,
|
||||
'rank' => 5,
|
||||
]);
|
||||
$confirmed = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $makeUser()->id,
|
||||
'accepted_terms_at' => now(),
|
||||
'total_points' => 500,
|
||||
'rank' => 5,
|
||||
]);
|
||||
|
||||
$orderedUserIds = IncentiveParticipant::query()
|
||||
->where('incentive_id', $incentive->id)
|
||||
->orderByIncentiveLeaderboard()
|
||||
->pluck('user_id')
|
||||
->all();
|
||||
|
||||
expect($orderedUserIds)->toBe([$confirmed->user_id, $anonymous->user_id]);
|
||||
});
|
||||
|
||||
it('begrenzt die User-Rangliste auf 30 Plaetze (Gewinner-Zone bleibt max_winners)', function () {
|
||||
$incentive = Incentive::factory()->create(['max_winners' => 20]);
|
||||
|
||||
$makeUser = fn () => User::forceCreate([
|
||||
'email' => 't-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
foreach (range(1, 35) as $i) {
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $makeUser()->id,
|
||||
'total_points' => 1000 - $i,
|
||||
]);
|
||||
}
|
||||
|
||||
$ranking = IncentiveParticipant::where('incentive_id', $incentive->id)
|
||||
->withRankingActivity()
|
||||
->orderByIncentiveLeaderboard()
|
||||
->limit(IncentiveController::USER_RANKING_DISPLAY_LIMIT)
|
||||
->get();
|
||||
|
||||
expect($ranking)->toHaveCount(30);
|
||||
});
|
||||
|
||||
it('blendet in der User-Ranglogik Teilnehmer ohne Partner, Abo und Punkte aus', function () {
|
||||
$incentive = Incentive::factory()->create();
|
||||
|
||||
$makeUser = fn () => User::forceCreate([
|
||||
'email' => 't-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $makeUser()->id,
|
||||
'qualified_partners' => 0,
|
||||
'qualified_abos' => 0,
|
||||
'total_points' => 0,
|
||||
]);
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $makeUser()->id,
|
||||
'qualified_partners' => 0,
|
||||
'qualified_abos' => 0,
|
||||
'total_points' => 100,
|
||||
]);
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $makeUser()->id,
|
||||
'qualified_partners' => 1,
|
||||
'qualified_abos' => 0,
|
||||
'total_points' => 0,
|
||||
]);
|
||||
|
||||
$ids = IncentiveParticipant::query()
|
||||
->where('incentive_id', $incentive->id)
|
||||
->withRankingActivity()
|
||||
->orderBy('id')
|
||||
->pluck('id')
|
||||
->all();
|
||||
|
||||
expect($ids)->toHaveCount(2);
|
||||
});
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewPartner;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\Product;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingOrderItem;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\Incentive\IncentiveTracker;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-15 12:00:00'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
function createWizardRegistrationOrderWithProducts(User $registrant, User $shopOwner, array $products): ShoppingOrder
|
||||
{
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create(['name' => 'S', 'active' => true]);
|
||||
$shippingCountry = ShippingCountry::create(['shipping_id' => $shipping->id, 'country_id' => $country->id]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'T'.substr(uniqid('', true), 0, 6),
|
||||
'slug' => 't-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $registrant->id,
|
||||
'member_id' => $shopOwner->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'su-'.uniqid('', true).'@example.com',
|
||||
'is_from' => 'wizard',
|
||||
]);
|
||||
|
||||
$order = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $registrant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 1,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
foreach ($products as $product) {
|
||||
ShoppingOrderItem::create([
|
||||
'shopping_order_id' => $order->id,
|
||||
'product_id' => $product->id,
|
||||
'qty' => 1,
|
||||
'price' => 50,
|
||||
]);
|
||||
}
|
||||
|
||||
return $order->fresh(['shopping_order_items.product']);
|
||||
}
|
||||
|
||||
function createProductForTest(bool $membershipOnly): Product
|
||||
{
|
||||
$id = DB::table('products')->insertGetId([
|
||||
'name' => 'P '.uniqid(),
|
||||
'title' => 'T',
|
||||
'is_membership_only' => $membershipOnly,
|
||||
'active' => true,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
return Product::query()->findOrFail($id);
|
||||
}
|
||||
|
||||
it('trackNewPartner legt keinen Neupartner an bei reiner Mitgliedschaft ohne Starterpaket', function () {
|
||||
$sponsor = User::forceCreate([
|
||||
'email' => 'sp-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$newPartner = User::forceCreate([
|
||||
'email' => 'np-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'm_sponsor' => $sponsor->id,
|
||||
]);
|
||||
|
||||
$membershipProduct = createProductForTest(membershipOnly: true);
|
||||
|
||||
$order = createWizardRegistrationOrderWithProducts($newPartner, $sponsor, [$membershipProduct]);
|
||||
|
||||
expect($order->qualifiesForIncentiveTrackedPartner())->toBeFalse();
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $sponsor->id,
|
||||
]);
|
||||
|
||||
IncentiveTracker::trackNewPartner($order);
|
||||
|
||||
expect(
|
||||
IncentiveNewPartner::where('user_id', $newPartner->id)->exists()
|
||||
)->toBeFalse();
|
||||
});
|
||||
|
||||
it('trackNewPartner legt Neupartner an wenn Registrierung ein Starterpaket enthält', function () {
|
||||
$sponsor = User::forceCreate([
|
||||
'email' => 'sp-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$newPartner = User::forceCreate([
|
||||
'email' => 'np-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'm_sponsor' => $sponsor->id,
|
||||
]);
|
||||
|
||||
$starterProduct = createProductForTest(membershipOnly: false);
|
||||
|
||||
$order = createWizardRegistrationOrderWithProducts($newPartner, $sponsor, [$starterProduct]);
|
||||
|
||||
expect($order->qualifiesForIncentiveTrackedPartner())->toBeTrue();
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $sponsor->id,
|
||||
]);
|
||||
|
||||
IncentiveTracker::trackNewPartner($order);
|
||||
|
||||
expect(
|
||||
IncentiveNewPartner::where('user_id', $newPartner->id)->exists()
|
||||
)->toBeTrue();
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('markiert Teilnehmer ohne Teilnahmebestaetigung als nicht oeffentlich (hasAcceptedTerms)', function () {
|
||||
$incentive = Incentive::factory()->create();
|
||||
|
||||
$userA = User::forceCreate([
|
||||
'email' => 'a-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
$userB = User::forceCreate([
|
||||
'email' => 'b-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$anonymous = IncentiveParticipant::factory()->unconfirmed()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $userA->id,
|
||||
]);
|
||||
|
||||
$confirmed = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $userB->id,
|
||||
'accepted_terms_at' => now(),
|
||||
]);
|
||||
|
||||
expect($anonymous->hasAcceptedTerms())->toBeFalse()
|
||||
->and($confirmed->hasAcceptedTerms())->toBeTrue();
|
||||
});
|
||||
90
tests/Feature/Incentive/IncentiveTeaserPageTest.php
Normal file
90
tests/Feature/Incentive/IncentiveTeaserPageTest.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\User\IncentiveController;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('collectGalleryImages returns an array of image paths excluding the hero image', function () {
|
||||
$incentive = Incentive::factory()->active()->create([
|
||||
'image' => 'nikki-beach.jpg',
|
||||
]);
|
||||
|
||||
$controller = new IncentiveController;
|
||||
|
||||
$method = new ReflectionMethod($controller, 'collectGalleryImages');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($controller, $incentive);
|
||||
|
||||
expect($result)->toBeArray();
|
||||
expect($result)->not->toContain('img/incentive/nikki-beach.jpg');
|
||||
|
||||
foreach ($result as $path) {
|
||||
expect($path)->toStartWith('img/incentive/');
|
||||
}
|
||||
});
|
||||
|
||||
it('collectGalleryImages returns empty array when directory does not exist', function () {
|
||||
$incentive = Incentive::factory()->active()->create([
|
||||
'image' => 'nonexistent-hero.jpg',
|
||||
]);
|
||||
|
||||
$controller = new IncentiveController;
|
||||
|
||||
$method = new ReflectionMethod($controller, 'collectGalleryImages');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($controller, $incentive);
|
||||
|
||||
expect($result)->toBeArray();
|
||||
});
|
||||
|
||||
it('collectGalleryImages returns sorted results', function () {
|
||||
$incentive = Incentive::factory()->active()->create([
|
||||
'image' => 'nikki-beach.jpg',
|
||||
]);
|
||||
|
||||
$controller = new IncentiveController;
|
||||
|
||||
$method = new ReflectionMethod($controller, 'collectGalleryImages');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invoke($controller, $incentive);
|
||||
|
||||
$sorted = $result;
|
||||
sort($sorted);
|
||||
|
||||
expect($result)->toBe($sorted);
|
||||
});
|
||||
|
||||
it('teaser view receives galleryImages and participant data', function () {
|
||||
$incentive = Incentive::factory()->active()->create();
|
||||
|
||||
$user = User::forceCreate([
|
||||
'email' => 'teaser-' . uniqid('', true) . '@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
'active' => 1,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $user->id,
|
||||
'accepted_terms_at' => now(),
|
||||
]);
|
||||
|
||||
$this->actingAs($user);
|
||||
|
||||
$controller = new IncentiveController;
|
||||
$response = $controller->teaser($incentive->slug);
|
||||
|
||||
$data = $response->getData();
|
||||
expect($data)->toHaveKeys(['incentive', 'participant', 'galleryImages'])
|
||||
->and($data['incentive']->id)->toBe($incentive->id)
|
||||
->and($data['participant']->id)->toBe($participant->id)
|
||||
->and($data['galleryImages'])->toBeArray();
|
||||
});
|
||||
25
tests/Feature/LocalizationMiddlewareTest.php
Normal file
25
tests/Feature/LocalizationMiddlewareTest.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
uses(Tests\TestCase::class);
|
||||
|
||||
it('does not throw when session locale is a malicious string', function () {
|
||||
$response = $this->withSession(['locale' => '-1 or 5*5=25 --'])
|
||||
->get('/impressum');
|
||||
|
||||
$response->assertSuccessful();
|
||||
});
|
||||
|
||||
it('applies a valid session locale', function () {
|
||||
$response = $this->withSession(['locale' => 'en'])
|
||||
->get('/impressum');
|
||||
|
||||
$response->assertSuccessful();
|
||||
expect(app()->getLocale())->toBe('en');
|
||||
});
|
||||
|
||||
it('clears invalid session locale', function () {
|
||||
$this->withSession(['locale' => '-1 or 5*5=25 --'])
|
||||
->get('/impressum');
|
||||
|
||||
expect(session('locale'))->toBeNull();
|
||||
});
|
||||
140
tests/Feature/MembershipQtyProtectionTest.php
Normal file
140
tests/Feature/MembershipQtyProtectionTest.php
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Mitgliedschaftsprodukte (is_membership_only) dürfen nie qty > 1 haben.
|
||||
*
|
||||
* Absicherung in:
|
||||
* - MembershipController::storePayment() — qty wird auf 1 erzwungen
|
||||
* - CardController::updateCard() — qty-Änderung wird auf 1 begrenzt
|
||||
*/
|
||||
|
||||
use App\Http\Controllers\Web\CardController;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\Request;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Schema::connection('sqlite')->table('products', function ($table) {
|
||||
if (! Schema::connection('sqlite')->hasColumn('products', 'slug')) {
|
||||
$table->string('slug')->nullable();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function createMembershipProduct(): Product
|
||||
{
|
||||
return Product::forceCreate([
|
||||
'name' => 'MIVITA BUSINESS Paket',
|
||||
'title' => 'Test',
|
||||
'price' => 69.90,
|
||||
'tax' => 19.00,
|
||||
'active' => true,
|
||||
'is_membership_only' => true,
|
||||
'pos' => 1,
|
||||
'show_at' => 3,
|
||||
'show_on' => '["7","8"]',
|
||||
'identifier' => 'show_order',
|
||||
'action' => '["0","1"]',
|
||||
'no_commission' => true,
|
||||
'no_free_shipping' => false,
|
||||
'free_shipping_consultant' => false,
|
||||
'weight' => 0,
|
||||
'points' => 0,
|
||||
'slug' => 'mivita-business-paket',
|
||||
]);
|
||||
}
|
||||
|
||||
function createRegularProduct(): Product
|
||||
{
|
||||
return Product::forceCreate([
|
||||
'name' => 'Aloe Vera Creme',
|
||||
'title' => 'Test',
|
||||
'price' => 29.90,
|
||||
'tax' => 19.00,
|
||||
'active' => true,
|
||||
'is_membership_only' => false,
|
||||
'pos' => 2,
|
||||
'show_at' => 1,
|
||||
'no_commission' => false,
|
||||
'no_free_shipping' => false,
|
||||
'free_shipping_consultant' => false,
|
||||
'weight' => 200,
|
||||
'points' => 10,
|
||||
'slug' => 'aloe-vera-creme',
|
||||
]);
|
||||
}
|
||||
|
||||
it('begrenzt Mitgliedschaftsprodukt auf qty=1 bei CardController updateCard', function () {
|
||||
$product = createMembershipProduct();
|
||||
|
||||
$cartItem = \Yard::instance('webshop')->add(
|
||||
$product->id,
|
||||
$product->name,
|
||||
1,
|
||||
$product->price,
|
||||
false,
|
||||
false,
|
||||
['slug' => $product->slug, 'weight' => 0, 'points' => 0, 'no_commission' => true, 'no_free_shipping' => false, 'show_on' => $product->show_on]
|
||||
);
|
||||
|
||||
$request = new Request(['quantity' => [$cartItem->rowId => 5]]);
|
||||
app()->instance('request', $request);
|
||||
\Request::swap($request);
|
||||
|
||||
$controller = new CardController;
|
||||
$controller->updateCard();
|
||||
|
||||
$updatedItem = \Yard::instance('webshop')->get($cartItem->rowId);
|
||||
|
||||
expect($updatedItem->qty)->toBe(1);
|
||||
|
||||
\Yard::instance('webshop')->destroy();
|
||||
});
|
||||
|
||||
it('laesst qty-Aenderung fuer normale Produkte in CardController updateCard zu', function () {
|
||||
$product = createRegularProduct();
|
||||
|
||||
$cartItem = \Yard::instance('webshop')->add(
|
||||
$product->id,
|
||||
$product->name,
|
||||
1,
|
||||
$product->price,
|
||||
false,
|
||||
false,
|
||||
['slug' => $product->slug, 'weight' => 200, 'points' => 10, 'no_commission' => false, 'no_free_shipping' => false]
|
||||
);
|
||||
|
||||
$request = new Request(['quantity' => [$cartItem->rowId => 4]]);
|
||||
app()->instance('request', $request);
|
||||
\Request::swap($request);
|
||||
|
||||
$controller = new CardController;
|
||||
$controller->updateCard();
|
||||
|
||||
$updatedItem = \Yard::instance('webshop')->get($cartItem->rowId);
|
||||
|
||||
expect($updatedItem->qty)->toBe(4);
|
||||
|
||||
\Yard::instance('webshop')->destroy();
|
||||
});
|
||||
|
||||
it('erzwingt qty=1 im MembershipController fuer is_membership_only Produkte', function () {
|
||||
$product = createMembershipProduct();
|
||||
|
||||
$qty_from_request = 5;
|
||||
$enforced_qty = $product->is_membership_only ? 1 : $qty_from_request;
|
||||
|
||||
expect($enforced_qty)->toBe(1);
|
||||
});
|
||||
|
||||
it('nutzt Request-qty im MembershipController fuer nicht-membership Produkte', function () {
|
||||
$product = createRegularProduct();
|
||||
|
||||
$qty_from_request = 3;
|
||||
$enforced_qty = $product->is_membership_only ? 1 : $qty_from_request;
|
||||
|
||||
expect($enforced_qty)->toBe(3);
|
||||
});
|
||||
152
tests/Feature/RepairMissingAboFromOrdersTest.php
Normal file
152
tests/Feature/RepairMissingAboFromOrdersTest.php
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @see \App\Console\Commands\RepairMissingAboFromOrders
|
||||
*/
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\PaymentTransaction;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingPayment;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function repairMissingAboSeedOrder(): ShoppingOrder
|
||||
{
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Standard',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'shop-owner-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 8),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'customer-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'cust-'.uniqid('', true).'@example.com',
|
||||
'is_for' => 'ot',
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 10,
|
||||
'is_abo' => true,
|
||||
'abo_interval' => 15,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
$shoppingPayment = ShoppingPayment::create([
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'reference' => 'ref-'.uniqid(),
|
||||
'amount' => 10000,
|
||||
'currency' => 'EUR',
|
||||
'clearingtype' => 'cc',
|
||||
'abo_interval' => 15,
|
||||
'mode' => 'test',
|
||||
]);
|
||||
|
||||
PaymentTransaction::create([
|
||||
'shopping_payment_id' => $shoppingPayment->id,
|
||||
'request' => 'transaction',
|
||||
'txid' => 123456,
|
||||
'userid' => 987654,
|
||||
'status' => 'PAYONE',
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
]);
|
||||
|
||||
return $shoppingOrder;
|
||||
}
|
||||
|
||||
it('zeigt fehlende Abo-Verknuepfung im Trockenlauf ohne Aenderung', function () {
|
||||
$order = repairMissingAboSeedOrder();
|
||||
|
||||
expect(UserAbo::count())->toBe(0);
|
||||
|
||||
$exit = Artisan::call('abo:repair-missing', [
|
||||
'--order' => (string) $order->id,
|
||||
'--mode' => 'test',
|
||||
]);
|
||||
|
||||
expect($exit)->toBe(0);
|
||||
expect(UserAbo::count())->toBe(0);
|
||||
});
|
||||
|
||||
it('repariert fehlende UserAbo-Verknuepfung mit --fix --force', function () {
|
||||
$order = repairMissingAboSeedOrder();
|
||||
|
||||
expect(UserAboOrder::where('shopping_order_id', $order->id)->exists())->toBeFalse();
|
||||
|
||||
$exit = Artisan::call('abo:repair-missing', [
|
||||
'--fix' => true,
|
||||
'--force' => true,
|
||||
'--order' => (string) $order->id,
|
||||
'--mode' => 'test',
|
||||
]);
|
||||
|
||||
expect($exit)->toBe(0);
|
||||
|
||||
$order->refresh();
|
||||
expect($order->getUserAbo())->not->toBeNull();
|
||||
expect($order->getUserAbo()->status)->toBe(2);
|
||||
});
|
||||
171
tests/Feature/RetryFailedPaypalAbosTest.php
Normal file
171
tests/Feature/RetryFailedPaypalAbosTest.php
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function createPaypalHoldAbo(array $overrides = []): array
|
||||
{
|
||||
$country = Country::firstOrCreate(['code' => 'DE'], [
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
Shipping::firstOrCreate(['name' => 'Standard'], ['active' => true]);
|
||||
$shipping = Shipping::where('name', 'Standard')->first();
|
||||
|
||||
ShippingCountry::firstOrCreate([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'shop-owner-'.uniqid('', true).'@test.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$uniqueSuffix = uniqid('', true);
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS'.$uniqueSuffix,
|
||||
'slug' => 'ts-'.$uniqueSuffix,
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'berater-'.uniqid('', true).'@test.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $consultant->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'abo-'.uniqid().'@test.com',
|
||||
'is_for' => 'me',
|
||||
'is_from' => 'user_order',
|
||||
]);
|
||||
|
||||
$referenceOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $consultant->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $country->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 3,
|
||||
'points' => 10,
|
||||
'is_abo' => true,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
'total_shipping' => 105,
|
||||
]);
|
||||
|
||||
$aboData = array_merge([
|
||||
'user_id' => $consultant->id,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'me',
|
||||
'email' => $shoppingUser->billing_email,
|
||||
'payone_userid' => rand(100000, 999999),
|
||||
'clearingtype' => 'wlt',
|
||||
'wallettype' => 'PPE',
|
||||
'active' => true,
|
||||
'status' => 3,
|
||||
'abo_interval' => 5,
|
||||
'start_date' => '2026-01-05',
|
||||
'last_date' => '2026-04-05',
|
||||
'next_date' => '2026-04-05',
|
||||
], $overrides);
|
||||
|
||||
$userAbo = UserAbo::create($aboData);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $referenceOrder->id,
|
||||
'status' => 3,
|
||||
'paid' => false,
|
||||
]);
|
||||
|
||||
return [
|
||||
'userAbo' => $userAbo,
|
||||
'consultant' => $consultant,
|
||||
'shoppingUser' => $shoppingUser,
|
||||
'referenceOrder' => $referenceOrder,
|
||||
'country' => $country,
|
||||
'userShop' => $userShop,
|
||||
];
|
||||
}
|
||||
|
||||
it('findet nur PayPal-Abos mit Status 3 und next_date 2026-04-05 im Dry-Run', function () {
|
||||
createPaypalHoldAbo();
|
||||
|
||||
createPaypalHoldAbo(['clearingtype' => 'cc', 'wallettype' => null]);
|
||||
createPaypalHoldAbo(['status' => 2]);
|
||||
createPaypalHoldAbo(['next_date' => '2026-05-05']);
|
||||
|
||||
$this->artisan('abo:retry-failed-paypal', ['--dry-run' => true])
|
||||
->assertSuccessful()
|
||||
->expectsOutputToContain('Betroffene Abos: 1');
|
||||
});
|
||||
|
||||
it('meldet wenn keine betroffenen Abos vorhanden sind', function () {
|
||||
$this->artisan('abo:retry-failed-paypal', ['--dry-run' => true])
|
||||
->assertSuccessful()
|
||||
->expectsOutputToContain('Keine betroffenen PayPal-Abos');
|
||||
});
|
||||
|
||||
it('überspringt Abos die heute bereits bezahlt wurden', function () {
|
||||
$data = createPaypalHoldAbo();
|
||||
$abo = $data['userAbo'];
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $abo->id,
|
||||
'shopping_order_id' => $data['referenceOrder']->id,
|
||||
'status' => 1,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
$this->artisan('abo:retry-failed-paypal', ['--abo-id' => $abo->id])
|
||||
->assertSuccessful()
|
||||
->expectsOutputToContain('Bereits heute bezahlt');
|
||||
});
|
||||
|
||||
it('zeigt korrekte Zusammenfassung im Dry-Run', function () {
|
||||
createPaypalHoldAbo();
|
||||
createPaypalHoldAbo();
|
||||
createPaypalHoldAbo();
|
||||
|
||||
$this->artisan('abo:retry-failed-paypal', ['--dry-run' => true])
|
||||
->assertSuccessful()
|
||||
->expectsOutputToContain('Betroffene Abos: 3');
|
||||
});
|
||||
|
||||
it('filtert korrekt nach einzelner Abo-ID', function () {
|
||||
$first = createPaypalHoldAbo();
|
||||
createPaypalHoldAbo();
|
||||
|
||||
$this->artisan('abo:retry-failed-paypal', ['--dry-run' => true, '--abo-id' => $first['userAbo']->id])
|
||||
->assertSuccessful()
|
||||
->expectsOutputToContain('Betroffene Abos: 1');
|
||||
});
|
||||
119
tests/Feature/Sys/PayoneCallbackTestbenchPrepareCronTest.php
Normal file
119
tests/Feature/Sys/PayoneCallbackTestbenchPrepareCronTest.php
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\SyS\PayoneCallbackTestbench;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-06-10 12:00:00'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('setzt next_date auf heute und entfernt heutige UserAboOrders (Vorbereitung Cron)', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'x',
|
||||
'fr' => 'x',
|
||||
'it' => 'x',
|
||||
'ru' => 'x',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create(['name' => 'Std', 'active' => true]);
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'so-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'B'.substr(uniqid('', true), 0, 6),
|
||||
'slug' => 'b-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'co-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'cu-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'c@example.com',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => null,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => 'abo@example.com',
|
||||
'payone_userid' => 999002,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
'next_date' => '2026-12-01',
|
||||
]);
|
||||
|
||||
$order = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 10,
|
||||
'paid' => true,
|
||||
'mode' => 'test',
|
||||
'total' => 50,
|
||||
'subtotal' => 50,
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $order->id,
|
||||
'status' => 1,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
expect(UserAboOrder::where('user_abo_id', $userAbo->id)->count())->toBe(1);
|
||||
|
||||
PayoneCallbackTestbench::prepareUserAboForCronRun($userAbo->fresh());
|
||||
|
||||
$userAbo->refresh();
|
||||
|
||||
expect(Carbon::parse($userAbo->next_date)->format('Y-m-d'))->toBe('2026-06-10')
|
||||
->and(UserAboOrder::where('user_abo_id', $userAbo->id)->count())->toBe(0);
|
||||
});
|
||||
5
tests/Pest.php
Normal file
5
tests/Pest.php
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
uses(Tests\TestCase::class)->in('Feature/Incentive');
|
||||
uses(Tests\TestCase::class)->in('Feature/Sys');
|
||||
uses(Tests\TestCase::class)->in('Unit/Incentive');
|
||||
239
tests/Unit/Incentive/IncentiveModelTest.php
Normal file
239
tests/Unit/Incentive/IncentiveModelTest.php
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\IncentivePointsLog;
|
||||
use Carbon\Carbon;
|
||||
|
||||
// === Incentive: Zeitraum-Pruefungen ===
|
||||
|
||||
it('detects qualification period correctly', function () {
|
||||
$incentive = new Incentive([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
]);
|
||||
|
||||
expect($incentive->isInQualificationPeriod(Carbon::parse('2026-05-15')))->toBeTrue()
|
||||
->and($incentive->isInQualificationPeriod(Carbon::parse('2026-04-01')))->toBeTrue()
|
||||
->and($incentive->isInQualificationPeriod(Carbon::parse('2026-07-31')))->toBeTrue()
|
||||
->and($incentive->isInQualificationPeriod(Carbon::parse('2026-03-31')))->toBeFalse()
|
||||
->and($incentive->isInQualificationPeriod(Carbon::parse('2026-08-01')))->toBeFalse();
|
||||
});
|
||||
|
||||
it('detects calculation period correctly', function () {
|
||||
$incentive = new Incentive([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
]);
|
||||
|
||||
expect($incentive->isInCalculationPeriod(Carbon::parse('2026-08-15')))->toBeTrue()
|
||||
->and($incentive->isInCalculationPeriod(Carbon::parse('2026-04-01')))->toBeTrue()
|
||||
->and($incentive->isInCalculationPeriod(Carbon::parse('2026-09-01')))->toBeFalse()
|
||||
->and($incentive->isInCalculationPeriod(Carbon::parse('2026-03-31')))->toBeFalse();
|
||||
});
|
||||
|
||||
it('checks date in scope by month/year', function () {
|
||||
$incentive = new Incentive([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'calculation_end' => '2026-08-31',
|
||||
]);
|
||||
|
||||
expect($incentive->isDateInScope(4, 2026))->toBeTrue()
|
||||
->and($incentive->isDateInScope(8, 2026))->toBeTrue()
|
||||
->and($incentive->isDateInScope(9, 2026))->toBeFalse()
|
||||
->and($incentive->isDateInScope(3, 2026))->toBeFalse();
|
||||
});
|
||||
|
||||
it('returns correct calculation months', function () {
|
||||
$incentive = new Incentive([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'calculation_end' => '2026-08-31',
|
||||
]);
|
||||
|
||||
$months = $incentive->getCalculationMonths();
|
||||
|
||||
expect($months)->toHaveCount(5)
|
||||
->and($months[0])->toBe(['month' => 4, 'year' => 2026])
|
||||
->and($months[1])->toBe(['month' => 5, 'year' => 2026])
|
||||
->and($months[4])->toBe(['month' => 8, 'year' => 2026]);
|
||||
});
|
||||
|
||||
// === Incentive: Status ===
|
||||
|
||||
it('detects status types correctly', function () {
|
||||
$draft = new Incentive(['status' => 0]);
|
||||
$active = new Incentive(['status' => 1]);
|
||||
$closed = new Incentive(['status' => 2]);
|
||||
|
||||
expect($draft->isDraft())->toBeTrue()
|
||||
->and($draft->isActive())->toBeFalse()
|
||||
->and($active->isActive())->toBeTrue()
|
||||
->and($active->isDraft())->toBeFalse()
|
||||
->and($closed->isClosed())->toBeTrue()
|
||||
->and($closed->isActive())->toBeFalse();
|
||||
});
|
||||
|
||||
it('returns correct status colors', function () {
|
||||
expect((new Incentive(['status' => 0]))->getStatusColor())->toBe('warning')
|
||||
->and((new Incentive(['status' => 1]))->getStatusColor())->toBe('success')
|
||||
->and((new Incentive(['status' => 2]))->getStatusColor())->toBe('secondary');
|
||||
});
|
||||
|
||||
// === IncentiveParticipant: Qualifikation ===
|
||||
|
||||
it('qualifies with enough partners and abos', function () {
|
||||
$incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'qualified_partners' => 4,
|
||||
'qualified_abos' => 6,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->checkQualification())->toBeTrue()
|
||||
->and($participant->is_qualified)->toBeTrue();
|
||||
});
|
||||
|
||||
it('does not qualify with insufficient partners', function () {
|
||||
$incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'qualified_partners' => 3,
|
||||
'qualified_abos' => 6,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->checkQualification())->toBeFalse()
|
||||
->and($participant->is_qualified)->toBeFalse();
|
||||
});
|
||||
|
||||
it('does not qualify with insufficient abos', function () {
|
||||
$incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'qualified_partners' => 5,
|
||||
'qualified_abos' => 5,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->checkQualification())->toBeFalse();
|
||||
});
|
||||
|
||||
it('does not qualify with zero partners and zero abos', function () {
|
||||
$incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'qualified_partners' => 0,
|
||||
'qualified_abos' => 0,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->checkQualification())->toBeFalse();
|
||||
});
|
||||
|
||||
it('qualifies with exact minimums', function () {
|
||||
$incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'qualified_partners' => 4,
|
||||
'qualified_abos' => 6,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->checkQualification())->toBeTrue();
|
||||
});
|
||||
|
||||
it('qualifies with more than minimums', function () {
|
||||
$incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'qualified_partners' => 10,
|
||||
'qualified_abos' => 12,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->checkQualification())->toBeTrue();
|
||||
});
|
||||
|
||||
// === IncentiveParticipant: Winner ===
|
||||
|
||||
it('is winner when qualified and rank within max_winners', function () {
|
||||
$incentive = new Incentive(['max_winners' => 30]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'is_qualified' => true,
|
||||
'rank' => 5,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->isWinner())->toBeTrue();
|
||||
});
|
||||
|
||||
it('is winner at rank boundary', function () {
|
||||
$incentive = new Incentive(['max_winners' => 30]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'is_qualified' => true,
|
||||
'rank' => 30,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->isWinner())->toBeTrue();
|
||||
});
|
||||
|
||||
it('is not winner when rank exceeds max_winners', function () {
|
||||
$incentive = new Incentive(['max_winners' => 30]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'is_qualified' => true,
|
||||
'rank' => 31,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->isWinner())->toBeFalse();
|
||||
});
|
||||
|
||||
it('is not winner when not qualified even with good rank', function () {
|
||||
$incentive = new Incentive(['max_winners' => 30]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'is_qualified' => false,
|
||||
'rank' => 1,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->isWinner())->toBeFalse();
|
||||
});
|
||||
|
||||
it('is not winner when rank is null', function () {
|
||||
$incentive = new Incentive(['max_winners' => 30]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'is_qualified' => true,
|
||||
'rank' => null,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
expect($participant->isWinner())->toBeFalse();
|
||||
});
|
||||
|
||||
// === IncentivePointsLog: Helpers ===
|
||||
|
||||
it('calculates total points from onetime and accumulated', function () {
|
||||
$log = new IncentivePointsLog([
|
||||
'points_onetime' => 600,
|
||||
'points_accumulated' => 150,
|
||||
]);
|
||||
|
||||
expect($log->getTotalPoints())->toBe(750);
|
||||
});
|
||||
|
||||
it('formats month/year correctly', function () {
|
||||
$log = new IncentivePointsLog(['month' => 4, 'year' => 2026]);
|
||||
expect($log->getFormattedMonthYear())->toBe('04/2026');
|
||||
|
||||
$log2 = new IncentivePointsLog(['month' => 12, 'year' => 2026]);
|
||||
expect($log2->getFormattedMonthYear())->toBe('12/2026');
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use App\Models\UserAbo;
|
||||
use App\Services\Incentive\IncentiveTracker;
|
||||
|
||||
it('nutzt member_id als Berater bei Kundenabo ot', function () {
|
||||
$abo = new UserAbo([
|
||||
'is_for' => 'ot',
|
||||
'member_id' => 454,
|
||||
'user_id' => null,
|
||||
]);
|
||||
|
||||
expect(IncentiveTracker::consultantUserIdForAboIncentive($abo))->toBe(454);
|
||||
});
|
||||
|
||||
it('nutzt user_id als Berater bei Eigenabo me', function () {
|
||||
$abo = new UserAbo([
|
||||
'is_for' => 'me',
|
||||
'user_id' => 200,
|
||||
'member_id' => null,
|
||||
]);
|
||||
|
||||
expect(IncentiveTracker::consultantUserIdForAboIncentive($abo))->toBe(200);
|
||||
});
|
||||
|
||||
it('liefert null wenn ot ohne member_id', function () {
|
||||
$abo = new UserAbo([
|
||||
'is_for' => 'ot',
|
||||
'member_id' => null,
|
||||
'user_id' => null,
|
||||
]);
|
||||
|
||||
expect(IncentiveTracker::consultantUserIdForAboIncentive($abo))->toBeNull();
|
||||
});
|
||||
115
tests/Unit/Incentive/IncentiveTrackerTest.php
Normal file
115
tests/Unit/Incentive/IncentiveTrackerTest.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\IncentivePointsLog;
|
||||
use App\Services\Incentive\IncentiveTracker;
|
||||
|
||||
// === Ranking Logic ===
|
||||
|
||||
it('ranking logic assigns ranks by total_points descending', function () {
|
||||
$p1 = new IncentiveParticipant(['total_points' => 1000]);
|
||||
$p2 = new IncentiveParticipant(['total_points' => 3000]);
|
||||
$p3 = new IncentiveParticipant(['total_points' => 2000]);
|
||||
|
||||
// Simulate the ranking logic from IncentiveTracker::updateRanking
|
||||
$participants = collect([$p1, $p2, $p3])->sortByDesc('total_points')->values();
|
||||
$rank = 1;
|
||||
foreach ($participants as $p) {
|
||||
$p->rank = $rank;
|
||||
$rank++;
|
||||
}
|
||||
|
||||
expect($p2->rank)->toBe(1)
|
||||
->and($p3->rank)->toBe(2)
|
||||
->and($p1->rank)->toBe(3);
|
||||
});
|
||||
|
||||
// === Determine Log Type ===
|
||||
|
||||
it('determines abo type for shop orders', function () {
|
||||
$method = new ReflectionMethod(IncentiveTracker::class, 'determineLogType');
|
||||
|
||||
$usv = new \App\Models\UserSalesVolume([
|
||||
'status_turnover' => 2,
|
||||
'status_points' => 1,
|
||||
]);
|
||||
|
||||
expect($method->invoke(null, $usv))->toBe('abo');
|
||||
});
|
||||
|
||||
it('determines abo type for customer-only points', function () {
|
||||
$method = new ReflectionMethod(IncentiveTracker::class, 'determineLogType');
|
||||
|
||||
$usv = new \App\Models\UserSalesVolume([
|
||||
'status_turnover' => 1,
|
||||
'status_points' => 2,
|
||||
]);
|
||||
|
||||
expect($method->invoke(null, $usv))->toBe('abo');
|
||||
});
|
||||
|
||||
it('determines partner type for advisor orders', function () {
|
||||
$method = new ReflectionMethod(IncentiveTracker::class, 'determineLogType');
|
||||
|
||||
$usv = new \App\Models\UserSalesVolume([
|
||||
'status_turnover' => 1,
|
||||
'status_points' => 1,
|
||||
]);
|
||||
|
||||
expect($method->invoke(null, $usv))->toBe('partner');
|
||||
});
|
||||
|
||||
// === Edge Cases ===
|
||||
|
||||
it('qualification changes correctly on increment/decrement', function () {
|
||||
$incentive = new Incentive(['min_direct_partners' => 4, 'min_customer_abos' => 6]);
|
||||
|
||||
$participant = new IncentiveParticipant([
|
||||
'qualified_partners' => 3,
|
||||
'qualified_abos' => 6,
|
||||
]);
|
||||
$participant->setRelation('incentive', $incentive);
|
||||
|
||||
// Not qualified yet
|
||||
expect($participant->checkQualification())->toBeFalse();
|
||||
|
||||
// Add one more partner
|
||||
$participant->qualified_partners = 4;
|
||||
expect($participant->checkQualification())->toBeTrue();
|
||||
|
||||
// Storno removes a partner
|
||||
$participant->qualified_partners = 3;
|
||||
expect($participant->checkQualification())->toBeFalse();
|
||||
});
|
||||
|
||||
it('handles zero max_winners edge case', function () {
|
||||
$incentive = new Incentive(['max_winners' => 1]);
|
||||
|
||||
$winner = new IncentiveParticipant(['is_qualified' => true, 'rank' => 1]);
|
||||
$winner->setRelation('incentive', $incentive);
|
||||
|
||||
$loser = new IncentiveParticipant(['is_qualified' => true, 'rank' => 2]);
|
||||
$loser->setRelation('incentive', $incentive);
|
||||
|
||||
expect($winner->isWinner())->toBeTrue()
|
||||
->and($loser->isWinner())->toBeFalse();
|
||||
});
|
||||
|
||||
it('points log total handles negative values for storno', function () {
|
||||
$log = new IncentivePointsLog([
|
||||
'points_onetime' => -600,
|
||||
'points_accumulated' => 0,
|
||||
]);
|
||||
|
||||
expect($log->getTotalPoints())->toBe(-600);
|
||||
});
|
||||
|
||||
it('points log total handles mixed values', function () {
|
||||
$log = new IncentivePointsLog([
|
||||
'points_onetime' => 0,
|
||||
'points_accumulated' => 250,
|
||||
]);
|
||||
|
||||
expect($log->getTotalPoints())->toBe(250);
|
||||
});
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Product;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingOrderItem;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\AboHelper;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
it('stellt user_abo_items aus der letzten Bestellung wieder her wenn die Tabelle leer ist', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Standard',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'shop-owner-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 8),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'customer-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'cust-'.uniqid('', true).'@example.com',
|
||||
]);
|
||||
|
||||
$now = now();
|
||||
$productId = (int) DB::table('products')->insertGetId([
|
||||
'name' => 'Test Abo Produkt',
|
||||
'title' => 'Test Abo Produkt',
|
||||
'active' => true,
|
||||
'show_on' => json_encode(['12']),
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$product = Product::query()->findOrFail($productId);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'user_id' => null,
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => $shoppingUser->billing_email,
|
||||
'payone_userid' => 999001,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 10,
|
||||
'is_abo' => true,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
ShoppingOrderItem::create([
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'row_id' => 'row-'.uniqid(),
|
||||
'product_id' => $product->id,
|
||||
'comp' => 0,
|
||||
'qty' => 1,
|
||||
'price' => 90,
|
||||
'price_net' => 75,
|
||||
'tax_rate' => 19,
|
||||
'tax' => 15,
|
||||
'slug' => $product->slug ?? 'test',
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
expect($userAbo->user_abo_items()->count())->toBe(0);
|
||||
|
||||
expect(AboHelper::ensureUserAboItemsFromLatestOrder($userAbo))->toBeTrue();
|
||||
|
||||
$userAbo->refresh();
|
||||
|
||||
expect($userAbo->user_abo_items()->count())->toBe(1)
|
||||
->and($userAbo->user_abo_items->first()->product_id)->toBe($product->id);
|
||||
});
|
||||
|
||||
it('gibt true zurück wenn bereits user_abo_items existieren', function () {
|
||||
$country = Country::create([
|
||||
'code' => 'AT',
|
||||
'phone' => '43',
|
||||
'en' => 'Austria',
|
||||
'de' => 'Österreich',
|
||||
'es' => 'Austria',
|
||||
'fr' => 'Autriche',
|
||||
'it' => 'Austria',
|
||||
'ru' => 'Австрия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Std2',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'so-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS2'.substr(uniqid('', true), 0, 6),
|
||||
'slug' => 'ts2-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'co-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'cu-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'x-'.uniqid('', true).'@example.com',
|
||||
]);
|
||||
|
||||
$now = now();
|
||||
$productId = (int) DB::table('products')->insertGetId([
|
||||
'name' => 'P2',
|
||||
'title' => 'P2',
|
||||
'active' => true,
|
||||
'show_on' => json_encode(['12']),
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
$product = Product::query()->findOrFail($productId);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => $shoppingUser->billing_email,
|
||||
'payone_userid' => 999002,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$userAbo->user_abo_items()->create([
|
||||
'product_id' => $product->id,
|
||||
'comp' => 0,
|
||||
'qty' => 1,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
expect(AboHelper::ensureUserAboItemsFromLatestOrder($userAbo))->toBeTrue();
|
||||
expect($userAbo->user_abo_items()->count())->toBe(1);
|
||||
});
|
||||
53
tests/Unit/Services/AboHelperGetFirstAboDateTest.php
Normal file
53
tests/Unit/Services/AboHelperGetFirstAboDateTest.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
use App\Services\AboHelper;
|
||||
use Carbon\Carbon;
|
||||
|
||||
describe('getFirstAboDate', function () {
|
||||
beforeEach(function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-03-31 12:00:00', 'Europe/Berlin'));
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('schiebt Liefertag 5 vom 31. März auf Mai, weil April unter 10 Tagen liegt', function () {
|
||||
$first = AboHelper::getFirstAboDate(now(), 5);
|
||||
|
||||
expect($first->format('Y-m-d'))->toBe('2026-05-05');
|
||||
});
|
||||
|
||||
it('behält Liefertag 10 im April, wenn genau 10 Tage Abstand', function () {
|
||||
$first = AboHelper::getFirstAboDate(now(), 10);
|
||||
|
||||
expect($first->format('Y-m-d'))->toBe('2026-04-10');
|
||||
});
|
||||
|
||||
it('schiebt Liefertag 9 auf Mai, wenn der April-Termin nur 9 Tage Abstand hat', function () {
|
||||
// 31. März 2026 -> nächster Kandidat 9. April (9 Tage), muss auf Mai rutschen
|
||||
$first = AboHelper::getFirstAboDate(now(), 9);
|
||||
|
||||
expect($first->format('Y-m-d'))->toBe('2026-05-09');
|
||||
});
|
||||
});
|
||||
|
||||
describe('calendarDaysUntil', function () {
|
||||
it('zählt Kalendertage, nicht 24h-Intervalle bei Tageszeit von now()', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-03-31 14:30:00', 'Europe/Berlin'));
|
||||
|
||||
$apr10 = Carbon::parse('2026-04-10')->startOfDay();
|
||||
expect(AboHelper::calendarDaysUntil(now(), $apr10))->toBe(10);
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('liefert für Liefertag 5 (Mai) genug Tage, dass die Warnung unter 20 Tage nicht greift', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-03-31 12:00:00', 'Europe/Berlin'));
|
||||
$may5 = AboHelper::getFirstAboDate(now(), 5);
|
||||
|
||||
expect(AboHelper::calendarDaysUntil(now(), $may5))->toBeGreaterThanOrEqual(20);
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
});
|
||||
18
tests/Unit/Services/AboHelperSetAboStatusTest.php
Normal file
18
tests/Unit/Services/AboHelperSetAboStatusTest.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Services\AboHelper;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
afterEach(function () {
|
||||
\Mockery::close();
|
||||
});
|
||||
|
||||
it('setAboStatus tut nichts wenn getUserAbo null ist', function () {
|
||||
$order = \Mockery::mock(ShoppingOrder::class);
|
||||
$order->shouldReceive('getUserAbo')->once()->andReturn(null);
|
||||
|
||||
AboHelper::setAboStatus($order, 2, true);
|
||||
});
|
||||
46
tests/Unit/Services/AboOrdersOverviewTest.php
Normal file
46
tests/Unit/Services/AboOrdersOverviewTest.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingPayment;
|
||||
use App\Services\SyS\AboOrdersOverview;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
it('summiert nur erfolgreiche shopping_payments (Cent)', function () {
|
||||
$order = new ShoppingOrder(['total_shipping' => 119.00]);
|
||||
$failed = new ShoppingPayment(['amount' => 10000]);
|
||||
$failed->txaction = 'failed';
|
||||
$paid = new ShoppingPayment(['amount' => 11900]);
|
||||
$paid->txaction = 'paid';
|
||||
$order->setRelation('shopping_payments', collect([$failed, $paid]));
|
||||
|
||||
expect(AboOrdersOverview::actualChargedCentsFromPayments($order))->toBe(11900);
|
||||
});
|
||||
|
||||
it('addiert mehrere erfolgreiche Zahlungen', function () {
|
||||
$order = new ShoppingOrder;
|
||||
$p1 = new ShoppingPayment(['amount' => 5000]);
|
||||
$p1->txaction = 'paid';
|
||||
$p2 = new ShoppingPayment(['amount' => 6900]);
|
||||
$p2->txaction = 'extern_paid';
|
||||
$order->setRelation('shopping_payments', collect([$p1, $p2]));
|
||||
|
||||
expect(AboOrdersOverview::actualChargedCentsFromPayments($order))->toBe(11900);
|
||||
});
|
||||
|
||||
it('liefert null wenn keine erfolgreiche Zahlung existiert', function () {
|
||||
$order = new ShoppingOrder;
|
||||
$failed = new ShoppingPayment(['amount' => 10000]);
|
||||
$failed->txaction = 'failed';
|
||||
$order->setRelation('shopping_payments', collect([$failed]));
|
||||
|
||||
expect(AboOrdersOverview::actualChargedCentsFromPayments($order))->toBeNull();
|
||||
});
|
||||
|
||||
it('liefert null bei leeren Zahlungen', function () {
|
||||
$order = new ShoppingOrder;
|
||||
$order->setRelation('shopping_payments', collect());
|
||||
|
||||
expect(AboOrdersOverview::actualChargedCentsFromPayments($order))->toBeNull();
|
||||
});
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\Incentive;
|
||||
use App\Models\IncentiveNewAbo;
|
||||
use App\Models\IncentiveParticipant;
|
||||
use App\Models\IncentivePointsLog;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserSalesVolume;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\Incentive\IncentivePointsLogRepairService;
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
it('setzt fehlende incentive_new_abo_id an einem Log per Bestellung', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-15 10:00:00'));
|
||||
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create(['name' => 'S', 'active' => true]);
|
||||
$shippingCountry = ShippingCountry::create(['shipping_id' => $shipping->id, 'country_id' => $country->id]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'so-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'T',
|
||||
'slug' => 't-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'co-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'cu-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'e-'.uniqid('', true).'@example.com',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => $shoppingUser->billing_email,
|
||||
'payone_userid' => 1,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $consultant->id,
|
||||
]);
|
||||
|
||||
$newAbo = IncentiveNewAbo::create([
|
||||
'participant_id' => $participant->id,
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'activated_at' => Carbon::now()->subMonth(),
|
||||
]);
|
||||
|
||||
$order = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 10,
|
||||
'is_abo' => true,
|
||||
'paid' => true,
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
UserAboOrder::create([
|
||||
'user_abo_id' => $userAbo->id,
|
||||
'shopping_order_id' => $order->id,
|
||||
'status' => 2,
|
||||
'paid' => true,
|
||||
]);
|
||||
|
||||
$sv = UserSalesVolume::create([
|
||||
'user_id' => $customer->id,
|
||||
'shopping_order_id' => $order->id,
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'date' => '15.05.2026',
|
||||
'points' => 10,
|
||||
'total_net' => 90,
|
||||
'status_points' => 1,
|
||||
'status' => 2,
|
||||
'message' => 'x',
|
||||
]);
|
||||
|
||||
IncentivePointsLog::create([
|
||||
'participant_id' => $participant->id,
|
||||
'type' => 'abo',
|
||||
'source_type' => UserSalesVolume::class,
|
||||
'source_id' => $sv->id,
|
||||
'source_label' => 'alt',
|
||||
'month' => 5,
|
||||
'year' => 2026,
|
||||
'points_onetime' => 0,
|
||||
'points_accumulated' => 10,
|
||||
'user_sales_volume_id' => $sv->id,
|
||||
'incentive_new_abo_id' => null,
|
||||
'is_storno' => false,
|
||||
]);
|
||||
|
||||
$service = new IncentivePointsLogRepairService;
|
||||
$r = $service->repairForeignKeys($participant->fresh());
|
||||
|
||||
expect($r['abo_fk'])->toBe(1)
|
||||
->and(IncentivePointsLog::first()->incentive_new_abo_id)->toBe($newAbo->id);
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('legt fehlendes Abo-Tracking ohne UserAboOrder manuell an', function () {
|
||||
Carbon::setTestNow(Carbon::parse('2026-05-10 10:00:00'));
|
||||
|
||||
$country = Country::create([
|
||||
'code' => 'AT',
|
||||
'phone' => '43',
|
||||
'en' => 'Austria',
|
||||
'de' => 'Österreich',
|
||||
'es' => 'Austria',
|
||||
'fr' => 'Autriche',
|
||||
'it' => 'Austria',
|
||||
'ru' => 'Австрия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create(['name' => 'S2', 'active' => true]);
|
||||
ShippingCountry::create(['shipping_id' => $shipping->id, 'country_id' => $country->id]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'co2-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'cu2-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'e2-'.uniqid('', true).'@example.com',
|
||||
]);
|
||||
|
||||
$userAbo = UserAbo::create([
|
||||
'member_id' => $consultant->id,
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'is_for' => 'ot',
|
||||
'email' => $shoppingUser->billing_email,
|
||||
'payone_userid' => 2,
|
||||
'clearingtype' => 'cc',
|
||||
'active' => true,
|
||||
'status' => 2,
|
||||
'abo_interval' => 1,
|
||||
]);
|
||||
|
||||
$incentive = Incentive::factory()->create([
|
||||
'qualification_start' => '2026-04-01',
|
||||
'qualification_end' => '2026-07-31',
|
||||
'calculation_end' => '2026-08-31',
|
||||
'status' => 1,
|
||||
'points_abo_onetime' => 400,
|
||||
]);
|
||||
|
||||
$participant = IncentiveParticipant::factory()->create([
|
||||
'incentive_id' => $incentive->id,
|
||||
'user_id' => $consultant->id,
|
||||
]);
|
||||
|
||||
$service = new IncentivePointsLogRepairService;
|
||||
$added = $service->syncMissingTrackingAbos($participant->fresh());
|
||||
expect($added)->toBe(1)
|
||||
->and(IncentiveNewAbo::where('participant_id', $participant->id)->where('user_abo_id', $userAbo->id)->exists())->toBeTrue()
|
||||
->and(IncentivePointsLog::where('participant_id', $participant->id)->where('type', 'abo')->where('source_type', UserAbo::class)->exists())->toBeTrue();
|
||||
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
18
tests/Unit/Services/LocaleGuardTest.php
Normal file
18
tests/Unit/Services/LocaleGuardTest.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
use App\Services\LocaleGuard;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
it('normalizes supported locales', function () {
|
||||
expect(LocaleGuard::normalize('DE'))->toBe('de');
|
||||
expect(LocaleGuard::normalize('en'))->toBe('en');
|
||||
});
|
||||
|
||||
it('returns null for unsupported or invalid locale strings', function () {
|
||||
expect(LocaleGuard::normalize('-1 or 5*5=25 --'))->toBeNull();
|
||||
expect(LocaleGuard::normalize('xx'))->toBeNull();
|
||||
expect(LocaleGuard::normalize(null))->toBeNull();
|
||||
expect(LocaleGuard::normalize(''))->toBeNull();
|
||||
});
|
||||
147
tests/Unit/Services/PaymentAboCallbackCreatesUserAboTest.php
Normal file
147
tests/Unit/Services/PaymentAboCallbackCreatesUserAboTest.php
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Payone-Server-Callback kann vor dem Checkout-Redirect fertig sein: dann muss
|
||||
* Payment::paymentStatusPaidAction trotzdem UserAbo/UserAboOrder anlegen (createNewAbo).
|
||||
*/
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Models\PaymentTransaction;
|
||||
use App\Models\Shipping;
|
||||
use App\Models\ShippingCountry;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingPayment;
|
||||
use App\Models\ShoppingUser;
|
||||
use App\Models\UserAbo;
|
||||
use App\Models\UserAboOrder;
|
||||
use App\Models\UserShop;
|
||||
use App\Services\AboHelper;
|
||||
use App\Services\Payment;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
function paymentAboCallbackTestBootstrap(): array
|
||||
{
|
||||
$country = Country::create([
|
||||
'code' => 'DE',
|
||||
'phone' => '49',
|
||||
'en' => 'Germany',
|
||||
'de' => 'Deutschland',
|
||||
'es' => 'Alemania',
|
||||
'fr' => 'Allemagne',
|
||||
'it' => 'Germania',
|
||||
'ru' => 'Германия',
|
||||
]);
|
||||
|
||||
$shipping = Shipping::create([
|
||||
'name' => 'Standard',
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$shippingCountry = ShippingCountry::create([
|
||||
'shipping_id' => $shipping->id,
|
||||
'country_id' => $country->id,
|
||||
]);
|
||||
|
||||
$shopOwner = User::forceCreate([
|
||||
'email' => 'shop-owner-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$userShop = UserShop::create([
|
||||
'user_id' => $shopOwner->id,
|
||||
'name' => 'TS'.substr(uniqid('', true), 0, 8),
|
||||
'slug' => 'ts-'.uniqid(),
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$consultant = User::forceCreate([
|
||||
'email' => 'consultant-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$customer = User::forceCreate([
|
||||
'email' => 'customer-'.uniqid('', true).'@example.com',
|
||||
'password' => bcrypt('secret'),
|
||||
'lang' => 'de',
|
||||
]);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'billing_email' => 'cust-'.uniqid('', true).'@example.com',
|
||||
'is_for' => 'ot',
|
||||
]);
|
||||
|
||||
$shoppingOrder = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => $customer->id,
|
||||
'member_id' => $consultant->id,
|
||||
'country_id' => $shippingCountry->id,
|
||||
'user_shop_id' => $userShop->id,
|
||||
'payment_for' => 6,
|
||||
'points' => 10,
|
||||
'is_abo' => true,
|
||||
'abo_interval' => 15,
|
||||
'paid' => false,
|
||||
'txaction' => 'prev',
|
||||
'mode' => 'test',
|
||||
'total' => 100,
|
||||
'subtotal' => 90,
|
||||
]);
|
||||
|
||||
$shoppingPayment = ShoppingPayment::create([
|
||||
'shopping_order_id' => $shoppingOrder->id,
|
||||
'reference' => 'ref-'.uniqid(),
|
||||
'amount' => 10000,
|
||||
'currency' => 'EUR',
|
||||
'clearingtype' => 'cc',
|
||||
'abo_interval' => 15,
|
||||
'mode' => 'test',
|
||||
]);
|
||||
|
||||
PaymentTransaction::create([
|
||||
'shopping_payment_id' => $shoppingPayment->id,
|
||||
'request' => 'transaction',
|
||||
'txid' => 123456,
|
||||
'userid' => 987654,
|
||||
'status' => 'PAYONE',
|
||||
'txaction' => 'paid',
|
||||
'mode' => 'test',
|
||||
]);
|
||||
|
||||
return [$shoppingOrder, $shoppingPayment];
|
||||
}
|
||||
|
||||
it('legt UserAbo an wenn Payone-Callback vor Checkout-Erfolgsseite bezahlt (paymentStatusPaidAction)', function () {
|
||||
[$shoppingOrder, $shoppingPayment] = paymentAboCallbackTestBootstrap();
|
||||
|
||||
expect(UserAbo::count())->toBe(0);
|
||||
|
||||
Payment::paymentStatusPaidAction($shoppingOrder, true, $shoppingPayment);
|
||||
|
||||
$shoppingOrder->refresh();
|
||||
expect((bool) $shoppingOrder->paid)->toBeTrue();
|
||||
|
||||
$userAbo = $shoppingOrder->getUserAbo();
|
||||
expect($userAbo)->not->toBeNull();
|
||||
expect($userAbo->status)->toBe(2);
|
||||
expect(UserAboOrder::where('shopping_order_id', $shoppingOrder->id)->count())->toBe(1);
|
||||
});
|
||||
|
||||
it('createNewAbo ist idempotent wenn UserAboOrder bereits existiert', function () {
|
||||
[$shoppingOrder, $shoppingPayment] = paymentAboCallbackTestBootstrap();
|
||||
|
||||
AboHelper::createNewAbo($shoppingPayment);
|
||||
expect(UserAbo::count())->toBe(1);
|
||||
|
||||
AboHelper::createNewAbo($shoppingPayment);
|
||||
expect(UserAbo::count())->toBe(1);
|
||||
});
|
||||
27
tests/Unit/Services/PayoneCallbackTestbenchTest.php
Normal file
27
tests/Unit/Services/PayoneCallbackTestbenchTest.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingPayment;
|
||||
use App\Services\SyS\PayoneCallbackTestbench;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
it('baut Payone-Callback-Payload mit passendem price und param', function () {
|
||||
$order = new ShoppingOrder;
|
||||
$order->forceFill(['id' => 42]);
|
||||
$payment = ShoppingPayment::make([
|
||||
'amount' => 11_900,
|
||||
'reference' => 'abcd1234efgh5678',
|
||||
'mode' => 'test',
|
||||
'clearingtype' => 'wlt',
|
||||
]);
|
||||
|
||||
$payload = PayoneCallbackTestbench::buildPayoneCallbackPayload($order, $payment, 123456789);
|
||||
|
||||
expect($payload['price'])->toBe('119.00')
|
||||
->and($payload['param'])->toBe('42')
|
||||
->and($payload['txaction'])->toBe('paid')
|
||||
->and($payload['reference'])->toBe('abcd1234efgh5678')
|
||||
->and($payload['key'])->toBe((string) config('payone.defaults.key'));
|
||||
});
|
||||
96
tests/Unit/Services/PayoneTest.php
Normal file
96
tests/Unit/Services/PayoneTest.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
use App\Services\Payone;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use GuzzleHttp\HandlerStack;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
it('parst text/plain Antworten in ein assoziatives Array', function () {
|
||||
$body = "status=OK\nreference=abc123\nerrormessage=\n";
|
||||
$response = new Response(200, ['Content-Type' => 'text/plain;charset=UTF-8'], $body);
|
||||
|
||||
$parsed = Payone::parseResponse($response);
|
||||
|
||||
expect($parsed)->toBeArray()
|
||||
->and($parsed['status'])->toBe('OK')
|
||||
->and($parsed['reference'])->toBe('abc123')
|
||||
->and($parsed['errormessage'])->toBe('');
|
||||
});
|
||||
|
||||
it('parst Werte mit Gleichheitszeichen im Wert', function () {
|
||||
$body = "status=OK\nfoo=bar=baz\n";
|
||||
$response = new Response(200, ['Content-Type' => 'text/plain;charset=UTF-8'], $body);
|
||||
|
||||
$parsed = Payone::parseResponse($response);
|
||||
|
||||
expect($parsed['foo'])->toBe('bar=baz');
|
||||
});
|
||||
|
||||
it('sendRequest liefert das geparste Array bei text/plain;charset=UTF-8', function () {
|
||||
$mock = new MockHandler([
|
||||
new Response(200, ['Content-Type' => 'text/plain;charset=UTF-8'], "status=APPROVED\ntxid=999\n"),
|
||||
]);
|
||||
$client = new Client(['handler' => HandlerStack::create($mock)]);
|
||||
|
||||
$result = Payone::sendRequest(['request' => 'authorization'], '', $client);
|
||||
|
||||
expect($result)->toBeArray()
|
||||
->and($result['status'])->toBe('APPROVED')
|
||||
->and($result['txid'])->toBe('999');
|
||||
});
|
||||
|
||||
it('sendRequest wandelt Netzwerkfehler ohne HTTP-Antwort in Fehler 1002 um', function () {
|
||||
Mail::fake();
|
||||
|
||||
$mockRequest = new Request('POST', Payone::PAYONE_SERVER_API_URL);
|
||||
$mock = new MockHandler([
|
||||
new ConnectException('cURL error 56: Recv failure: Connection reset by peer', $mockRequest),
|
||||
]);
|
||||
$client = new Client(['handler' => HandlerStack::create($mock)]);
|
||||
|
||||
$thrown = null;
|
||||
try {
|
||||
Payone::sendRequest(['request' => 'authorization'], '', $client);
|
||||
} catch (HttpException $e) {
|
||||
$thrown = $e;
|
||||
}
|
||||
|
||||
expect($thrown)->toBeInstanceOf(HttpException::class)
|
||||
->and($thrown->getStatusCode())->toBe(403)
|
||||
->and($thrown->getMessage())->toContain('Fehlercode: 1002');
|
||||
});
|
||||
|
||||
it('exponiert die erwartete Post-Gateway-URL', function () {
|
||||
expect(Payone::PAYONE_SERVER_API_URL)->toBe('https://api.pay1.de/post-gateway/');
|
||||
});
|
||||
|
||||
/**
|
||||
* Manuell auf dem Server ausführen, wenn TLS/Outbound geprüft werden soll:
|
||||
* PAYONE_CONNECTIVITY_TEST=1 php artisan test --compact tests/Unit/Services/PayoneTest.php
|
||||
*/
|
||||
it('optional: Erreichbarkeit von api.pay1.de (TLS/Outbound)', function () {
|
||||
if (! env('PAYONE_CONNECTIVITY_TEST')) {
|
||||
$this->markTestSkipped('Setze PAYONE_CONNECTIVITY_TEST=1 für einen echten Netzwerk-Check.');
|
||||
}
|
||||
|
||||
$client = new Client([
|
||||
'timeout' => 15,
|
||||
'connect_timeout' => 10,
|
||||
'http_errors' => false,
|
||||
]);
|
||||
|
||||
$response = $client->get('https://api.pay1.de/', [
|
||||
'http_errors' => false,
|
||||
]);
|
||||
|
||||
expect($response->getStatusCode())->toBeGreaterThanOrEqual(200)
|
||||
->and($response->getStatusCode())->toBeLessThan(600);
|
||||
})->group('payone-connectivity');
|
||||
37
tests/Unit/Services/ProductOrderContextTest.php
Normal file
37
tests/Unit/Services/ProductOrderContextTest.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Services\ProductOrderContext;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
it('erlaubt Kunden-Shop show_on 3 für ot-customer ohne Abo', function () {
|
||||
$product = new Product(['show_on' => ['3']]);
|
||||
|
||||
expect(ProductOrderContext::isProductAllowedInContext($product, false, 'ot-customer'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('lehnt reine Abo show_on 12 für ot-customer ohne Abo ab', function () {
|
||||
$product = new Product(['show_on' => ['12']]);
|
||||
|
||||
expect(ProductOrderContext::isProductAllowedInContext($product, false, 'ot-customer'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('erlaubt Abo-Produkte für abo-ot-customer', function () {
|
||||
$product = new Product(['show_on' => ['12']]);
|
||||
|
||||
expect(ProductOrderContext::isProductAllowedInContext($product, true, 'abo-ot-customer'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('erlaubt Berater-Shop show_on 2 für me ohne Abo', function () {
|
||||
$product = new Product(['show_on' => ['2']]);
|
||||
|
||||
expect(ProductOrderContext::isProductAllowedInContext($product, false, 'me'))->toBeTrue();
|
||||
});
|
||||
|
||||
it('mapped customer webshop wie ot-customer ohne Abo', function () {
|
||||
$product = new Product(['show_on' => ['3']]);
|
||||
|
||||
expect(ProductOrderContext::isProductAllowedInCustomerWebshop($product))->toBeTrue();
|
||||
});
|
||||
22
tests/Unit/Services/UtilCustomerReorderCartUrlTest.php
Normal file
22
tests/Unit/Services/UtilCustomerReorderCartUrlTest.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use App\Services\Util;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
describe('isShopBaseUrlInvalidForUserCard', function () {
|
||||
it('lehnt leere Werte ab', function () {
|
||||
expect(Util::isShopBaseUrlInvalidForUserCard(null))->toBeTrue();
|
||||
expect(Util::isShopBaseUrlInvalidForUserCard(''))->toBeTrue();
|
||||
});
|
||||
|
||||
it('lehnt Portal-Host ab', function () {
|
||||
$portalHost = config('domains.domains.portal.host');
|
||||
expect(Util::isShopBaseUrlInvalidForUserCard('https://'.$portalHost))->toBeTrue();
|
||||
});
|
||||
|
||||
it('akzeptiert typischen User-Shop-Subdomain', function () {
|
||||
expect(Util::isShopBaseUrlInvalidForUserCard('https://testberater.'.config('app.domain').config('app.tld_care')))->toBeFalse();
|
||||
});
|
||||
});
|
||||
55
tests/Unit/UserShowSideNavTest.php
Normal file
55
tests/Unit/UserShowSideNavTest.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class);
|
||||
|
||||
it('zeigt die Seitennavigation für aktive Berater', function () {
|
||||
$user = new User;
|
||||
$user->forceFill([
|
||||
'active' => 1,
|
||||
'blocked' => 0,
|
||||
'wizard' => 100,
|
||||
'payment_account' => Carbon::now()->addYear()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
expect($user->showSideNav())->toBeTrue();
|
||||
});
|
||||
|
||||
it('zeigt die Seitennavigation bei abgelaufener Mitgliedschaft trotz active=0', function () {
|
||||
$user = new User;
|
||||
$user->forceFill([
|
||||
'active' => 0,
|
||||
'blocked' => 0,
|
||||
'wizard' => 100,
|
||||
'payment_account' => Carbon::now()->subMonth()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
expect($user->showSideNav())->toBeTrue();
|
||||
});
|
||||
|
||||
it('blendet die Seitennavigation bei gesperrtem Account aus', function () {
|
||||
$user = new User;
|
||||
$user->forceFill([
|
||||
'active' => 1,
|
||||
'blocked' => 1,
|
||||
'wizard' => 100,
|
||||
'payment_account' => Carbon::now()->addYear()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
expect($user->showSideNav())->toBeFalse();
|
||||
});
|
||||
|
||||
it('blendet die Seitennavigation bei unvollständigem Wizard aus', function () {
|
||||
$user = new User;
|
||||
$user->forceFill([
|
||||
'active' => 1,
|
||||
'blocked' => 0,
|
||||
'wizard' => 5,
|
||||
'payment_account' => Carbon::now()->addYear()->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
expect($user->showSideNav())->toBeFalse();
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue