mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-12 18:22:16 +01:00
Added user and organization deletion system; Added coverage annotations
This commit is contained in:
committed by
Constantin Graf
parent
8857befc6c
commit
86f5ea47bb
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Console\Commands\Admin;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Service\DeletionService;
|
||||
use Illuminate\Support\Str;
|
||||
use Mockery\MockInterface;
|
||||
use Tests\TestCaseWithDatabase;
|
||||
|
||||
class DeleteOrganizationCommandTest extends TestCaseWithDatabase
|
||||
{
|
||||
public function test_it_calls_the_deletion_service_with_the_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$this->mock(DeletionService::class, function (MockInterface $mock) use ($organization): void {
|
||||
$mock->shouldReceive('deleteOrganization')
|
||||
->withArgs(fn (Organization $organizationArg) => $organizationArg->is($organization))
|
||||
->once();
|
||||
});
|
||||
|
||||
// Act
|
||||
$this->artisan('admin:delete-organization', ['organization' => $organization->getKey()])
|
||||
->expectsOutput("Deleting organization with ID {$organization->getKey()}")
|
||||
->expectsOutput("Organization with ID {$organization->getKey()} has been deleted.")
|
||||
->assertExitCode(0);
|
||||
}
|
||||
|
||||
public function test_it_fails_if_organization_does_not_exist(): void
|
||||
{
|
||||
// Arrange
|
||||
$organizationId = Str::uuid()->toString();
|
||||
|
||||
// Act
|
||||
$this->artisan('admin:delete-organization', ['organization' => $organizationId])
|
||||
->expectsOutput('Organization with ID '.$organizationId.' not found.')
|
||||
->assertExitCode(1);
|
||||
}
|
||||
|
||||
public function test_it_fails_if_organization_id_is_not_a_valid_uuid(): void
|
||||
{
|
||||
// Arrange
|
||||
$organizationId = 'invalid-uuid';
|
||||
|
||||
// Act
|
||||
$this->artisan('admin:delete-organization', ['organization' => $organizationId])
|
||||
->expectsOutput('Organization ID must be a valid UUID.')
|
||||
->assertExitCode(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Console\Commands\SelfHost;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SelfHostGenerateKeysCommandTest extends TestCase
|
||||
{
|
||||
public function test_generates_app_key_and_passport_keys_per_default_in_env_format(): void
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:generate-keys');
|
||||
|
||||
// Assert
|
||||
$this->assertSame(Command::SUCCESS, $exitCode);
|
||||
$output = Artisan::output();
|
||||
$this->assertStringContainsString('APP_KEY="base64:', $output);
|
||||
$this->assertStringContainsString('PASSPORT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----', $output);
|
||||
$this->assertStringContainsString('PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----', $output);
|
||||
}
|
||||
|
||||
public function test_generates_app_key_and_passport_keys_in_yaml_format_if_requested(): void
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:generate-keys --format=yaml');
|
||||
|
||||
// Assert
|
||||
$this->assertSame(Command::SUCCESS, $exitCode);
|
||||
$output = Artisan::output();
|
||||
$this->assertStringContainsString('APP_KEY: "base64:', $output);
|
||||
$this->assertStringContainsString("PASSPORT_PRIVATE_KEY: |\n -----BEGIN PRIVATE KEY-----", $output);
|
||||
$this->assertStringContainsString("PASSPORT_PUBLIC_KEY: |\n -----BEGIN PUBLIC KEY-----", $output);
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,10 @@ declare(strict_types=1);
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
use Tests\TestCaseWithDatabase;
|
||||
|
||||
abstract class FilamentTestCase extends TestCase
|
||||
abstract class FilamentTestCase extends TestCaseWithDatabase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
@@ -7,8 +7,10 @@ namespace Tests\Unit\Filament;
|
||||
use App\Filament\Resources\OrganizationResource;
|
||||
use App\Models\Organization;
|
||||
use App\Models\User;
|
||||
use App\Service\DeletionService;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
class OrganizationResourceTest extends FilamentTestCase
|
||||
{
|
||||
@@ -50,4 +52,23 @@ class OrganizationResourceTest extends FilamentTestCase
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
}
|
||||
|
||||
public function test_can_delete_a_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = $this->createUserWithPermission();
|
||||
$this->mock(DeletionService::class, function (MockInterface $mock) use ($user): void {
|
||||
$mock->shouldReceive('deleteOrganization')
|
||||
->withArgs(fn (Organization $organizationArg) => $organizationArg->is($user->organization))
|
||||
->once();
|
||||
});
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(OrganizationResource\Pages\EditOrganization::class, ['record' => $user->organization->getKey()])
|
||||
->callAction('delete')
|
||||
->assertHasNoActionErrors();
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Filament;
|
||||
|
||||
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
||||
use App\Filament\Resources\UserResource;
|
||||
use App\Models\User;
|
||||
use App\Service\DeletionService;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Livewire\Livewire;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
class UserResourceTest extends FilamentTestCase
|
||||
{
|
||||
@@ -46,4 +49,42 @@ class UserResourceTest extends FilamentTestCase
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
}
|
||||
|
||||
public function test_can_delete_a_user(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = $this->createUserWithPermission();
|
||||
$this->mock(DeletionService::class, function (MockInterface $mock) use ($user): void {
|
||||
$mock->shouldReceive('deleteUser')
|
||||
->withArgs(fn (User $userArg) => $userArg->is($user->user))
|
||||
->once();
|
||||
});
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(UserResource\Pages\EditUser::class, ['record' => $user->user->getKey()])
|
||||
->callAction('delete');
|
||||
|
||||
// Assert
|
||||
$response->assertHasNoActionErrors();
|
||||
$response->assertSuccessful();
|
||||
}
|
||||
|
||||
public function test_delete_user_shows_error_notification_on_failure(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = $this->createUserWithPermission();
|
||||
$this->mock(DeletionService::class, function (MockInterface $mock) use ($user): void {
|
||||
$mock->shouldReceive('deleteUser')
|
||||
->withArgs(fn (User $userArg) => $userArg->is($user->user))
|
||||
->andThrow(new CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers());
|
||||
});
|
||||
|
||||
// Act
|
||||
$response = Livewire::test(UserResource\Pages\EditUser::class, ['record' => $user->user->getKey()])
|
||||
->callAction('delete');
|
||||
|
||||
// Assert
|
||||
$response->assertNotified(__('exceptions.api.can_not_delete_user_who_is_owner_of_organization_with_multiple_members'));
|
||||
$response->assertSuccessful();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ namespace Tests\Unit\Model;
|
||||
use App\Models\Client;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(Client::class)]
|
||||
#[UsesClass(Client::class)]
|
||||
class ClientModelTest extends ModelTestAbstract
|
||||
{
|
||||
public function test_it_belongs_to_a_organization(): void
|
||||
|
||||
@@ -8,7 +8,11 @@ use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectMember;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(ProjectMember::class)]
|
||||
#[UsesClass(ProjectMember::class)]
|
||||
class ProjectMemberModelTest extends ModelTestAbstract
|
||||
{
|
||||
public function test_it_belongs_to_a_project(): void
|
||||
|
||||
@@ -10,7 +10,11 @@ use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectMember;
|
||||
use App\Models\Task;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(Project::class)]
|
||||
#[UsesClass(Project::class)]
|
||||
class ProjectModelTest extends ModelTestAbstract
|
||||
{
|
||||
public function test_it_belongs_to_a_organization(): void
|
||||
|
||||
@@ -6,7 +6,11 @@ namespace Tests\Unit\Model;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\Tag;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(Tag::class)]
|
||||
#[UsesClass(Tag::class)]
|
||||
class TagModelTest extends ModelTestAbstract
|
||||
{
|
||||
public function test_it_belongs_to_a_organization(): void
|
||||
|
||||
@@ -10,7 +10,11 @@ use App\Models\Project;
|
||||
use App\Models\ProjectMember;
|
||||
use App\Models\Task;
|
||||
use App\Models\TimeEntry;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(Task::class)]
|
||||
#[UsesClass(Task::class)]
|
||||
class TaskModelTest extends ModelTestAbstract
|
||||
{
|
||||
public function test_it_belongs_to_a_organization(): void
|
||||
|
||||
@@ -11,7 +11,11 @@ use App\Models\Task;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Carbon;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(TimeEntry::class)]
|
||||
#[UsesClass(TimeEntry::class)]
|
||||
class TimeEntryModelTest extends ModelTestAbstract
|
||||
{
|
||||
public function test_it_belongs_to_a_user(): void
|
||||
|
||||
@@ -6,8 +6,12 @@ namespace Tests\Unit\Rules;
|
||||
|
||||
use App\Rules\ColorRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(ColorRule::class)]
|
||||
#[UsesClass(ColorRule::class)]
|
||||
class ColorRuleTest extends TestCase
|
||||
{
|
||||
public function test_validation_passes_if_value_is_valid_color(): void
|
||||
|
||||
@@ -6,8 +6,12 @@ namespace Tests\Unit\Rules;
|
||||
|
||||
use App\Rules\CurrencyRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(CurrencyRule::class)]
|
||||
#[UsesClass(CurrencyRule::class)]
|
||||
class CurrencyRuleTest extends TestCase
|
||||
{
|
||||
public function test_validation_passes_if_value_is_valid_currency_code(): void
|
||||
|
||||
@@ -12,8 +12,12 @@ use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use App\Service\BillableRateService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(BillableRateService::class)]
|
||||
#[UsesClass(BillableRateService::class)]
|
||||
class BillableRateServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
@@ -15,8 +15,12 @@ use App\Models\User;
|
||||
use App\Service\DashboardService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(DashboardService::class)]
|
||||
#[UsesClass(DashboardService::class)]
|
||||
class DashboardServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
346
tests/Unit/Service/DeletionServiceTest.php
Normal file
346
tests/Unit/Service/DeletionServiceTest.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?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\Client;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectMember;
|
||||
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 PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCaseWithDatabase;
|
||||
use TiMacDonald\Log\LogEntry;
|
||||
|
||||
#[CoversClass(DeletionService::class)]
|
||||
#[UsesClass(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
|
||||
* }
|
||||
*/
|
||||
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]);
|
||||
|
||||
$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,
|
||||
];
|
||||
}
|
||||
|
||||
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, 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($specialCase ? 7 : 6, TimeEntry::query()->whereBelongsTo($organization, 'organization')->count());
|
||||
}
|
||||
|
||||
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);
|
||||
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()->forOrganization($otherOrganization)->forMember($memberOwner)->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
|
||||
$user = User::factory()->withProfilePicture()->withPersonalOrganization()->create();
|
||||
$otherUser = User::factory()->withProfilePicture()->withPersonalOrganization()->create();
|
||||
Storage::disk('public')->assertExists($user->profile_photo_path);
|
||||
Storage::disk('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(),
|
||||
]);
|
||||
Storage::disk('public')->assertMissing($user->profile_photo_path);
|
||||
Storage::disk('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_deletes_owned_organizations_that_have_only_one_member_and_makes_makes_the_user_placeholder_in_not_owned_organizations(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$organizationOwned = Organization::factory()->withOwner($user)->create();
|
||||
$organizationNotOwned = Organization::factory()->create();
|
||||
$memberOwned = Member::factory()->forUser($user)->forOrganization($organizationOwned)->role(Role::Owner)->create();
|
||||
$memberNotOwned = Member::factory()->forUser($user)->forOrganization($organizationNotOwned)->role(Role::Employee)->create();
|
||||
|
||||
// Act
|
||||
$this->deletionService->deleteUser($user);
|
||||
|
||||
// Assert
|
||||
$this->assertDatabaseMissing(User::class, [
|
||||
'id' => $user->getKey(),
|
||||
]);
|
||||
$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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,12 @@ use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use App\Service\Import\ImportDatabaseHelper;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(ImportDatabaseHelper::class)]
|
||||
#[UsesClass(ImportDatabaseHelper::class)]
|
||||
class ImportDatabaseHelperTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
@@ -5,11 +5,17 @@ declare(strict_types=1);
|
||||
namespace Tests\Unit\Service\Import;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Service\Import\Importers\ImporterProvider;
|
||||
use App\Service\Import\ImportService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(ImportService::class)]
|
||||
#[CoversClass(ImporterProvider::class)]
|
||||
#[UsesClass(ImportService::class)]
|
||||
class ImportServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
@@ -2,12 +2,20 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
namespace Tests\Unit\Service\Import\Importers;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Service\Import\Importers\ClockifyProjectsImporter;
|
||||
use App\Service\Import\Importers\DefaultImporter;
|
||||
use App\Service\Import\Importers\ImportException;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(ClockifyProjectsImporter::class)]
|
||||
#[CoversClass(ImportException::class)]
|
||||
#[CoversClass(DefaultImporter::class)]
|
||||
#[UsesClass(ClockifyProjectsImporter::class)]
|
||||
class ClockifyProjectsImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
public function test_import_of_test_file_succeeds(): void
|
||||
@@ -2,13 +2,21 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
namespace Tests\Unit\Service\Import\Importers;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Service\Import\Importers\ClockifyTimeEntriesImporter;
|
||||
use App\Service\Import\Importers\DefaultImporter;
|
||||
use App\Service\Import\Importers\ImportException;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(ClockifyTimeEntriesImporter::class)]
|
||||
#[CoversClass(ImportException::class)]
|
||||
#[CoversClass(DefaultImporter::class)]
|
||||
#[UsesClass(ClockifyTimeEntriesImporter::class)]
|
||||
class ClockifyTimeEntriesImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
public function test_import_of_test_file_succeeds(): void
|
||||
46
tests/Unit/Service/Import/Importers/ImporterProviderTest.php
Normal file
46
tests/Unit/Service/Import/Importers/ImporterProviderTest.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importers;
|
||||
|
||||
use App\Service\Import\Importers\ClockifyProjectsImporter;
|
||||
use App\Service\Import\Importers\ImporterProvider;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(ImporterProvider::class)]
|
||||
#[UsesClass(ImporterProvider::class)]
|
||||
class ImporterProviderTest extends TestCase
|
||||
{
|
||||
public function test_register_importer_can_register_a_new_importer_for_example_in_an_extension(): void
|
||||
{
|
||||
// Arrange
|
||||
$provider = new ImporterProvider();
|
||||
|
||||
// Act
|
||||
$provider->registerImporter('some_provider_importer', ClockifyProjectsImporter::class);
|
||||
|
||||
// Assert
|
||||
$importer = $provider->getImporter('some_provider_importer');
|
||||
$this->assertSame(ClockifyProjectsImporter::class, $importer::class);
|
||||
}
|
||||
|
||||
public function test_get_importer_keys_return_the_keys_of_the_available_importers(): void
|
||||
{
|
||||
// Arrange
|
||||
$provider = new ImporterProvider();
|
||||
|
||||
// Act
|
||||
$keys = $provider->getImporterKeys();
|
||||
|
||||
// Assert
|
||||
$this->assertSame([
|
||||
'toggl_time_entries',
|
||||
'toggl_data_importer',
|
||||
'clockify_time_entries',
|
||||
'clockify_projects',
|
||||
], $keys);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
namespace Tests\Unit\Service\Import\Importers;
|
||||
|
||||
use App\Enums\Role;
|
||||
use App\Models\Client;
|
||||
@@ -2,17 +2,24 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
namespace Tests\Unit\Service\Import\Importers;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Service\Import\Importers\DefaultImporter;
|
||||
use App\Service\Import\Importers\ImportException;
|
||||
use App\Service\Import\Importers\TogglDataImporter;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Spatie\TemporaryDirectory\TemporaryDirectory;
|
||||
use ZipArchive;
|
||||
|
||||
#[CoversClass(TogglDataImporter::class)]
|
||||
#[CoversClass(ImportException::class)]
|
||||
#[CoversClass(DefaultImporter::class)]
|
||||
#[UsesClass(TogglDataImporter::class)]
|
||||
class TogglDataImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
private function createTestZip(string $folder): string
|
||||
@@ -2,13 +2,21 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Service\Import\Importer;
|
||||
namespace Tests\Unit\Service\Import\Importers;
|
||||
|
||||
use App\Models\Organization;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Service\Import\Importers\DefaultImporter;
|
||||
use App\Service\Import\Importers\ImportException;
|
||||
use App\Service\Import\Importers\TogglTimeEntriesImporter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(TogglTimeEntriesImporter::class)]
|
||||
#[CoversClass(ImportException::class)]
|
||||
#[CoversClass(DefaultImporter::class)]
|
||||
#[UsesClass(TogglTimeEntriesImporter::class)]
|
||||
class TogglTimeEntriesImporterTest extends ImporterTestAbstract
|
||||
{
|
||||
public function test_import_of_test_file_succeeds(): void
|
||||
@@ -10,8 +10,12 @@ use App\Models\User;
|
||||
use App\Service\PermissionStore;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Jetstream\Jetstream;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(PermissionStore::class)]
|
||||
#[UsesClass(PermissionStore::class)]
|
||||
class PermissionStoreTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
@@ -11,8 +11,12 @@ use App\Models\Project;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Service\TimeEntryAggregationService;
|
||||
use Illuminate\Support\Carbon;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCaseWithDatabase;
|
||||
|
||||
#[CoversClass(TimeEntryAggregationService::class)]
|
||||
#[UsesClass(TimeEntryAggregationService::class)]
|
||||
class TimeEntryAggregationServiceTest extends TestCaseWithDatabase
|
||||
{
|
||||
private TimeEntryAggregationService $service;
|
||||
|
||||
@@ -8,9 +8,13 @@ use App\Models\User;
|
||||
use App\Service\TimezoneService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
use TiMacDonald\Log\LogEntry;
|
||||
|
||||
#[CoversClass(TimezoneService::class)]
|
||||
#[UsesClass(TimezoneService::class)]
|
||||
class TimezoneServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
@@ -13,12 +13,24 @@ use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use App\Service\UserService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
use Tests\TestCase;
|
||||
|
||||
#[CoversClass(UserService::class)]
|
||||
#[UsesClass(UserService::class)]
|
||||
class UserServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private UserService $userService;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->userService = app(UserService::class);
|
||||
}
|
||||
|
||||
public function test_assign_organization_entities_to_different_user(): void
|
||||
{
|
||||
// Arrange
|
||||
@@ -36,9 +48,7 @@ class UserServiceTest extends TestCase
|
||||
ProjectMember::factory()->forProject($project)->forMember($fromUserMember)->create();
|
||||
|
||||
// Act
|
||||
/** @var UserService $userService */
|
||||
$userService = app(UserService::class);
|
||||
$userService->assignOrganizationEntitiesToDifferentUser($organization, $fromUser, $toUser);
|
||||
$this->userService->assignOrganizationEntitiesToDifferentUser($organization, $fromUser, $toUser);
|
||||
|
||||
// Assert
|
||||
$this->assertSame(3, TimeEntry::query()->whereBelongsTo($toUser, 'user')->count());
|
||||
@@ -49,6 +59,22 @@ class UserServiceTest extends TestCase
|
||||
$this->assertSame(0, ProjectMember::query()->whereBelongsTo($fromUser, 'user')->count());
|
||||
}
|
||||
|
||||
public function test_assign_organization_entities_to_different_user_fails_if_new_user_is_not_member_of_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$fromUser = User::factory()->create();
|
||||
$toUser = User::factory()->create();
|
||||
$fromUserMember = Member::factory()->forOrganization($organization)->forUser($fromUser)->create();
|
||||
|
||||
// Act
|
||||
try {
|
||||
$this->userService->assignOrganizationEntitiesToDifferentUser($organization, $fromUser, $toUser);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->assertSame('User is not a member of the organization', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function test_change_ownership_changes_ownership_of_organization_to_new_user(): void
|
||||
{
|
||||
// Arrange
|
||||
@@ -63,13 +89,116 @@ class UserServiceTest extends TestCase
|
||||
]);
|
||||
|
||||
// Act
|
||||
/** @var UserService $userService */
|
||||
$userService = app(UserService::class);
|
||||
$userService->changeOwnership($organization, $newOwner);
|
||||
$this->userService->changeOwnership($organization, $newOwner);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($newOwner->getKey(), $organization->refresh()->user_id);
|
||||
$this->assertSame(Role::Owner->value, Member::whereBelongsTo($newOwner)->whereBelongsTo($organization)->firstOrFail()->role);
|
||||
$this->assertSame(Role::Admin->value, Member::whereBelongsTo($oldOwner)->whereBelongsTo($organization)->firstOrFail()->role);
|
||||
}
|
||||
|
||||
public function test_change_ownership_fails_if_new_user_is_not_member_of_organization(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$newOwner = User::factory()->create();
|
||||
$oldOwner = User::factory()->create();
|
||||
$organization->users()->attach($oldOwner->getKey(), [
|
||||
'role' => Role::Owner->value,
|
||||
]);
|
||||
|
||||
// Act
|
||||
try {
|
||||
$this->userService->changeOwnership($organization, $newOwner);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->assertSame('User is not a member of the organization', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function test_make_member_to_placeholder_creates_new_user_based_on_member_and_changes_member_to_placeholder(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$organization = Organization::factory()->create();
|
||||
$member = Member::factory()->forOrganization($organization)->forUser($user)->role(Role::Employee)->create();
|
||||
$timeEntry = TimeEntry::factory()->forOrganization($organization)->forMember($member)->create();
|
||||
$project = Project::factory()->forOrganization($organization)->create();
|
||||
$projectMember = ProjectMember::factory()->forProject($project)->forMember($member)->create();
|
||||
// Note: create other user, organization, member, time entry and project member to check that they are not changed
|
||||
$otherUser = User::factory()->create();
|
||||
$otherOrganization = Organization::factory()->create();
|
||||
$otherMember = Member::factory()->forOrganization($otherOrganization)->forUser($otherUser)->role(Role::Employee)->create();
|
||||
$otherTimeEntry = TimeEntry::factory()->forOrganization($otherOrganization)->forMember($otherMember)->create();
|
||||
$otherProject = Project::factory()->forOrganization($otherOrganization)->create();
|
||||
$otherProjectMember = ProjectMember::factory()->forProject($otherProject)->forMember($otherMember)->create();
|
||||
|
||||
// Act
|
||||
$this->userService->makeMemberToPlaceholder($member);
|
||||
|
||||
// Assert
|
||||
$member->refresh();
|
||||
$timeEntry->refresh();
|
||||
$projectMember->refresh();
|
||||
$placeholderUser = $member->user;
|
||||
$this->assertTrue($placeholderUser->is_placeholder);
|
||||
$this->assertSame(Role::Placeholder->value, $member->role);
|
||||
$this->assertSame($organization->getKey(), $member->organization_id);
|
||||
$this->assertSame($placeholderUser->getKey(), $projectMember->user_id);
|
||||
$this->assertSame($member->getKey(), $projectMember->member_id);
|
||||
$this->assertSame($placeholderUser->getKey(), $timeEntry->user_id);
|
||||
$this->assertSame($member->getKey(), $timeEntry->member_id);
|
||||
$this->assertSame(1, $user->organizations()->count());
|
||||
// Note: check that other user did not change
|
||||
$otherMember->refresh();
|
||||
$otherTimeEntry->refresh();
|
||||
$otherProjectMember->refresh();
|
||||
$otherUser->refresh();
|
||||
$this->assertFalse($otherUser->is_placeholder);
|
||||
$this->assertSame(Role::Employee->value, $otherMember->role);
|
||||
$this->assertSame($otherOrganization->getKey(), $otherMember->organization_id);
|
||||
$this->assertSame($otherUser->getKey(), $otherProjectMember->user_id);
|
||||
$this->assertSame($otherMember->getKey(), $otherProjectMember->member_id);
|
||||
$this->assertSame($otherUser->getKey(), $otherTimeEntry->user_id);
|
||||
$this->assertSame($otherMember->getKey(), $otherTimeEntry->member_id);
|
||||
$this->assertSame(1, $otherUser->organizations()->count());
|
||||
}
|
||||
|
||||
public function test_make_sure_user_has_current_organization_sets_current_organization_for_user_if_null(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$organization = Organization::factory()->create();
|
||||
$otherOrganization = Organization::factory()->create();
|
||||
Member::factory()->forUser($user)->forOrganization($organization)->create();
|
||||
$user->current_team_id = null;
|
||||
$user->save();
|
||||
|
||||
// Act
|
||||
$this->userService->makeSureUserHasCurrentOrganization($user);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($organization->getKey(), $user->refresh()->currentOrganization->getKey());
|
||||
}
|
||||
|
||||
public function make_sure_user_has_at_least_one_organization_creates_organization_for_user_if_there_are_not_member_of_one(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$organization = Organization::factory()->create();
|
||||
|
||||
// Act
|
||||
$this->userService->makeSureUserHasAtLeastOneOrganization($user);
|
||||
|
||||
// Assert
|
||||
$user->refresh();
|
||||
$this->assertSame(1, $user->organizations()->count());
|
||||
$newOrganization = $user->organizations()->first();
|
||||
$this->assertNotSame($organization->getKey(), $newOrganization->getKey());
|
||||
$this->assertSame($user->name."'s Organization", $newOrganization->name);
|
||||
$this->assertTrue($newOrganization->personal_team);
|
||||
$this->assertSame($user->getKey(), $newOrganization->user_id);
|
||||
$newMember = Member::whereBelongsTo($user)->whereBelongsTo($newOrganization)->firstOrFail();
|
||||
$this->assertSame(Role::Owner->value, $newMember->role);
|
||||
$this->assertSame($newOrganization->getKey(), $user->currentOrganization->getKey());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user