122 خطوط
4.6 KiB
PHP
122 خطوط
4.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Taxonomy;
|
|
|
|
use App\Models\User;
|
|
use App\Modules\Identity\Domain\Enums\UserRole;
|
|
use App\Modules\Organizations\Domain\Organization;
|
|
use App\Modules\Taxonomy\Domain\Enums\TaxonomyStatus;
|
|
use App\Modules\Taxonomy\Domain\TaxonomyNode;
|
|
use App\Modules\Taxonomy\Domain\TaxonomyType;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Laravel\Sanctum\Sanctum;
|
|
use Tests\TestCase;
|
|
|
|
class TaxonomyApiTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_designer_can_create_type_and_hierarchical_nodes(): void
|
|
{
|
|
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
|
|
Sanctum::actingAs($designer);
|
|
|
|
$typeId = $this->postJson('/api/v1/taxonomy-types', [
|
|
'key' => 'skill',
|
|
'name' => 'مهارت',
|
|
])->assertCreated()->json('data.id');
|
|
|
|
$parentId = $this->postJson('/api/v1/taxonomy-nodes', [
|
|
'taxonomyTypeId' => $typeId,
|
|
'name' => 'ارتباطات',
|
|
'code' => 'COMMUNICATION',
|
|
])->assertCreated()->json('data.id');
|
|
|
|
$this->postJson('/api/v1/taxonomy-nodes', [
|
|
'taxonomyTypeId' => $typeId,
|
|
'parentId' => $parentId,
|
|
'name' => 'بازخورد مؤثر',
|
|
'code' => 'GIVING_FEEDBACK',
|
|
])->assertCreated()->assertJsonPath('data.parentId', $parentId);
|
|
}
|
|
|
|
public function test_circular_hierarchy_is_rejected(): void
|
|
{
|
|
[$designer, $type] = $this->designerAndType();
|
|
$root = $this->node($designer->organization_id, $type->getKey(), 'Leadership');
|
|
$child = $this->node($designer->organization_id, $type->getKey(), 'Coaching', $root->getKey());
|
|
Sanctum::actingAs($designer);
|
|
|
|
$this->patchJson('/api/v1/taxonomy-nodes/'.$root->getKey(), ['parentId' => $child->getKey()])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors('parentId');
|
|
}
|
|
|
|
public function test_taxonomy_listing_is_tenant_isolated_even_with_client_organization_input(): void
|
|
{
|
|
[$designer, $type] = $this->designerAndType();
|
|
$ownNode = $this->node($designer->organization_id, $type->getKey(), 'Own skill');
|
|
$otherOrganization = Organization::factory()->create();
|
|
$otherType = TaxonomyType::query()->create([
|
|
'organization_id' => $otherOrganization->getKey(),
|
|
'key' => 'skill',
|
|
'name' => 'Skill',
|
|
'status' => TaxonomyStatus::Active,
|
|
]);
|
|
$otherNode = $this->node($otherOrganization->getKey(), $otherType->getKey(), 'Private skill');
|
|
Sanctum::actingAs($designer);
|
|
|
|
$response = $this->getJson('/api/v1/taxonomy-nodes?organization_id='.$otherOrganization->getKey())->assertOk();
|
|
$response->assertJsonFragment(['id' => $ownNode->getKey()]);
|
|
$response->assertJsonMissing(['id' => $otherNode->getKey()]);
|
|
}
|
|
|
|
public function test_manager_may_read_but_cannot_manage_taxonomy(): void
|
|
{
|
|
$manager = User::factory()->create(['role' => UserRole::Manager]);
|
|
Sanctum::actingAs($manager);
|
|
|
|
$this->getJson('/api/v1/taxonomy-types')->assertOk();
|
|
$this->postJson('/api/v1/taxonomy-types', ['key' => 'skill', 'name' => 'Skill'])->assertForbidden();
|
|
}
|
|
|
|
public function test_archived_node_remains_available_for_history(): void
|
|
{
|
|
[$designer, $type] = $this->designerAndType();
|
|
$node = $this->node($designer->organization_id, $type->getKey(), 'Historical skill');
|
|
Sanctum::actingAs($designer);
|
|
|
|
$this->patchJson('/api/v1/taxonomy-nodes/'.$node->getKey(), ['status' => 'archived'])
|
|
->assertOk()
|
|
->assertJsonPath('data.status', 'archived');
|
|
|
|
$this->getJson('/api/v1/taxonomy-nodes?status=archived')
|
|
->assertOk()
|
|
->assertJsonFragment(['id' => $node->getKey(), 'status' => 'archived']);
|
|
}
|
|
|
|
private function designerAndType(): array
|
|
{
|
|
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
|
|
$type = TaxonomyType::query()->create([
|
|
'organization_id' => $designer->organization_id,
|
|
'key' => 'skill',
|
|
'name' => 'Skill',
|
|
'status' => TaxonomyStatus::Active,
|
|
]);
|
|
|
|
return [$designer, $type];
|
|
}
|
|
|
|
private function node(string $organizationId, string $typeId, string $name, ?string $parentId = null): TaxonomyNode
|
|
{
|
|
return TaxonomyNode::query()->create([
|
|
'organization_id' => $organizationId,
|
|
'taxonomy_type_id' => $typeId,
|
|
'parent_id' => $parentId,
|
|
'name' => $name,
|
|
'status' => TaxonomyStatus::Active,
|
|
]);
|
|
}
|
|
}
|