Page MenuHomePhorge

D1924.1775213192.diff
No OneTemporary

Authored By
Unknown
Size
25 KB
Referenced Files
None
Subscribers
None

D1924.1775213192.diff

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

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)

Event Timeline