Changeset View
Changeset View
Standalone View
Standalone View
src/tests/Feature/WalletTest.php
<?php | <?php | ||||
namespace Tests\Feature; | namespace Tests\Feature; | ||||
use App\Package; | use App\Package; | ||||
use App\Plan; | |||||
use App\User; | use App\User; | ||||
use App\Sku; | use App\Sku; | ||||
use App\Transaction; | use App\Transaction; | ||||
use App\Wallet; | use App\Wallet; | ||||
use Carbon\Carbon; | use Carbon\Carbon; | ||||
use Illuminate\Support\Facades\DB; | use Illuminate\Support\Facades\DB; | ||||
use Illuminate\Support\Facades\Queue; | use Illuminate\Support\Facades\Queue; | ||||
use Tests\TestCase; | use Tests\TestCase; | ||||
class WalletTest extends TestCase | class WalletTest extends TestCase | ||||
{ | { | ||||
private $users = [ | private $users = [ | ||||
'UserWallet1@UserWallet.com', | 'UserWallet1@UserWallet.com', | ||||
'UserWallet2@UserWallet.com', | 'UserWallet2@UserWallet.com', | ||||
'UserWallet3@UserWallet.com', | 'UserWallet3@UserWallet.com', | ||||
'UserWallet4@UserWallet.com', | 'UserWallet4@UserWallet.com', | ||||
'UserWallet5@UserWallet.com', | 'UserWallet5@UserWallet.com', | ||||
'WalletControllerA@WalletController.com', | 'WalletControllerA@WalletController.com', | ||||
'WalletControllerB@WalletController.com', | 'WalletControllerB@WalletController.com', | ||||
'WalletController2A@WalletController.com', | 'WalletController2A@WalletController.com', | ||||
'WalletController2B@WalletController.com', | 'WalletController2B@WalletController.com', | ||||
'jane@kolabnow.com' | 'jane@kolabnow.com' | ||||
]; | ]; | ||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
public function setUp(): void | public function setUp(): void | ||||
{ | { | ||||
parent::setUp(); | parent::setUp(); | ||||
Carbon::setTestNow(Carbon::createFromDate(2022, 02, 02)); | Carbon::setTestNow(Carbon::createFromDate(2022, 02, 02)); | ||||
foreach ($this->users as $user) { | foreach ($this->users as $user) { | ||||
$this->deleteTestUser($user); | $this->deleteTestUser($user); | ||||
} | } | ||||
Sku::select()->update(['fee' => 0]); | |||||
} | } | ||||
/** | |||||
* {@inheritDoc} | |||||
*/ | |||||
public function tearDown(): void | public function tearDown(): void | ||||
{ | { | ||||
foreach ($this->users as $user) { | foreach ($this->users as $user) { | ||||
$this->deleteTestUser($user); | $this->deleteTestUser($user); | ||||
} | } | ||||
Sku::select()->update(['fee' => 0]); | Sku::select()->update(['fee' => 0]); | ||||
Show All 37 Lines | class WalletTest extends TestCase | ||||
*/ | */ | ||||
public function testBalanceLastsUntil(): void | public function testBalanceLastsUntil(): void | ||||
{ | { | ||||
// Monthly cost of all entitlements: 990 | // Monthly cost of all entitlements: 990 | ||||
// 28 days: 35.36 per day | // 28 days: 35.36 per day | ||||
// 31 days: 31.93 per day | // 31 days: 31.93 per day | ||||
$user = $this->getTestUser('jane@kolabnow.com'); | $user = $this->getTestUser('jane@kolabnow.com'); | ||||
$package = Package::withEnvTenantContext()->where('title', 'kolab')->first(); | $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first(); | ||||
$user->assignPackage($package); | $user->assignPlan($plan); | ||||
$wallet = $user->wallets()->first(); | $wallet = $user->wallets()->first(); | ||||
// User/entitlements created today, balance=0 | // User/entitlements created today, balance=0 | ||||
$until = $wallet->balanceLastsUntil(); | $until = $wallet->balanceLastsUntil(); | ||||
$this->assertSame( | $this->assertSame( | ||||
Carbon::now()->addMonthsWithoutOverflow(1)->toDateString(), | Carbon::now()->addMonthsWithoutOverflow(1)->toDateString(), | ||||
$until->toDateString() | $until->toDateString() | ||||
Show All 30 Lines | public function testBalanceLastsUntil(): void | ||||
$wallet->entitlements()->delete(); | $wallet->entitlements()->delete(); | ||||
$until = $wallet->refresh()->balanceLastsUntil(); | $until = $wallet->refresh()->balanceLastsUntil(); | ||||
$this->assertSame(null, $until); | $this->assertSame(null, $until); | ||||
} | } | ||||
/** | /** | ||||
* Test for Wallet::costsPerDay() | |||||
*/ | |||||
public function testCostsPerDay(): void | |||||
{ | |||||
// 990 | |||||
// 28 days: 35.36 | |||||
// 31 days: 31.93 | |||||
$user = $this->getTestUser('jane@kolabnow.com'); | |||||
$package = Package::withEnvTenantContext()->where('title', 'kolab')->first(); | |||||
$mailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); | |||||
$user->assignPackage($package); | |||||
$wallet = $user->wallets()->first(); | |||||
$costsPerDay = $wallet->costsPerDay(); | |||||
$this->assertTrue($costsPerDay < 35.38); | |||||
$this->assertTrue($costsPerDay > 31.93); | |||||
} | |||||
/** | |||||
* Verify a wallet is created, when a user is created. | * Verify a wallet is created, when a user is created. | ||||
*/ | */ | ||||
public function testCreateUserCreatesWallet(): void | public function testCreateUserCreatesWallet(): void | ||||
{ | { | ||||
$user = $this->getTestUser('UserWallet1@UserWallet.com'); | $user = $this->getTestUser('UserWallet1@UserWallet.com'); | ||||
$this->assertCount(1, $user->wallets); | $this->assertCount(1, $user->wallets); | ||||
$this->assertSame(\config('app.currency'), $user->wallets[0]->currency); | $this->assertSame(\config('app.currency'), $user->wallets[0]->currency); | ||||
▲ Show 20 Lines • Show All 155 Lines • ▼ Show 20 Lines | public function testChargeAndDeleteEntitlements(): void | ||||
$wallet = $user->wallets()->first(); | $wallet = $user->wallets()->first(); | ||||
$discount = \App\Discount::withEnvTenantContext()->where('discount', 30)->first(); | $discount = \App\Discount::withEnvTenantContext()->where('discount', 30)->first(); | ||||
$wallet->discount()->associate($discount); | $wallet->discount()->associate($discount); | ||||
$wallet->save(); | $wallet->save(); | ||||
// Add 40% fee to all SKUs | // Add 40% fee to all SKUs | ||||
Sku::select()->update(['fee' => DB::raw("`cost` * 0.4")]); | Sku::select()->update(['fee' => DB::raw("`cost` * 0.4")]); | ||||
$package = Package::withEnvTenantContext()->where('title', 'kolab')->first(); | $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first(); | ||||
$storage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); | $storage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); | ||||
$user->assignPackage($package); | $user->assignPlan($plan); | ||||
$user->assignSku($storage, 5); | $user->assignSku($storage, 5); | ||||
$user->refresh(); | $user->setSetting('plan_id', null); // disable plan and trial | ||||
// Reset reseller's wallet balance and transactions | // Reset reseller's wallet balance and transactions | ||||
$reseller_wallet = $user->tenant->wallet(); | $reseller_wallet = $user->tenant->wallet(); | ||||
$reseller_wallet->balance = 0; | $reseller_wallet->balance = 0; | ||||
$reseller_wallet->save(); | $reseller_wallet->save(); | ||||
Transaction::where('object_id', $reseller_wallet->id)->where('object_type', \App\Wallet::class)->delete(); | Transaction::where('object_id', $reseller_wallet->id)->where('object_type', \App\Wallet::class)->delete(); | ||||
// ------------------------------------ | // ------------------------------------ | ||||
// Test normal charging of entitlements | // Test normal charging of entitlements | ||||
// ------------------------------------ | // ------------------------------------ | ||||
// Backdate and charge entitlements, we're expecting one month to be charged | // Backdate and charge entitlements, we're expecting one month to be charged | ||||
// Set fake NOW date to make simpler asserting results that depend on number of days in current/last month | // Set fake NOW date to make simpler asserting results that depend on number of days in current/last month | ||||
Carbon::setTestNow(Carbon::create(2021, 5, 21, 12)); | Carbon::setTestNow(Carbon::create(2021, 5, 21, 12)); | ||||
$backdate = Carbon::now()->subWeeks(7); | $backdate = Carbon::now()->subWeeks(7); | ||||
$this->backdateEntitlements($user->entitlements, $backdate); | $this->backdateEntitlements($user->entitlements, $backdate); | ||||
$charge = $wallet->chargeEntitlements(); | $charge = $wallet->chargeEntitlements(); | ||||
$wallet->refresh(); | $wallet->refresh(); | ||||
$reseller_wallet->refresh(); | $reseller_wallet->refresh(); | ||||
// TODO: Update these comments with what is actually being used to calculate these numbers | // User discount is 30% | ||||
// 388 + 310 + 17 + 17 = 732 | // Expected: groupware: 490 x 70% + mailbox: 500 x 70% + storage: 5 x round(25x70%) = 778 | ||||
$this->assertSame(-778, $wallet->balance); | $this->assertSame(-778, $wallet->balance); | ||||
// 388 - 555 x 40% + 310 - 444 x 40% + 34 - 50 x 40% = 312 | // Reseller fee is 40% | ||||
// Expected: groupware: 490 x 30% + mailbox: 500 x 30% + storage: 5 x round(25x30%) = 332 | |||||
$this->assertSame(332, $reseller_wallet->balance); | $this->assertSame(332, $reseller_wallet->balance); | ||||
$transactions = Transaction::where('object_id', $wallet->id) | $transactions = Transaction::where('object_id', $wallet->id) | ||||
->where('object_type', \App\Wallet::class)->get(); | ->where('object_type', \App\Wallet::class)->get(); | ||||
$reseller_transactions = Transaction::where('object_id', $reseller_wallet->id) | $reseller_transactions = Transaction::where('object_id', $reseller_wallet->id) | ||||
->where('object_type', \App\Wallet::class)->get(); | ->where('object_type', \App\Wallet::class)->get(); | ||||
$this->assertCount(1, $reseller_transactions); | $this->assertCount(1, $reseller_transactions); | ||||
$trans = $reseller_transactions[0]; | $trans = $reseller_transactions[0]; | ||||
$this->assertSame("Charged user jane@kolabnow.com", $trans->description); | $this->assertSame("Charged user jane@kolabnow.com", $trans->description); | ||||
$this->assertSame(332, $trans->amount); | $this->assertSame(332, $trans->amount); | ||||
$this->assertSame(Transaction::WALLET_CREDIT, $trans->type); | $this->assertSame(Transaction::WALLET_CREDIT, $trans->type); | ||||
$this->assertCount(1, $transactions); | $this->assertCount(1, $transactions); | ||||
$trans = $transactions[0]; | $trans = $transactions[0]; | ||||
$this->assertSame('', $trans->description); | $this->assertSame('', $trans->description); | ||||
$this->assertSame(-778, $trans->amount); | $this->assertSame(-778, $trans->amount); | ||||
$this->assertSame(Transaction::WALLET_DEBIT, $trans->type); | $this->assertSame(Transaction::WALLET_DEBIT, $trans->type); | ||||
// TODO: Test entitlement transaction records | // Assert all entitlements' updated_at timestamp | ||||
$date = $backdate->addMonthsWithoutOverflow(1); | |||||
$this->assertCount(12, $wallet->entitlements()->where('updated_at', $date)->get()); | |||||
// ----------------------------------- | // ----------------------------------- | ||||
// Test charging on entitlement delete | // Test charging on entitlement delete | ||||
// ----------------------------------- | // ----------------------------------- | ||||
$reseller_wallet->balance = 0; | $reseller_wallet->balance = 0; | ||||
$reseller_wallet->save(); | $reseller_wallet->save(); | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | public function testChargeAndDeleteEntitlements(): void | ||||
$this->assertSame('', $trans->description); | $this->assertSame('', $trans->description); | ||||
$this->assertSame(-11, $trans->amount); | $this->assertSame(-11, $trans->amount); | ||||
$this->assertSame(Transaction::WALLET_DEBIT, $trans->type); | $this->assertSame(Transaction::WALLET_DEBIT, $trans->type); | ||||
// TODO: Test entitlement transaction records | // TODO: Test entitlement transaction records | ||||
} | } | ||||
/** | /** | ||||
* Test for charging and removing entitlements when in trial | |||||
*/ | |||||
public function testChargeAndDeleteEntitlementsTrial(): void | |||||
{ | |||||
$user = $this->getTestUser('jane@kolabnow.com'); | |||||
$wallet = $user->wallets()->first(); | |||||
$plan = Plan::withEnvTenantContext()->where('title', 'individual')->first(); | |||||
$storage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); | |||||
$user->assignPlan($plan); | |||||
$user->assignSku($storage, 5); | |||||
// ------------------------------------ | |||||
// Test normal charging of entitlements | |||||
// ------------------------------------ | |||||
// Backdate and charge entitlements, we're expecting one month to be charged | |||||
// Set fake NOW date to make simpler asserting results that depend on number of days in current/last month | |||||
Carbon::setTestNow(Carbon::create(2021, 5, 21, 12)); | |||||
$backdate = Carbon::now()->subWeeks(7); | |||||
$this->backdateEntitlements($user->entitlements, $backdate); | |||||
$charge = $wallet->chargeEntitlements(); | |||||
$wallet->refresh(); | |||||
// Expected: storage: 5 x 25 = 125 (the rest is free in trial) | |||||
$this->assertSame($balance = -125, $wallet->balance); | |||||
// Assert wallet transaction | |||||
$transactions = $wallet->transactions()->get(); | |||||
$this->assertCount(1, $transactions); | |||||
$trans = $transactions[0]; | |||||
$this->assertSame('', $trans->description); | |||||
$this->assertSame($balance, $trans->amount); | |||||
$this->assertSame(Transaction::WALLET_DEBIT, $trans->type); | |||||
// Assert entitlement transactions | |||||
$etransactions = Transaction::where('transaction_id', $trans->id)->get(); | |||||
$this->assertCount(5, $etransactions); | |||||
$trans = $etransactions[0]; | |||||
$this->assertSame(null, $trans->description); | |||||
$this->assertSame(25, $trans->amount); | |||||
$this->assertSame(Transaction::ENTITLEMENT_BILLED, $trans->type); | |||||
// Assert all entitlements' updated_at timestamp | |||||
$date = $backdate->addMonthsWithoutOverflow(1); | |||||
$this->assertCount(12, $wallet->entitlements()->where('updated_at', $date)->get()); | |||||
// Run again, expect no changes | |||||
$charge = $wallet->chargeEntitlements(); | |||||
$wallet->refresh(); | |||||
$this->assertSame($balance, $wallet->balance); | |||||
$this->assertCount(1, $wallet->transactions()->get()); | |||||
$this->assertCount(12, $wallet->entitlements()->where('updated_at', $date)->get()); | |||||
// ----------------------------------- | |||||
// Test charging on entitlement delete | |||||
// ----------------------------------- | |||||
$wallet->transactions()->delete(); | |||||
$user->removeSku($storage, 2); | |||||
$wallet->refresh(); | |||||
// we expect the wallet to have been charged for 19 days of use of | |||||
// 2 deleted storage entitlements: 2 x round(25 / 31 * 19) = 30 | |||||
$this->assertSame($balance -= 30, $wallet->balance); | |||||
// Assert wallet transactions | |||||
$transactions = $wallet->transactions()->get(); | |||||
$this->assertCount(2, $transactions); | |||||
$trans = $transactions[0]; | |||||
$this->assertSame('', $trans->description); | |||||
$this->assertSame(-15, $trans->amount); | |||||
$this->assertSame(Transaction::WALLET_DEBIT, $trans->type); | |||||
$trans = $transactions[1]; | |||||
$this->assertSame('', $trans->description); | |||||
$this->assertSame(-15, $trans->amount); | |||||
$this->assertSame(Transaction::WALLET_DEBIT, $trans->type); | |||||
// Assert entitlement transactions | |||||
/* Note: Commented out because the observer does not create per-entitlement transactions | |||||
$etransactions = Transaction::where('transaction_id', $transactions[0]->id)->get(); | |||||
$this->assertCount(1, $etransactions); | |||||
$trans = $etransactions[0]; | |||||
$this->assertSame(null, $trans->description); | |||||
$this->assertSame(15, $trans->amount); | |||||
$this->assertSame(Transaction::ENTITLEMENT_BILLED, $trans->type); | |||||
$etransactions = Transaction::where('transaction_id', $transactions[1]->id)->get(); | |||||
$this->assertCount(1, $etransactions); | |||||
$trans = $etransactions[0]; | |||||
$this->assertSame(null, $trans->description); | |||||
$this->assertSame(15, $trans->amount); | |||||
$this->assertSame(Transaction::ENTITLEMENT_BILLED, $trans->type); | |||||
*/ | |||||
} | |||||
/** | |||||
* Tests for updateEntitlements() | * Tests for updateEntitlements() | ||||
*/ | */ | ||||
public function testUpdateEntitlements(): void | public function testUpdateEntitlements(): void | ||||
{ | { | ||||
$this->markTestIncomplete(); | $this->markTestIncomplete(); | ||||
} | } | ||||
} | } |