mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-08 13:53:17 +01:00
Models that belong to an organization via a parent model (for example project members via their project) implement AuditableThroughParent and return the parent relation. The owner organization of their audits is taken from the loaded parent if it matches the foreign key, otherwise it is queried. The declaration is also used to backfill the owner of existing audits.
210 lines
8.0 KiB
PHP
210 lines
8.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Model;
|
|
|
|
use App\Models\Audit;
|
|
use App\Models\Concerns\AuditableWithoutOwner;
|
|
use App\Models\Member;
|
|
use App\Models\Organization;
|
|
use App\Models\Project;
|
|
use App\Models\ProjectMember;
|
|
use App\Models\TimeEntry;
|
|
use App\Models\User;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
|
use Illuminate\Support\Facades\DB;
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
|
|
#[CoversClass(Audit::class)]
|
|
class AuditModelTest extends ModelTestAbstract
|
|
{
|
|
public function test_it_belongs_to_an_owner_organization(): void
|
|
{
|
|
// Arrange
|
|
$organization = Organization::factory()->create();
|
|
$audit = Audit::factory()->create([
|
|
'owner_organization_id' => $organization->getKey(),
|
|
]);
|
|
|
|
// Act
|
|
$audit->refresh();
|
|
$ownerOrganizationRel = $audit->ownerOrganization;
|
|
|
|
// Assert
|
|
$this->assertNotNull($ownerOrganizationRel);
|
|
$this->assertTrue($ownerOrganizationRel->is($organization));
|
|
}
|
|
|
|
public function test_it_belongs_to_an_owner_user(): void
|
|
{
|
|
// Arrange
|
|
$user = User::factory()->create();
|
|
$audit = Audit::factory()->create([
|
|
'owner_user_id' => $user->getKey(),
|
|
]);
|
|
|
|
// Act
|
|
$audit->refresh();
|
|
$ownerUserRel = $audit->ownerUser;
|
|
|
|
// Assert
|
|
$this->assertNotNull($ownerUserRel);
|
|
$this->assertTrue($ownerUserRel->is($user));
|
|
}
|
|
|
|
public function test_audits_of_models_with_organization_have_the_organization_as_owner(): void
|
|
{
|
|
// Arrange
|
|
$organization = Organization::factory()->create();
|
|
$member = Member::factory()->forOrganization($organization)->create();
|
|
|
|
// Act
|
|
$timeEntry = TimeEntry::factory()->forOrganization($organization)->forMember($member)->create();
|
|
|
|
// Assert
|
|
$audit = Audit::query()->where('auditable_id', $timeEntry->getKey())->sole();
|
|
$this->assertSame($organization->getKey(), $audit->owner_organization_id);
|
|
$this->assertNull($audit->owner_user_id);
|
|
}
|
|
|
|
public function test_audits_of_project_members_have_the_organization_of_the_project_as_owner(): void
|
|
{
|
|
// Arrange
|
|
$organization = Organization::factory()->create();
|
|
$project = Project::factory()->forOrganization($organization)->create();
|
|
$member = Member::factory()->forOrganization($organization)->create();
|
|
|
|
// Act
|
|
$projectMember = ProjectMember::factory()->forProject($project)->forMember($member)->create();
|
|
|
|
// Assert
|
|
$audit = Audit::query()->where('auditable_id', $projectMember->getKey())->sole();
|
|
$this->assertSame($organization->getKey(), $audit->owner_organization_id);
|
|
$this->assertNull($audit->owner_user_id);
|
|
}
|
|
|
|
public function test_owner_organization_through_parent_uses_the_loaded_parent_without_query(): void
|
|
{
|
|
// Arrange
|
|
$organization = Organization::factory()->create();
|
|
$project = Project::factory()->forOrganization($organization)->create();
|
|
$projectMember = ProjectMember::factory()->forProject($project)->forMember(Member::factory()->forOrganization($organization)->create())->create();
|
|
$projectMember->load('project');
|
|
DB::enableQueryLog();
|
|
|
|
// Act
|
|
$ownerOrganizationId = $projectMember->getAuditOwnerOrganizationId();
|
|
|
|
// Assert
|
|
$this->assertSame($organization->getKey(), $ownerOrganizationId);
|
|
$this->assertCount(0, DB::getQueryLog());
|
|
}
|
|
|
|
public function test_owner_organization_through_parent_ignores_a_loaded_parent_that_does_not_match_the_foreign_key(): void
|
|
{
|
|
// Arrange
|
|
$organization = Organization::factory()->create();
|
|
$otherOrganization = Organization::factory()->create();
|
|
$project = Project::factory()->forOrganization($organization)->create();
|
|
$otherProject = Project::factory()->forOrganization($otherOrganization)->create();
|
|
$projectMember = ProjectMember::factory()->forProject($project)->forMember(Member::factory()->forOrganization($organization)->create())->create();
|
|
$projectMember->load('project');
|
|
$projectMember->project_id = $otherProject->getKey();
|
|
|
|
// Act
|
|
$ownerOrganizationId = $projectMember->getAuditOwnerOrganizationId();
|
|
|
|
// Assert
|
|
$this->assertSame($otherOrganization->getKey(), $ownerOrganizationId);
|
|
}
|
|
|
|
public function test_audits_of_an_organization_have_the_organization_itself_as_owner(): void
|
|
{
|
|
// Act
|
|
$organization = Organization::factory()->create();
|
|
|
|
// Assert
|
|
$audit = Audit::query()->where('auditable_id', $organization->getKey())->sole();
|
|
$this->assertSame($organization->getKey(), $audit->owner_organization_id);
|
|
$this->assertNull($audit->owner_user_id);
|
|
}
|
|
|
|
public function test_audits_of_a_user_have_the_user_itself_as_owner(): void
|
|
{
|
|
// Act
|
|
$user = User::factory()->create();
|
|
|
|
// Assert
|
|
$audit = Audit::query()->where('auditable_id', $user->getKey())->sole();
|
|
$this->assertSame($user->getKey(), $audit->owner_user_id);
|
|
$this->assertNull($audit->owner_organization_id);
|
|
}
|
|
|
|
public function test_deleting_an_owner_deletes_its_audits_and_does_not_create_a_deletion_audit(): void
|
|
{
|
|
// Arrange
|
|
$user = User::factory()->create();
|
|
$organization = Organization::factory()->create();
|
|
$otherUser = User::factory()->create();
|
|
|
|
// Act
|
|
$user->delete();
|
|
$organization->delete();
|
|
|
|
// Assert
|
|
$this->assertSame(0, Audit::query()->where('auditable_id', $user->getKey())->count());
|
|
$this->assertSame(0, Audit::query()->where('auditable_id', $organization->getKey())->count());
|
|
$this->assertSame(1, Audit::query()->where('auditable_id', $otherUser->getKey())->count());
|
|
}
|
|
|
|
public function test_auditable_types_without_owner_are_determined_by_the_marker_interface(): void
|
|
{
|
|
// Arrange
|
|
$originalMorphMap = Relation::morphMap();
|
|
$modelWithoutOwner = new class extends Model implements AuditableWithoutOwner {};
|
|
Relation::morphMap(['model-without-owner' => $modelWithoutOwner::class]);
|
|
|
|
try {
|
|
// Act
|
|
$typesWithoutOwner = Audit::getAuditableTypesWithoutOwner();
|
|
$isWithoutOwner = Audit::isAuditableTypeWithoutOwner('model-without-owner');
|
|
$isTimeEntryWithoutOwner = Audit::isAuditableTypeWithoutOwner((new TimeEntry)->getMorphClass());
|
|
|
|
// Assert
|
|
$this->assertContains('model-without-owner', $typesWithoutOwner);
|
|
$this->assertNotContains((new TimeEntry)->getMorphClass(), $typesWithoutOwner);
|
|
$this->assertTrue($isWithoutOwner);
|
|
$this->assertFalse($isTimeEntryWithoutOwner);
|
|
} finally {
|
|
Relation::morphMap($originalMorphMap, false);
|
|
}
|
|
}
|
|
|
|
public function test_scope_where_missing_owner_only_returns_audits_without_owner_whose_type_should_have_one(): void
|
|
{
|
|
// Arrange
|
|
$originalMorphMap = Relation::morphMap();
|
|
$modelWithoutOwner = new class extends Model implements AuditableWithoutOwner {};
|
|
Relation::morphMap(['model-without-owner' => $modelWithoutOwner::class]);
|
|
$organization = Organization::factory()->create();
|
|
$user = User::factory()->create();
|
|
Audit::query()->delete();
|
|
$missingOwnerAudit = Audit::factory()->create(['auditable_type' => (new TimeEntry)->getMorphClass()]);
|
|
Audit::factory()->create(['auditable_type' => (new TimeEntry)->getMorphClass(), 'owner_organization_id' => $organization->getKey()]);
|
|
Audit::factory()->create(['owner_user_id' => $user->getKey()]);
|
|
Audit::factory()->create(['auditable_type' => 'model-without-owner']);
|
|
|
|
try {
|
|
// Act
|
|
$auditIds = Audit::query()->whereMissingOwner()->pluck('id')->all();
|
|
|
|
// Assert
|
|
$this->assertSame([$missingOwnerAudit->getKey()], $auditIds);
|
|
} finally {
|
|
Relation::morphMap($originalMorphMap, false);
|
|
}
|
|
}
|
|
}
|