Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117760631
D1924.1775213192.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
25 KB
Referenced Files
None
Subscribers
None
D1924.1775213192.diff
View Options
diff --git a/src/app/Console/Commands/DomainRestore.php b/src/app/Console/Commands/DomainRestore.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/DomainRestore.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class DomainRestore extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'domain:restore {domain}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Restore (undelete) a domain';
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $domain = \App\Domain::withTrashed()->where('namespace', $this->argument('domain'))->first();
+
+ if (!$domain) {
+ $this->error("Domain not found.");
+ return 1;
+ }
+
+ if (!$domain->trashed()) {
+ $this->error("The domain is not yet deleted.");
+ return 1;
+ }
+
+ $wallet = $domain->wallet();
+
+ if ($wallet && !$wallet->owner) {
+ $this->error("The domain owner is deleted.");
+ return 1;
+ }
+
+ DB::beginTransaction();
+ $domain->restore();
+ DB::commit();
+ }
+}
diff --git a/src/app/Console/Commands/UserRestore.php b/src/app/Console/Commands/UserRestore.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/UserRestore.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class UserRestore extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'user:restore {user}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Restore (undelete) a user';
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $user = \App\User::withTrashed()->where('email', $this->argument('user'))->first();
+
+ if (!$user) {
+ $this->error('User not found.');
+ return 1;
+ }
+
+ if (!$user->trashed()) {
+ $this->error('The user is not yet deleted.');
+ return 1;
+ }
+
+ DB::beginTransaction();
+ $user->restore();
+ DB::commit();
+ }
+}
diff --git a/src/app/Console/ObjectUpdateCommand.php b/src/app/Console/ObjectUpdateCommand.php
--- a/src/app/Console/ObjectUpdateCommand.php
+++ b/src/app/Console/ObjectUpdateCommand.php
@@ -2,6 +2,7 @@
namespace App\Console;
+use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Schema;
/**
diff --git a/src/app/Jobs/Domain/DeleteJob.php b/src/app/Jobs/Domain/DeleteJob.php
--- a/src/app/Jobs/Domain/DeleteJob.php
+++ b/src/app/Jobs/Domain/DeleteJob.php
@@ -24,6 +24,11 @@
\App\Backends\LDAP::deleteDomain($domain);
$domain->status |= \App\Domain::STATUS_DELETED;
+
+ if ($domain->isLdapReady()) {
+ $domain->status ^= \App\Domain::STATUS_LDAP_READY;
+ }
+
$domain->save();
}
}
diff --git a/src/app/Jobs/Domain/VerifyJob.php b/src/app/Jobs/Domain/VerifyJob.php
--- a/src/app/Jobs/Domain/VerifyJob.php
+++ b/src/app/Jobs/Domain/VerifyJob.php
@@ -16,9 +16,5 @@
$domain = $this->getDomain();
$domain->verify();
-
- // TODO: What should happen if the domain is not registered yet?
- // Should we start a new job with some specified delay?
- // Or we just give the user a button to start verification again?
}
}
diff --git a/src/app/Jobs/User/DeleteJob.php b/src/app/Jobs/User/DeleteJob.php
--- a/src/app/Jobs/User/DeleteJob.php
+++ b/src/app/Jobs/User/DeleteJob.php
@@ -28,6 +28,15 @@
\App\Backends\LDAP::deleteUser($user);
$user->status |= \App\User::STATUS_DELETED;
+
+ if ($user->isLdapReady()) {
+ $user->status ^= \App\User::STATUS_LDAP_READY;
+ }
+
+ if ($user->isImapReady()) {
+ $user->status ^= \App\User::STATUS_IMAP_READY;
+ }
+
$user->save();
}
}
diff --git a/src/app/Observers/DomainObserver.php b/src/app/Observers/DomainObserver.php
--- a/src/app/Observers/DomainObserver.php
+++ b/src/app/Observers/DomainObserver.php
@@ -87,6 +87,32 @@
\App\Jobs\Domain\UpdateJob::dispatch($domain->id);
}
+ /**
+ * Handle the domain "restoring" event.
+ *
+ * @param \App\Domain $domain The domain.
+ *
+ * @return void
+ */
+ public function restoring(Domain $domain)
+ {
+ // Make sure it's not DELETED/LDAP_READY/SUSPENDED
+ if ($domain->isDeleted()) {
+ $domain->status ^= Domain::STATUS_DELETED;
+ }
+ if ($domain->isLdapReady()) {
+ $domain->status ^= Domain::STATUS_LDAP_READY;
+ }
+ if ($domain->isSuspended()) {
+ $domain->status ^= Domain::STATUS_SUSPENDED;
+ }
+ if ($domain->isConfirmed() && $domain->isVerified()) {
+ $domain->status |= Domain::STATUS_ACTIVE;
+ }
+
+ // Note: $domain->save() is invoked between 'restoring' and 'restored' events
+ }
+
/**
* Handle the domain "restored" event.
*
@@ -96,7 +122,25 @@
*/
public function restored(Domain $domain)
{
- //
+ // Restore domain entitlements
+ // We'll restore only these that were deleted last. So, first we get
+ // the maximum deleted_at timestamp and then use it to select
+ // domain entitlements for restore
+ $deleted_at = \App\Entitlement::withTrashed()
+ ->where('entitleable_id', $domain->id)
+ ->where('entitleable_type', Domain::class)
+ ->max('deleted_at');
+
+ if ($deleted_at) {
+ \App\Entitlement::withTrashed()
+ ->where('entitleable_id', $domain->id)
+ ->where('entitleable_type', Domain::class)
+ ->where('deleted_at', '>=', (new \Carbon\Carbon($deleted_at))->subMinute())
+ ->update(['updated_at' => now(), 'deleted_at' => null]);
+ }
+
+ // Create the domain in LDAP again
+ \App\Jobs\Domain\CreateJob::dispatch($domain->id);
}
/**
diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php
--- a/src/app/Observers/UserObserver.php
+++ b/src/app/Observers/UserObserver.php
@@ -254,6 +254,86 @@
->delete();
}
+ /**
+ * Handle the user "restoring" event.
+ *
+ * @param \App\User $user The user
+ *
+ * @return void
+ */
+ public function restoring(User $user)
+ {
+ // Make sure it's not DELETED/LDAP_READY/IMAP_READY/SUSPENDED anymore
+ if ($user->isDeleted()) {
+ $user->status ^= User::STATUS_DELETED;
+ }
+ if ($user->isLdapReady()) {
+ $user->status ^= User::STATUS_LDAP_READY;
+ }
+ if ($user->isImapReady()) {
+ $user->status ^= User::STATUS_IMAP_READY;
+ }
+ if ($user->isSuspended()) {
+ $user->status ^= User::STATUS_SUSPENDED;
+ }
+
+ $user->status |= User::STATUS_ACTIVE;
+
+ // Note: $user->save() is invoked between 'restoring' and 'restored' events
+ }
+
+ /**
+ * Handle the user "restored" event.
+ *
+ * @param \App\User $user The user
+ *
+ * @return void
+ */
+ public function restored(User $user)
+ {
+ $wallets = $user->wallets()->pluck('id')->all();
+
+ // Restore user entitlements
+ // We'll restore only these that were deleted last. So, first we get
+ // the maximum deleted_at timestamp and then use it to select
+ // entitlements for restore
+ $deleted_at = \App\Entitlement::withTrashed()
+ ->where('entitleable_id', $user->id)
+ ->where('entitleable_type', User::class)
+ ->max('deleted_at');
+
+ if ($deleted_at) {
+ $threshold = (new \Carbon\Carbon($deleted_at))->subMinute();
+
+ // We need at least the user domain so it can be created in ldap.
+ // FIXME: What if the domain is owned by someone else?
+ $domain = $user->domain();
+ if ($domain->trashed() && !$domain->isPublic()) {
+ // Note: Domain entitlements will be restored by the DomainObserver
+ $domain->restore();
+ }
+
+ // Restore user entitlements
+ \App\Entitlement::withTrashed()
+ ->where('entitleable_id', $user->id)
+ ->where('entitleable_type', User::class)
+ ->where('deleted_at', '>=', $threshold)
+ ->update(['updated_at' => now(), 'deleted_at' => null]);
+
+ // Note: We're assuming that cost of entitlements was correct
+ // on user deletion, so we don't have to re-calculate it again.
+ }
+
+ // FIXME: Should we reset user aliases? or re-validate them in any way?
+
+ // Create user record in LDAP, then run the verification process
+ $chain = [
+ new \App\Jobs\User\VerifyJob($user->id),
+ ];
+
+ \App\Jobs\User\CreateJob::withChain($chain)->dispatch($user->id);
+ }
+
/**
* Handle the "retrieving" event.
*
diff --git a/src/tests/Feature/Console/DomainRestoreTest.php b/src/tests/Feature/Console/DomainRestoreTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Console/DomainRestoreTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Tests\Feature\Console;
+
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class DomainRestoreTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('user@force-delete.com');
+ $this->deleteTestDomain('force-delete.com');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestUser('user@force-delete.com');
+ $this->deleteTestDomain('force-delete.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test the command
+ */
+ public function testHandle(): void
+ {
+ Queue::fake();
+
+ // Non-existing domain
+ $code = \Artisan::call("domain:restore unknown.org");
+ $output = trim(\Artisan::output());
+ $this->assertSame(1, $code);
+ $this->assertSame("Domain not found.", $output);
+
+ // Create a user account for delete
+ $user = $this->getTestUser('user@force-delete.com');
+ $domain = $this->getTestDomain('force-delete.com', [
+ 'status' => \App\Domain::STATUS_NEW,
+ 'type' => \App\Domain::TYPE_HOSTED,
+ ]);
+ $package_kolab = \App\Package::where('title', 'kolab')->first();
+ $package_domain = \App\Package::where('title', 'domain-hosting')->first();
+ $user->assignPackage($package_kolab);
+ $domain->assignPackage($package_domain, $user);
+ $wallet = $user->wallets()->first();
+ $entitlements = $wallet->entitlements->pluck('id')->all();
+
+ $this->assertCount(5, $entitlements);
+
+ // Non-deleted domain
+ $code = \Artisan::call("domain:restore force-delete.com");
+ $output = trim(\Artisan::output());
+ $this->assertSame(1, $code);
+ $this->assertSame("The domain is not yet deleted.", $output);
+
+ $domain->delete();
+
+ $this->assertTrue($domain->fresh()->trashed());
+
+ // Deleted domain
+ $code = \Artisan::call("domain:restore force-delete.com");
+ $output = trim(\Artisan::output());
+ $this->assertSame(0, $code);
+ $this->assertSame("", $output);
+
+ $this->assertFalse($domain->fresh()->trashed());
+
+ $user->delete();
+
+ $this->assertTrue($domain->fresh()->trashed());
+ $this->assertTrue($user->fresh()->trashed());
+
+ // Deleted domain, deleted owner
+ $code = \Artisan::call("domain:restore force-delete.com");
+ $output = trim(\Artisan::output());
+ $this->assertSame(1, $code);
+ $this->assertSame("The domain owner is deleted.", $output);
+
+ $this->assertTrue($domain->fresh()->trashed());
+ }
+}
diff --git a/src/tests/Feature/Console/UserRestoreTest.php b/src/tests/Feature/Console/UserRestoreTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Console/UserRestoreTest.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Tests\Feature\Console;
+
+use Illuminate\Support\Facades\Queue;
+use Tests\TestCase;
+
+class UserRestoreTest extends TestCase
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+
+ $this->deleteTestUser('user@force-delete.com');
+ $this->deleteTestDomain('force-delete.com');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tearDown(): void
+ {
+ $this->deleteTestUser('user@force-delete.com');
+ $this->deleteTestDomain('force-delete.com');
+
+ parent::tearDown();
+ }
+
+ /**
+ * Test the command
+ */
+ public function testHandle(): void
+ {
+ Queue::fake();
+
+ // Non-existing user
+ $code = \Artisan::call("user:restore unknown@unknown.org");
+ $output = trim(\Artisan::output());
+ $this->assertSame(1, $code);
+ $this->assertSame("User not found.", $output);
+
+ // Create a user account for delete
+ $user = $this->getTestUser('user@force-delete.com');
+ $domain = $this->getTestDomain('force-delete.com', [
+ 'status' => \App\Domain::STATUS_NEW,
+ 'type' => \App\Domain::TYPE_HOSTED,
+ ]);
+ $package_kolab = \App\Package::where('title', 'kolab')->first();
+ $package_domain = \App\Package::where('title', 'domain-hosting')->first();
+ $user->assignPackage($package_kolab);
+ $domain->assignPackage($package_domain, $user);
+ $wallet = $user->wallets()->first();
+ $entitlements = $wallet->entitlements->pluck('id')->all();
+
+ $this->assertCount(5, $entitlements);
+
+ // Non-deleted user
+ $code = \Artisan::call("user:restore {$user->email}");
+ $output = trim(\Artisan::output());
+ $this->assertSame(1, $code);
+ $this->assertSame("The user is not yet deleted.", $output);
+
+ $user->delete();
+
+ $this->assertTrue($user->trashed());
+ $this->assertTrue($domain->fresh()->trashed());
+
+ // Deleted user
+ $code = \Artisan::call("user:restore {$user->email}");
+ $output = trim(\Artisan::output());
+ $this->assertSame(0, $code);
+ $this->assertSame("", $output);
+
+ $this->assertFalse($user->fresh()->trashed());
+ $this->assertFalse($domain->fresh()->trashed());
+ }
+}
diff --git a/src/tests/Feature/DomainTest.php b/src/tests/Feature/DomainTest.php
--- a/src/tests/Feature/DomainTest.php
+++ b/src/tests/Feature/DomainTest.php
@@ -31,6 +31,8 @@
foreach ($this->domains as $domain) {
$this->deleteTestDomain($domain);
}
+
+ $this->deleteTestUser('user@gmail.com');
}
/**
@@ -42,6 +44,8 @@
$this->deleteTestDomain($domain);
}
+ $this->deleteTestUser('user@gmail.com');
+
parent::tearDown();
}
@@ -216,4 +220,81 @@
$this->assertCount(0, Domain::withTrashed()->where('id', $domain->id)->get());
}
+
+ /**
+ * Test domain restoring
+ */
+ public function testRestore(): void
+ {
+ Queue::fake();
+
+ $domain = $this->getTestDomain('gmail.com', [
+ 'status' => Domain::STATUS_NEW | Domain::STATUS_SUSPENDED
+ | Domain::STATUS_LDAP_READY | Domain::STATUS_CONFIRMED,
+ 'type' => Domain::TYPE_PUBLIC,
+ ]);
+
+ $user = $this->getTestUser('user@gmail.com');
+ $sku = \App\Sku::where('title', 'domain-hosting')->first();
+ $now = \Carbon\Carbon::now();
+
+ // Assign two entitlements to the domain, so we can assert that only the
+ // ones deleted last will be restored
+ $ent1 = \App\Entitlement::create([
+ 'wallet_id' => $user->wallets->first()->id,
+ 'sku_id' => $sku->id,
+ 'cost' => 0,
+ 'entitleable_id' => $domain->id,
+ 'entitleable_type' => Domain::class,
+ ]);
+ $ent2 = \App\Entitlement::create([
+ 'wallet_id' => $user->wallets->first()->id,
+ 'sku_id' => $sku->id,
+ 'cost' => 0,
+ 'entitleable_id' => $domain->id,
+ 'entitleable_type' => Domain::class,
+ ]);
+
+ $domain->delete();
+
+ $this->assertTrue($domain->fresh()->trashed());
+ $this->assertFalse($domain->fresh()->isDeleted());
+ $this->assertTrue($ent1->fresh()->trashed());
+ $this->assertTrue($ent2->fresh()->trashed());
+
+ // Backdate some properties
+ \App\Entitlement::withTrashed()->where('id', $ent2->id)->update(['deleted_at' => $now->subMinutes(2)]);
+ \App\Entitlement::withTrashed()->where('id', $ent1->id)->update(['updated_at' => $now->subMinutes(10)]);
+
+ Queue::fake();
+
+ $domain->restore();
+ $domain->refresh();
+
+ $this->assertFalse($domain->trashed());
+ $this->assertFalse($domain->isDeleted());
+ $this->assertFalse($domain->isSuspended());
+ $this->assertFalse($domain->isLdapReady());
+ $this->assertTrue($domain->isActive());
+ $this->assertTrue($domain->isConfirmed());
+
+ // Assert entitlements
+ $this->assertTrue($ent2->fresh()->trashed());
+ $this->assertFalse($ent1->fresh()->trashed());
+ $this->assertTrue($ent1->updated_at->greaterThan(\Carbon\Carbon::now()->subSeconds(5)));
+
+ // We expect only one CreateJob and one UpdateJob
+ // Because how Illuminate/Database/Eloquent/SoftDeletes::restore() method
+ // is implemented we cannot skip the UpdateJob in any way.
+ // I don't want to overwrite this method, the extra job shouldn't do any harm.
+ $this->assertCount(2, Queue::pushedJobs()); // @phpstan-ignore-line
+ Queue::assertPushed(\App\Jobs\Domain\UpdateJob::class, 1);
+ Queue::assertPushed(\App\Jobs\Domain\CreateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\Domain\CreateJob::class,
+ function ($job) use ($domain) {
+ return $domain->id === TestCase::getObjectProperty($job, 'domainId');
+ }
+ );
+ }
}
diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php
--- a/src/tests/Feature/UserTest.php
+++ b/src/tests/Feature/UserTest.php
@@ -20,6 +20,7 @@
$this->deleteTestUser('UserAccountC@UserAccount.com');
$this->deleteTestGroup('test-group@UserAccount.com');
$this->deleteTestDomain('UserAccount.com');
+ $this->deleteTestDomain('UserAccountAdd.com');
}
public function tearDown(): void
@@ -30,6 +31,7 @@
$this->deleteTestUser('UserAccountC@UserAccount.com');
$this->deleteTestGroup('test-group@UserAccount.com');
$this->deleteTestDomain('UserAccount.com');
+ $this->deleteTestDomain('UserAccountAdd.com');
parent::tearDown();
}
@@ -305,7 +307,7 @@
/**
* Test user deletion vs. group membership
*/
- public function testDeleteAandGroups(): void
+ public function testDeleteAndGroups(): void
{
Queue::fake();
@@ -410,6 +412,119 @@
$this->assertSame('First Last', $user->name(true));
}
+ /**
+ * Test user restoring
+ */
+ public function testRestore(): void
+ {
+ Queue::fake();
+
+ // Test an account with users and domain
+ $userA = $this->getTestUser('UserAccountA@UserAccount.com', [
+ 'status' => User::STATUS_LDAP_READY | User::STATUS_IMAP_READY | User::STATUS_SUSPENDED,
+ ]);
+ $userB = $this->getTestUser('UserAccountB@UserAccount.com');
+ $package_kolab = \App\Package::where('title', 'kolab')->first();
+ $package_domain = \App\Package::where('title', 'domain-hosting')->first();
+ $domainA = $this->getTestDomain('UserAccount.com', [
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_HOSTED,
+ ]);
+ $domainB = $this->getTestDomain('UserAccountAdd.com', [
+ 'status' => Domain::STATUS_NEW,
+ 'type' => Domain::TYPE_HOSTED,
+ ]);
+ $userA->assignPackage($package_kolab);
+ $domainA->assignPackage($package_domain, $userA);
+ $domainB->assignPackage($package_domain, $userA);
+ $userA->assignPackage($package_kolab, $userB);
+
+ $storage_sku = \App\Sku::where('title', 'storage')->first();
+ $now = \Carbon\Carbon::now();
+ $wallet_id = $userA->wallets->first()->id;
+
+ // add an extra storage entitlement
+ $ent1 = \App\Entitlement::create([
+ 'wallet_id' => $wallet_id,
+ 'sku_id' => $storage_sku->id,
+ 'cost' => 0,
+ 'entitleable_id' => $userA->id,
+ 'entitleable_type' => User::class,
+ ]);
+
+ $entitlementsA = \App\Entitlement::where('entitleable_id', $userA->id);
+ $entitlementsB = \App\Entitlement::where('entitleable_id', $userB->id);
+ $entitlementsDomain = \App\Entitlement::where('entitleable_id', $domainA->id);
+
+ // First delete the user
+ $userA->delete();
+
+ $this->assertSame(0, $entitlementsA->count());
+ $this->assertSame(0, $entitlementsB->count());
+ $this->assertSame(0, $entitlementsDomain->count());
+ $this->assertTrue($userA->fresh()->trashed());
+ $this->assertTrue($userB->fresh()->trashed());
+ $this->assertTrue($domainA->fresh()->trashed());
+ $this->assertTrue($domainB->fresh()->trashed());
+ $this->assertFalse($userA->isDeleted());
+ $this->assertFalse($userB->isDeleted());
+ $this->assertFalse($domainA->isDeleted());
+
+ // Backdate one storage entitlement (it's not expected to be restored)
+ \App\Entitlement::withTrashed()->where('id', $ent1->id)
+ ->update(['deleted_at' => $now->copy()->subMinutes(2)]);
+
+ // Backdate entitlements to assert that they were restored with proper updated_at timestamp
+ \App\Entitlement::withTrashed()->where('wallet_id', $wallet_id)
+ ->update(['updated_at' => $now->subMinutes(10)]);
+
+ Queue::fake();
+
+ // Then restore it
+ $userA->restore();
+ $userA->refresh();
+
+ $this->assertFalse($userA->trashed());
+ $this->assertFalse($userA->isDeleted());
+ $this->assertFalse($userA->isSuspended());
+ $this->assertFalse($userA->isLdapReady());
+ $this->assertFalse($userA->isImapReady());
+ $this->assertTrue($userA->isActive());
+
+ $this->assertTrue($userB->fresh()->trashed());
+ $this->assertTrue($domainB->fresh()->trashed());
+ $this->assertFalse($domainA->fresh()->trashed());
+
+ // Assert entitlements
+ $this->assertSame(4, $entitlementsA->count()); // mailbox + groupware + 2 x storage
+ $this->assertTrue($ent1->fresh()->trashed());
+ $entitlementsA->get()->each(function ($ent) {
+ $this->assertTrue($ent->updated_at->greaterThan(\Carbon\Carbon::now()->subSeconds(5)));
+ });
+
+ // We expect only CreateJob + UpdateJob pair for both user and domain.
+ // Because how Illuminate/Database/Eloquent/SoftDeletes::restore() method
+ // is implemented we cannot skip the UpdateJob in any way.
+ // I don't want to overwrite this method, the extra job shouldn't do any harm.
+ $this->assertCount(4, Queue::pushedJobs()); // @phpstan-ignore-line
+ Queue::assertPushed(\App\Jobs\Domain\UpdateJob::class, 1);
+ Queue::assertPushed(\App\Jobs\Domain\CreateJob::class, 1);
+ Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1);
+ Queue::assertPushed(\App\Jobs\User\CreateJob::class, 1);
+ Queue::assertPushed(
+ \App\Jobs\User\CreateJob::class,
+ function ($job) use ($userA) {
+ return $userA->id === TestCase::getObjectProperty($job, 'userId');
+ }
+ );
+ Queue::assertPushedWithChain(
+ \App\Jobs\User\CreateJob::class,
+ [
+ \App\Jobs\User\VerifyJob::class,
+ ]
+ );
+ }
+
/**
* Tests for UserAliasesTrait::setAliases()
*/
diff --git a/src/tests/TestCaseTrait.php b/src/tests/TestCaseTrait.php
--- a/src/tests/TestCaseTrait.php
+++ b/src/tests/TestCaseTrait.php
@@ -212,14 +212,12 @@
{
// Disable jobs (i.e. skip LDAP oprations)
Queue::fake();
- $user = User::withTrashed()->where('email', $email)->first();
-
- if (!$user) {
- return User::firstOrCreate(['email' => $email], $attrib);
- }
+ $user = User::firstOrCreate(['email' => $email], $attrib);
- if ($user->deleted_at) {
- $user->restore();
+ if ($user->trashed()) {
+ // Note: we do not want to use user restore here
+ User::where('id', $user->id)->forceDelete();
+ $user = User::create(['email' => $email] + $attrib);
}
return $user;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 3, 10:46 AM (16 h, 43 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18823698
Default Alt Text
D1924.1775213192.diff (25 KB)
Attached To
Mode
D1924: Restoring a user/domain
Attached
Detach File
Event Timeline