New_Micro_Learning/backend/tests/Feature/Taxonomy/TaxonomyApiTest.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,
]);
}
}