New_Micro_Learning/backend/tests/Feature/Assets/AssetApiTest.php

110 خطوط
6.6 KiB
PHP

<?php
namespace Tests\Feature\Assets;
use App\Models\User;
use App\Modules\Assets\Domain\Asset;
use App\Modules\Courses\Domain\Block;
use App\Modules\Courses\Domain\Course;
use App\Modules\Courses\Domain\CourseModule;
use App\Modules\Courses\Domain\CourseVersion;
use App\Modules\Courses\Domain\Enums\CourseVersionStatus;
use App\Modules\Courses\Domain\Lesson;
use App\Modules\Identity\Domain\Enums\UserRole;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class AssetApiTest extends TestCase
{
use RefreshDatabase;
public function test_large_video_above_previous_25_mb_limit_is_accepted(): void
{
Storage::fake('local');
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
Sanctum::actingAs($designer);
$this->post('/api/v1/assets', ['file' => UploadedFile::fake()->create('training.mp4', 128 * 1024, 'video/mp4')], ['Accept' => 'application/json'])
->assertCreated()
->assertJsonPath('data.kind', 'video')
->assertJsonPath('data.size', 128 * 1024 * 1024);
}
public function test_designer_uploads_private_asset_lists_it_and_reads_signed_content(): void
{
Storage::fake('local');
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
Sanctum::actingAs($designer);
$response = $this->post('/api/v1/assets', ['file' => UploadedFile::fake()->image('safety.png', 800, 600), 'altText' => 'Safety worker'], ['Accept' => 'application/json'])
->assertCreated()->assertJsonPath('data.kind', 'image')->assertJsonPath('data.usageCount', 0);
$assetId = $response->json('data.id');
$this->assertStringStartsWith('/api/v1/assets/', $response->json('data.contentUrl'));
$this->assertDatabaseHas('assets', ['id' => $assetId, 'organization_id' => $designer->organization_id, 'original_name' => 'safety.png']);
$this->getJson('/api/v1/assets?kind=image&search=safety')->assertOk()->assertJsonPath('data.0.id', $assetId);
$this->getJson("/api/v1/assets/{$assetId}")
->assertOk()
->assertJsonPath('data.id', $assetId)
->assertJsonPath('data.name', 'safety.png');
$this->get($response->json('data.contentUrl'))->assertOk()->assertHeader('content-type', 'image/png');
}
public function test_duplicate_upload_is_deduplicated_and_used_draft_asset_can_be_detached_and_deleted(): void
{
Storage::fake('local');
[$designer, $course, $version, $lesson] = $this->foundation();
Sanctum::actingAs($designer);
$file = UploadedFile::fake()->createWithContent('manual.pdf', '%PDF-1.4 same content');
$assetId = $this->post('/api/v1/assets', ['file' => $file], ['Accept' => 'application/json'])->assertCreated()->json('data.id');
$this->post('/api/v1/assets', ['file' => UploadedFile::fake()->createWithContent('copy.pdf', '%PDF-1.4 same content')], ['Accept' => 'application/json'])->assertOk()->assertJsonPath('data.id', $assetId);
$this->assertDatabaseCount('assets', 1);
$blockId = $this->postJson("/api/v1/courses/{$course->getKey()}/versions/{$version->getKey()}/lessons/{$lesson->getKey()}/blocks", [
'type' => 'document', 'schemaVersion' => 1, 'data' => ['assetId' => $assetId, 'title' => 'Manual', 'description' => ''],
])->assertCreated()->json('data.id');
$this->getJson('/api/v1/assets')->assertOk()->assertJsonPath('data.0.usageCount', 1);
$this->deleteJson("/api/v1/assets/{$assetId}")->assertUnprocessable()->assertJsonValidationErrors('asset');
$this->deleteJson("/api/v1/assets/{$assetId}?detach=1")->assertNoContent();
$this->assertDatabaseMissing('assets', ['id' => $assetId]);
$this->assertEquals(
['title' => 'Manual', 'assetId' => null, 'description' => null],
Block::query()->findOrFail($blockId)->data,
);
}
public function test_asset_validation_authorization_and_tenant_references_are_enforced(): void
{
Storage::fake('local');
[$designer, $course, $version, $lesson] = $this->foundation();
Sanctum::actingAs($designer);
$this->post('/api/v1/assets', ['file' => UploadedFile::fake()->create('payload.exe', 10, 'application/x-msdownload')], ['Accept' => 'application/json'])->assertUnprocessable()->assertJsonValidationErrors('file');
$other = User::factory()->create(['role' => UserRole::CourseDesigner]);
$asset = Asset::query()->create(['organization_id' => $other->organization_id, 'uploaded_by' => $other->getKey(), 'kind' => 'image', 'original_name' => 'foreign.png', 'disk' => 'local', 'path' => 'foreign.png', 'mime_type' => 'image/png', 'size' => 10, 'sha256' => str_repeat('a', 64)]);
$this->postJson("/api/v1/courses/{$course->getKey()}/versions/{$version->getKey()}/lessons/{$lesson->getKey()}/blocks", [
'type' => 'image', 'schemaVersion' => 1, 'data' => ['assetId' => $asset->getKey(), 'url' => null, 'alt' => 'Foreign', 'caption' => '', 'decorative' => false],
])->assertUnprocessable()->assertJsonValidationErrors('data.assetId');
$this->deleteJson("/api/v1/assets/{$asset->getKey()}")->assertNotFound();
$this->getJson("/api/v1/assets/{$asset->getKey()}")->assertNotFound();
$manager = User::factory()->for($designer->organization)->create(['role' => UserRole::Manager]);
Sanctum::actingAs($manager);
$this->getJson('/api/v1/assets')->assertForbidden();
}
/** @return array{User, Course, CourseVersion, Lesson} */
private function foundation(): array
{
$designer = User::factory()->create(['role' => UserRole::CourseDesigner]);
$course = Course::query()->create(['organization_id' => $designer->organization_id, 'title' => 'Assets', 'slug' => 'assets', 'created_by' => $designer->getKey()]);
$version = CourseVersion::query()->create(['organization_id' => $designer->organization_id, 'course_id' => $course->getKey(), 'version_number' => 1, 'status' => CourseVersionStatus::Draft, 'title' => 'Assets']);
$module = CourseModule::query()->create(['organization_id' => $designer->organization_id, 'course_version_id' => $version->getKey(), 'title' => 'Module', 'position' => 1]);
$lesson = Lesson::query()->create(['organization_id' => $designer->organization_id, 'course_version_id' => $version->getKey(), 'course_module_id' => $module->getKey(), 'title' => 'Lesson', 'position' => 1]);
return [$designer, $course, $version, $lesson];
}
}