diff --git a/src/app/Console/Commands/User/RemoveSkuCommand.php b/src/app/Console/Commands/User/RemoveSkuCommand.php new file mode 100644 --- /dev/null +++ b/src/app/Console/Commands/User/RemoveSkuCommand.php @@ -0,0 +1,58 @@ +getUser($this->argument('user')); + + if (!$user) { + $this->error("User not found."); + return 1; + } + + $sku = $this->getObject(\App\Sku::class, $this->argument('sku'), 'title'); + + if (!$sku) { + $this->error("Unable to find the SKU {$this->argument('sku')}."); + return 1; + } + + $quantity = ((int) $this->option('qty')) ?: 1; + + if ($user->entitlements()->where('sku_id', $sku->id)->count() < $quantity) { + $this->error("There aren't that many entitlements."); + return 1; + } + + // removeSku() can charge the user wallet, let's use database transaction + + DB::beginTransaction(); + $user->removeSku($sku, $quantity); + DB::commit(); + } +} diff --git a/src/tests/Feature/Console/User/RemoveSkuTest.php b/src/tests/Feature/Console/User/RemoveSkuTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Feature/Console/User/RemoveSkuTest.php @@ -0,0 +1,79 @@ +deleteTestUser('remove-entitlement@kolabnow.com'); + } + + /** + * {@inheritDoc} + */ + public function tearDown(): void + { + $this->deleteTestUser('remove-entitlement@kolabnow.com'); + + parent::tearDown(); + } + + /** + * Test command runs + */ + public function testHandle(): void + { + $storage = \App\Sku::withEnvTenantContext()->where('title', 'storage')->first(); + $user = $this->getTestUser('remove-entitlement@kolabnow.com'); + + // Unknown user + $this->artisan("user:remove-sku unknown@unknown.org {$storage->id}") + ->assertExitCode(1) + ->expectsOutput("User not found."); + + // Unknown SKU + $this->artisan("user:remove-sku {$user->email} unknownsku") + ->assertExitCode(1) + ->expectsOutput("Unable to find the SKU unknownsku."); + + // Invalid quantity + $this->artisan("user:remove-sku {$user->email} {$storage->id} --qty=5") + ->assertExitCode(1) + ->expectsOutput("There aren't that many entitlements."); + + $user->assignSku($storage, 80); + $entitlements = $user->entitlements()->where('sku_id', $storage->id); + $this->assertSame(80, $entitlements->count()); + + // Backdate entitlements so they are charged on removal + $this->backdateEntitlements( + $entitlements->get(), + \Carbon\Carbon::now()->clone()->subWeeks(4), + \Carbon\Carbon::now()->clone()->subWeeks(4) + ); + + // Remove single entitlement + $this->artisan("user:remove-sku {$user->email} {$storage->title}") + ->assertExitCode(0); + + $this->assertSame(79, $entitlements->count()); + + // Mass removal + $start = microtime(true); + $this->artisan("user:remove-sku {$user->email} {$storage->id} --qty=78") + ->assertExitCode(0); + + // 5GB is free, so it should stay at 5 + $this->assertSame(5, $entitlements->count()); + $this->assertTrue($user->wallet()->balance < 0); + $this->assertTrue(microtime(true) - $start < 6); // TODO: Make it faster + } +} diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php --- a/src/tests/Feature/Controller/UsersTest.php +++ b/src/tests/Feature/Controller/UsersTest.php @@ -1230,7 +1230,7 @@ $groupware = Sku::withEnvTenantContext()->where('title', 'groupware')->first(); $mailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); - // standard package, 1 mailbox, 1 groupware, 2 storage + // standard package, 1 mailbox, 1 groupware, 5 storage $jane->assignPackage($kolab); // add 2 storage, 1 activesync