110 خطوط
6.6 KiB
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];
|
|
}
|
|
}
|