mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-07 21:33:18 +01:00
- Rename the audit actor columns user_type/user_id to actor_type/actor_id - Add owner_organization_id and owner_user_id to the audits table as foreign keys with cascade on delete, so that the audits of an organization or user are deleted together with it. The indexes and foreign keys are created without blocking writes on the large table. - Fill the owner columns for new audits via CustomAuditable. Organizations and users no longer record their own deletion audit, since it would reference the already deleted owner.
498 lines
22 KiB
PHP
498 lines
22 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Service;
|
|
|
|
use App\Enums\Role;
|
|
use App\Events\BeforeOrganizationDeletion;
|
|
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
|
use App\Models\Audit;
|
|
use App\Models\Client;
|
|
use App\Models\Member;
|
|
use App\Models\Organization;
|
|
use App\Models\Passport\Client as PassportClient;
|
|
use App\Models\Passport\RefreshToken;
|
|
use App\Models\Passport\Token;
|
|
use App\Models\Project;
|
|
use App\Models\ProjectMember;
|
|
use App\Models\Report;
|
|
use App\Models\Tag;
|
|
use App\Models\Task;
|
|
use App\Models\TimeEntry;
|
|
use App\Models\User;
|
|
use App\Service\DeletionService;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
use Tests\TestCaseWithDatabase;
|
|
use TiMacDonald\Log\LogEntry;
|
|
|
|
#[CoversClass(DeletionService::class)]
|
|
class DeletionServiceTest extends TestCaseWithDatabase
|
|
{
|
|
private DeletionService $deletionService;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Event::fake([
|
|
BeforeOrganizationDeletion::class,
|
|
]);
|
|
$this->deletionService = app(DeletionService::class);
|
|
}
|
|
|
|
/**
|
|
* Creates an organization with all relations.
|
|
* It is important that every relation has at least two entries, to test for possible lazy loading issues.
|
|
*
|
|
* @return object{
|
|
* organization: Organization,
|
|
* clients: Collection<Client>,
|
|
* projects: Collection<Project>,
|
|
* projectMembers: Collection<ProjectMember>,
|
|
* tags: Collection<Tag>,
|
|
* members: Collection<Member>,
|
|
* tasks: Collection<Task>,
|
|
* timeEntries: Collection<TimeEntry>,
|
|
* owner: User,
|
|
* reports: Collection<Report>
|
|
* }
|
|
*/
|
|
private function createOrganizationWithAllRelations(): object
|
|
{
|
|
$userOwner = User::factory()->create();
|
|
$userEmployee = User::factory()->withProfilePicture()->create();
|
|
$userPlaceholder = User::factory()->placeholder()->create();
|
|
|
|
$organization = Organization::factory()->withOwner($userOwner)->create();
|
|
|
|
// Create a personal organization for the employee
|
|
$personalOrganizationOfEmployee = Organization::factory()->withOwner($userEmployee)->create();
|
|
$personalOrganizationMember = Member::factory()->forUser($userEmployee)->forOrganization($personalOrganizationOfEmployee)->create();
|
|
|
|
// Set the current organizations for the users
|
|
$userOwner->update(['current_team_id' => $organization->id]);
|
|
$userEmployee->update(['current_team_id' => $personalOrganizationOfEmployee->id]);
|
|
$userPlaceholder->update(['current_team_id' => null]);
|
|
|
|
$memberOwner = Member::factory()->forUser($userOwner)->forOrganization($organization)->role(Role::Owner)->create();
|
|
$memberEmployee = Member::factory()->forUser($userEmployee)->forOrganization($organization)->role(Role::Employee)->create();
|
|
$memberPlaceholder = Member::factory()->forUser($userPlaceholder)->forOrganization($organization)->role(Role::Placeholder)->create();
|
|
$members = collect([$memberOwner, $memberEmployee, $memberPlaceholder]);
|
|
|
|
$clients = Client::factory()->forOrganization($organization)->createMany(2);
|
|
|
|
$projectWithClient = Project::factory()->forClient($clients->get(0))->forOrganization($organization)->create();
|
|
$projectWithoutClient = Project::factory()->forOrganization($organization)->create();
|
|
$projects = collect([$projectWithClient, $projectWithoutClient]);
|
|
|
|
$projectMemberOwner = ProjectMember::factory()->forMember($memberOwner)->forProject($projectWithClient)->create();
|
|
$projectMemberEmployee = ProjectMember::factory()->forMember($memberEmployee)->forProject($projectWithClient)->create();
|
|
$projectMembers = collect([$projectMemberOwner, $projectMemberEmployee]);
|
|
|
|
$tags = Tag::factory()->forOrganization($organization)->createMany(2);
|
|
|
|
$task1 = Task::factory()->forProject($projectWithClient)->forOrganization($organization)->create();
|
|
$task2 = Task::factory()->forProject($projectWithoutClient)->forOrganization($organization)->create();
|
|
$tasks = collect([$task1, $task2]);
|
|
|
|
$report1 = Report::factory()->forOrganization($organization)->create();
|
|
$report2 = Report::factory()->forOrganization($organization)->create();
|
|
$reports = collect([$report1, $report2]);
|
|
|
|
$timeEntries = TimeEntry::factory()->forOrganization($organization)->forMember($memberOwner)->createMany(2);
|
|
$timeEntriesWithTask = TimeEntry::factory()->forTask($task1)->forOrganization($organization)->forMember($memberEmployee)->createMany(2);
|
|
$timeEntriesWithProject = TimeEntry::factory()->forProject($projectWithClient)->forOrganization($organization)->forMember($memberPlaceholder)->createMany(2);
|
|
$timeEntries = $timeEntries->merge($timeEntriesWithTask)->merge($timeEntriesWithProject);
|
|
|
|
return (object) [
|
|
'organization' => $organization,
|
|
'clients' => $clients,
|
|
'projects' => $projects,
|
|
'projectMembers' => $projectMembers,
|
|
'tags' => $tags,
|
|
'members' => $members,
|
|
'tasks' => $tasks,
|
|
'timeEntries' => $timeEntries,
|
|
'owner' => $userOwner,
|
|
'reports' => $reports,
|
|
];
|
|
}
|
|
|
|
private function assertOrganizationDeleted(Organization $organization): void
|
|
{
|
|
Event::assertDispatched(function (BeforeOrganizationDeletion $event) use ($organization) {
|
|
return $event->organization->is($organization);
|
|
}, 1);
|
|
$this->assertSame(0, Organization::query()->where('id', $organization->id)->count());
|
|
$this->assertSame(0, Client::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(0, Project::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(0, ProjectMember::query()->whereBelongsToOrganization($organization)->count());
|
|
$this->assertSame(0, Tag::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(0, Member::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(0, Task::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(0, Report::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(0, TimeEntry::query()->whereBelongsTo($organization, 'organization')->count());
|
|
}
|
|
|
|
private function assertOrganizationNothingDeleted(Organization $organization, bool $specialCase = false): void
|
|
{
|
|
$this->assertSame(1, Organization::query()->where('id', $organization->id)->count());
|
|
$this->assertSame(2, Client::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(2, Project::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(2, ProjectMember::query()->whereBelongsToOrganization($organization)->count());
|
|
$this->assertSame(2, Tag::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(3, Member::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(2, Task::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame(2, Report::query()->whereBelongsTo($organization, 'organization')->count());
|
|
$this->assertSame($specialCase ? 7 : 6, TimeEntry::query()->whereBelongsTo($organization, 'organization')->count());
|
|
}
|
|
|
|
public function test_delete_organization_resets_the_current_organization_of_users_that_had_the_deleted_organization_as_current_organization(): void
|
|
{
|
|
// Arrange
|
|
$userOwner = User::factory()->create();
|
|
$organization = Organization::factory()->withOwner($userOwner)->create();
|
|
$userOwner->currentOrganization()->associate($organization);
|
|
$userOwner->save();
|
|
|
|
// Act
|
|
$this->deletionService->deleteOrganization($organization);
|
|
|
|
// Assert
|
|
$this->assertOrganizationDeleted($organization);
|
|
$userOwner->refresh();
|
|
$this->assertNull($userOwner->current_team_id);
|
|
$this->assertNotSame($organization->id, $userOwner->current_team_id);
|
|
}
|
|
|
|
public function test_delete_organization_deletes_all_resources_of_the_organization_but_does_not_delete_other_resources(): void
|
|
{
|
|
// Arrange
|
|
$organization = $this->createOrganizationWithAllRelations();
|
|
$otherOrganization = $this->createOrganizationWithAllRelations();
|
|
|
|
// Act
|
|
$this->deletionService->deleteOrganization($organization->organization);
|
|
|
|
// Assert
|
|
$this->assertOrganizationDeleted($organization->organization);
|
|
$this->assertOrganizationNothingDeleted($otherOrganization->organization);
|
|
$this->assertSame(0, Audit::query()->where('owner_organization_id', $organization->organization->getKey())->count());
|
|
$this->assertGreaterThan(0, Audit::query()->where('owner_organization_id', $otherOrganization->organization->getKey())->count());
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Start deleting organization'
|
|
&& $log->context['organization_id'] === $organization->organization->getKey(),
|
|
1
|
|
);
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Finished deleting organization'
|
|
&& $log->context['organization_id'] === $organization->organization->getKey(),
|
|
1
|
|
);
|
|
}
|
|
|
|
public function test_delete_organization_rolls_back_on_error_if_transaction_is_active(): void
|
|
{
|
|
// Arrange
|
|
$organization = $this->createOrganizationWithAllRelations();
|
|
$otherOrganization = $this->createOrganizationWithAllRelations();
|
|
$brokenTimeEntry = TimeEntry::factory()->forOrganization($otherOrganization->organization)->forProject($organization->projects->get(0))->create();
|
|
|
|
// Act
|
|
try {
|
|
$this->deletionService->deleteOrganization($organization->organization);
|
|
$this->fail();
|
|
} catch (QueryException) {
|
|
$this->assertTrue(true);
|
|
}
|
|
|
|
// Assert
|
|
Event::assertNotDispatched(function (BeforeOrganizationDeletion $event) use ($otherOrganization): bool {
|
|
return $event->organization->is($otherOrganization->organization);
|
|
});
|
|
Event::assertDispatched(function (BeforeOrganizationDeletion $event) use ($organization): bool {
|
|
return $event->organization->is($organization->organization);
|
|
}, 1);
|
|
$this->assertOrganizationNothingDeleted($organization->organization);
|
|
$this->assertOrganizationNothingDeleted($otherOrganization->organization, true);
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Start deleting organization'
|
|
&& $log->context['organization_id'] === $organization->organization->getKey(),
|
|
1
|
|
);
|
|
Log::assertNotLogged(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Finished deleting organization'
|
|
&& $log->context['organization_id'] === $organization->organization->getKey()
|
|
);
|
|
}
|
|
|
|
public function test_delete_user_fails_if_user_is_owner_of_an_organization_with_multiple_members(): void
|
|
{
|
|
// Arrange
|
|
$organization = $this->createOrganizationWithAllRelations();
|
|
$memberOwner = $organization->owner;
|
|
|
|
// Act
|
|
try {
|
|
$this->deletionService->deleteUser($memberOwner);
|
|
$this->fail();
|
|
} catch (CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers $exception) {
|
|
// Assert
|
|
$this->assertTrue(true);
|
|
}
|
|
}
|
|
|
|
public function test_delete_user_rolls_back_on_error_if_transaction_is_active(): void
|
|
{
|
|
// Arrange
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
$memberOwner = Member::factory()->forUser($user)->forOrganization($organization)->role(Role::Owner)->create();
|
|
$otherOrganization = Organization::factory()->create();
|
|
|
|
$brokenTimeEntry = TimeEntry::factory()->forMember($memberOwner)->forOrganization($otherOrganization)->create();
|
|
|
|
// Act
|
|
try {
|
|
$this->deletionService->deleteUser($user);
|
|
$this->fail();
|
|
} catch (QueryException) {
|
|
$this->assertTrue(true);
|
|
}
|
|
|
|
// Assert
|
|
$this->assertDatabaseHas(User::class, [
|
|
'id' => $user->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(Organization::class, [
|
|
'id' => $organization->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(Member::class, [
|
|
'id' => $memberOwner->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(TimeEntry::class, [
|
|
'id' => $brokenTimeEntry->getKey(),
|
|
]);
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Start deleting user'
|
|
&& $log->context['id'] === $user->getKey(),
|
|
1
|
|
);
|
|
Log::assertNotLogged(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Finished deleting user'
|
|
&& $log->context['id'] === $user->getKey()
|
|
);
|
|
}
|
|
|
|
public function test_delete_user_deletes_all_resources_of_the_user_but_does_not_delete_other_resources(): void
|
|
{
|
|
// Arrange
|
|
$this->mockPublicStorage();
|
|
$user = User::factory()->withProfilePicture()->withPersonalOrganization()->create();
|
|
$otherUser = User::factory()->withProfilePicture()->withPersonalOrganization()->create();
|
|
Storage::disk(config('filesystems.public'))->assertExists($user->profile_photo_path);
|
|
Storage::disk(config('filesystems.public'))->assertExists($otherUser->profile_photo_path);
|
|
|
|
// Act
|
|
$this->deletionService->deleteUser($user);
|
|
|
|
// Assert
|
|
$this->assertDatabaseMissing(User::class, [
|
|
'id' => $user->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(User::class, [
|
|
'id' => $otherUser->getKey(),
|
|
]);
|
|
$this->assertDatabaseMissing(Organization::class, [
|
|
'id' => $user->current_team_id,
|
|
]);
|
|
$this->assertDatabaseHas(Organization::class, [
|
|
'id' => $otherUser->current_team_id,
|
|
]);
|
|
$this->assertDatabaseHas(Member::class, [
|
|
'user_id' => $otherUser->getKey(),
|
|
]);
|
|
$this->assertDatabaseMissing(Member::class, [
|
|
'user_id' => $user->getKey(),
|
|
]);
|
|
$this->assertSame(0, Audit::query()->where('owner_user_id', $user->getKey())->count());
|
|
$this->assertSame(0, Audit::query()->where('owner_organization_id', $user->current_team_id)->count());
|
|
$this->assertGreaterThan(0, Audit::query()->where('owner_user_id', $otherUser->getKey())->count());
|
|
$this->assertGreaterThan(0, Audit::query()->where('owner_organization_id', $otherUser->current_team_id)->count());
|
|
Storage::disk(config('filesystems.public'))->assertMissing($user->profile_photo_path);
|
|
Storage::disk(config('filesystems.public'))->assertExists($otherUser->profile_photo_path);
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Start deleting user'
|
|
&& $log->context['id'] === $user->getKey(),
|
|
1
|
|
);
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Finished deleting user'
|
|
&& $log->context['id'] === $user->getKey(),
|
|
1
|
|
);
|
|
}
|
|
|
|
public function test_delete_user_keeps_audits_of_other_organizations_where_the_user_is_the_actor(): void
|
|
{
|
|
// Arrange
|
|
$user = User::factory()->withPersonalOrganization()->create();
|
|
$otherOrganization = Organization::factory()->create();
|
|
$audit = Audit::factory()->auditUser($user)->auditFor($otherOrganization)->create([
|
|
'owner_organization_id' => $otherOrganization->getKey(),
|
|
]);
|
|
|
|
// Act
|
|
$this->deletionService->deleteUser($user);
|
|
|
|
// Assert
|
|
$audit->refresh();
|
|
$this->assertSame($user->getKey(), $audit->actor_id);
|
|
$this->assertSame($otherOrganization->getKey(), $audit->owner_organization_id);
|
|
}
|
|
|
|
public function test_delete_user_deletes_owned_organizations_that_have_only_one_member_and_makes_makes_the_user_placeholder_in_not_owned_organizations(): void
|
|
{
|
|
// Arrange
|
|
$user = User::factory()->create();
|
|
$otherUser = User::factory()->create();
|
|
$organizationOwned = Organization::factory()->withOwner($user)->create();
|
|
$organizationNotOwned = Organization::factory()->withOwner($otherUser)->create();
|
|
$memberOwned = Member::factory()->forUser($user)->forOrganization($organizationOwned)->role(Role::Owner)->create();
|
|
$memberNotOwned = Member::factory()->forUser($user)->forOrganization($organizationNotOwned)->role(Role::Employee)->create();
|
|
TimeEntry::factory()->forOrganization($organizationOwned)->forMember($memberOwned)->createMany(2);
|
|
TimeEntry::factory()->forOrganization($organizationNotOwned)->forMember($memberNotOwned)->createMany(2);
|
|
$this->assertDatabaseCount(User::class, 2);
|
|
|
|
// Act
|
|
$this->deletionService->deleteUser($user);
|
|
|
|
// Assert
|
|
$this->assertDatabaseCount(Organization::class, 1);
|
|
$this->assertDatabaseCount(User::class, 2);
|
|
$this->assertDatabaseMissing(User::class, [
|
|
'id' => $user->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(User::class, [
|
|
'id' => $otherUser->getKey(),
|
|
'is_placeholder' => false,
|
|
]);
|
|
$this->assertDatabaseHas(User::class, [
|
|
'is_placeholder' => true,
|
|
]);
|
|
$this->assertDatabaseMissing(Organization::class, [
|
|
'id' => $organizationOwned->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(Organization::class, [
|
|
'id' => $organizationNotOwned->getKey(),
|
|
]);
|
|
$this->assertDatabaseMissing(Member::class, [
|
|
'id' => $memberOwned->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(Member::class, [
|
|
'id' => $memberNotOwned->getKey(),
|
|
'organization_id' => $organizationNotOwned->getKey(),
|
|
'role' => Role::Placeholder->value,
|
|
]);
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Start deleting user'
|
|
&& $log->context['id'] === $user->getKey(),
|
|
1
|
|
);
|
|
Log::assertLoggedTimes(fn (LogEntry $log) => $log->level === 'debug'
|
|
&& $log->message === 'Finished deleting user'
|
|
&& $log->context['id'] === $user->getKey(),
|
|
1
|
|
);
|
|
}
|
|
|
|
public function test_delete_user_with_current_organization_set_to_owned_org_that_will_be_deleted_does_not_cause_foreign_key_violation(): void
|
|
{
|
|
// Arrange
|
|
// User A creates an organization and invites User B
|
|
$userA = User::factory()->create();
|
|
$userB = User::factory()->create();
|
|
$organizationOfA = Organization::factory()->withOwner($userA)->create();
|
|
$organizationOfB = Organization::factory()->withOwner($userB)->create();
|
|
Member::factory()->forUser($userA)->forOrganization($organizationOfA)->role(Role::Owner)->create();
|
|
Member::factory()->forUser($userB)->forOrganization($organizationOfB)->role(Role::Owner)->create();
|
|
$memberBInOrgA = Member::factory()->forUser($userB)->forOrganization($organizationOfA)->role(Role::Employee)->create();
|
|
TimeEntry::factory()->forOrganization($organizationOfA)->forMember($memberBInOrgA)->createMany(2);
|
|
|
|
// User B's current_organization_id points to their own org (the one that will be deleted)
|
|
$userB->update(['current_team_id' => $organizationOfB->getKey()]);
|
|
|
|
// Act
|
|
$this->deletionService->deleteUser($userB);
|
|
|
|
// Assert
|
|
$this->assertDatabaseMissing(User::class, [
|
|
'id' => $userB->getKey(),
|
|
]);
|
|
$this->assertDatabaseMissing(Organization::class, [
|
|
'id' => $organizationOfB->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(Organization::class, [
|
|
'id' => $organizationOfA->getKey(),
|
|
]);
|
|
// The placeholder user should exist and must not reference the deleted organization,
|
|
// which is what caused the foreign key violation in #989
|
|
$placeholderUser = User::query()->where('is_placeholder', true)->first();
|
|
$this->assertNotNull($placeholderUser);
|
|
$this->assertNull($placeholderUser->current_team_id);
|
|
$this->assertDatabaseHas(Member::class, [
|
|
'id' => $memberBInOrgA->getKey(),
|
|
'user_id' => $placeholderUser->getKey(),
|
|
'organization_id' => $organizationOfA->getKey(),
|
|
'role' => Role::Placeholder->value,
|
|
]);
|
|
}
|
|
|
|
public function test_delete_user_deletes_access_tokens_and_their_refresh_tokens_but_does_not_delete_tokens_of_other_users(): void
|
|
{
|
|
// Arrange
|
|
$user = User::factory()->create();
|
|
$otherUser = User::factory()->create();
|
|
$passportClient = PassportClient::factory()->create();
|
|
|
|
$userToken = Token::factory()->forUser($user)->forClient($passportClient)->create();
|
|
$userRefreshToken = RefreshToken::query()->create([
|
|
'id' => Str::random(100),
|
|
'access_token_id' => $userToken->getKey(),
|
|
'revoked' => false,
|
|
'expires_at' => now()->addDays(30),
|
|
]);
|
|
|
|
$otherUserToken = Token::factory()->forUser($otherUser)->forClient($passportClient)->create();
|
|
$otherUserRefreshToken = RefreshToken::query()->create([
|
|
'id' => Str::random(100),
|
|
'access_token_id' => $otherUserToken->getKey(),
|
|
'revoked' => false,
|
|
'expires_at' => now()->addDays(30),
|
|
]);
|
|
|
|
// Act
|
|
$this->deletionService->deleteUser($user);
|
|
|
|
// Assert
|
|
$this->assertDatabaseMissing(Token::class, [
|
|
'id' => $userToken->getKey(),
|
|
]);
|
|
$this->assertDatabaseMissing(RefreshToken::class, [
|
|
'id' => $userRefreshToken->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(Token::class, [
|
|
'id' => $otherUserToken->getKey(),
|
|
]);
|
|
$this->assertDatabaseHas(RefreshToken::class, [
|
|
'id' => $otherUserRefreshToken->getKey(),
|
|
]);
|
|
}
|
|
}
|