Fixed: basePath self→static binding, namespace detection, event wiring,
SQLite cache, file cache driver. All Mod Boot classes converted to
$listens pattern for lifecycle event discovery.
Working endpoints:
- /v1/explorer/info — live chain height, difficulty, aliases
- /v1/explorer/stats — formatted chain statistics
- /v1/names/directory — alias directory grouped by type
- /v1/names/available/{name} — name availability check
- /v1/names/lookup/{name} — name details
Co-Authored-By: Charon <charon@lethean.io>
422 lines
13 KiB
PHP
422 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* Core PHP Framework
|
|
*
|
|
* Licensed under the European Union Public Licence (EUPL) v1.2.
|
|
* See LICENSE file for details.
|
|
*/
|
|
|
|
namespace Core\Tests\Feature;
|
|
|
|
use Core\Cdn\Models\StorageOffload as StorageOffloadModel;
|
|
use Core\Cdn\Services\StorageOffload;
|
|
use Illuminate\Contracts\Filesystem\Filesystem;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use PHPUnit\Framework\Attributes\Group;
|
|
use Tests\TestCase;
|
|
|
|
#[Group('slow')]
|
|
class StorageOffloadTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected StorageOffload $offloadService;
|
|
|
|
protected string $testFilePath;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Enable offload for testing
|
|
Config::set('offload.enabled', true);
|
|
Config::set('offload.disk', 'testing');
|
|
Config::set('offload.keep_local', false);
|
|
Config::set('offload.cdn_url', 'https://cdn.example.com');
|
|
|
|
// Configure testing disk
|
|
Config::set('filesystems.disks.testing', [
|
|
'driver' => 'local',
|
|
'root' => storage_path('framework/testing/offload'),
|
|
'url' => env('APP_URL').'/testing',
|
|
'visibility' => 'public',
|
|
]);
|
|
|
|
// Rebind the service so it picks up the new config
|
|
$this->app->forgetInstance(StorageOffload::class);
|
|
$this->app->bind(StorageOffload::class, function () {
|
|
return new StorageOffload;
|
|
});
|
|
|
|
$this->offloadService = app(StorageOffload::class);
|
|
|
|
// Create test file
|
|
$this->testFilePath = storage_path('app/test-file.jpg');
|
|
file_put_contents($this->testFilePath, 'test image content');
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
// Clean up test file
|
|
if (file_exists($this->testFilePath)) {
|
|
@unlink($this->testFilePath);
|
|
}
|
|
|
|
// Clean up testing disk
|
|
Storage::disk('testing')->deleteDirectory('');
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_service_is_enabled_when_configured(): void
|
|
{
|
|
$this->assertTrue($this->offloadService->isEnabled());
|
|
}
|
|
|
|
public function test_service_is_disabled_when_not_configured(): void
|
|
{
|
|
Config::set('offload.enabled', false);
|
|
$service = new StorageOffload;
|
|
|
|
$this->assertFalse($service->isEnabled());
|
|
}
|
|
|
|
public function test_can_upload_file_to_remote_storage(): void
|
|
{
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'test');
|
|
|
|
$this->assertInstanceOf(StorageOffloadModel::class, $result);
|
|
$this->assertEquals('testing', $result->disk);
|
|
$this->assertEquals($this->testFilePath, $result->local_path);
|
|
$this->assertNotNull($result->remote_path);
|
|
$this->assertGreaterThan(0, $result->file_size);
|
|
$this->assertEquals('test', $result->category);
|
|
|
|
// Verify file exists on remote storage
|
|
$this->assertTrue(Storage::disk('testing')->exists($result->remote_path));
|
|
}
|
|
|
|
public function test_upload_creates_database_record(): void
|
|
{
|
|
$this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertDatabaseHas('storage_offloads', [
|
|
'local_path' => $this->testFilePath,
|
|
'disk' => 'testing',
|
|
'category' => 'media',
|
|
]);
|
|
}
|
|
|
|
public function test_upload_calculates_file_hash(): void
|
|
{
|
|
$originalContent = file_get_contents($this->testFilePath);
|
|
$expectedHash = hash('sha256', $originalContent);
|
|
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertEquals($expectedHash, $result->hash);
|
|
}
|
|
|
|
public function test_upload_stores_mime_type(): void
|
|
{
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertNotEmpty($result->mime_type);
|
|
}
|
|
|
|
public function test_upload_with_custom_remote_path(): void
|
|
{
|
|
$customPath = 'custom/path/file.jpg';
|
|
$result = $this->offloadService->upload($this->testFilePath, $customPath, 'media');
|
|
|
|
$this->assertEquals($customPath, $result->remote_path);
|
|
$this->assertTrue(Storage::disk('testing')->exists($customPath));
|
|
}
|
|
|
|
public function test_upload_with_metadata(): void
|
|
{
|
|
$metadata = [
|
|
'user_id' => 123,
|
|
'original_name' => 'photo.jpg',
|
|
];
|
|
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media', $metadata);
|
|
|
|
$this->assertEquals(123, $result->getMetadata('user_id'));
|
|
$this->assertEquals('photo.jpg', $result->getMetadata('original_name'));
|
|
}
|
|
|
|
public function test_upload_deletes_local_file_when_configured(): void
|
|
{
|
|
Config::set('offload.keep_local', false);
|
|
|
|
$this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertFileDoesNotExist($this->testFilePath);
|
|
}
|
|
|
|
public function test_upload_keeps_local_file_when_configured(): void
|
|
{
|
|
Config::set('offload.keep_local', true);
|
|
|
|
// Rebind service to pick up new config
|
|
$service = new StorageOffload;
|
|
$service->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertFileExists($this->testFilePath);
|
|
}
|
|
|
|
public function test_upload_returns_null_when_disabled(): void
|
|
{
|
|
Config::set('offload.enabled', false);
|
|
$service = new StorageOffload;
|
|
|
|
$result = $service->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertNull($result);
|
|
}
|
|
|
|
public function test_upload_returns_null_for_missing_file(): void
|
|
{
|
|
$result = $this->offloadService->upload('/nonexistent/file.jpg', null, 'media');
|
|
|
|
$this->assertNull($result);
|
|
}
|
|
|
|
public function test_upload_respects_max_file_size(): void
|
|
{
|
|
Config::set('offload.max_file_size', 5); // 5 bytes
|
|
|
|
// Rebind service to pick up new config
|
|
$service = new StorageOffload;
|
|
$result = $service->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertNull($result);
|
|
}
|
|
|
|
public function test_upload_respects_allowed_extensions(): void
|
|
{
|
|
Config::set('offload.allowed_extensions', ['png', 'gif']);
|
|
|
|
// Rebind service to pick up new config
|
|
$service = new StorageOffload;
|
|
|
|
// .jpg extension should be rejected
|
|
$result = $service->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertNull($result);
|
|
}
|
|
|
|
public function test_can_delete_offloaded_file(): void
|
|
{
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
$remotePath = $result->remote_path;
|
|
|
|
$deleted = $this->offloadService->delete($this->testFilePath);
|
|
|
|
$this->assertTrue($deleted);
|
|
$this->assertFalse(Storage::disk('testing')->exists($remotePath));
|
|
$this->assertDatabaseMissing('storage_offloads', [
|
|
'local_path' => $this->testFilePath,
|
|
]);
|
|
}
|
|
|
|
public function test_delete_returns_false_for_non_offloaded_file(): void
|
|
{
|
|
$deleted = $this->offloadService->delete('/nonexistent/file.jpg');
|
|
|
|
$this->assertFalse($deleted);
|
|
}
|
|
|
|
public function test_can_get_url_for_offloaded_file(): void
|
|
{
|
|
$result = $this->offloadService->upload($this->testFilePath, 'test/file.jpg', 'media');
|
|
|
|
$url = $this->offloadService->url($this->testFilePath);
|
|
|
|
$this->assertNotNull($url);
|
|
$this->assertStringContainsString('cdn.example.com', $url);
|
|
$this->assertStringContainsString('test/file.jpg', $url);
|
|
}
|
|
|
|
public function test_url_returns_null_for_non_offloaded_file(): void
|
|
{
|
|
$url = $this->offloadService->url('/nonexistent/file.jpg');
|
|
|
|
$this->assertNull($url);
|
|
}
|
|
|
|
public function test_url_uses_cdn_when_configured(): void
|
|
{
|
|
Config::set('offload.cdn_url', 'https://cdn.example.com');
|
|
|
|
$result = $this->offloadService->upload($this->testFilePath, 'test/file.jpg', 'media');
|
|
$url = $this->offloadService->url($this->testFilePath);
|
|
|
|
$this->assertStringStartsWith('https://cdn.example.com/', $url);
|
|
}
|
|
|
|
public function test_url_uses_disk_url_when_no_cdn_configured(): void
|
|
{
|
|
Config::set('offload.cdn_url', null);
|
|
|
|
$result = $this->offloadService->upload($this->testFilePath, 'test/file.jpg', 'media');
|
|
$url = $this->offloadService->url($this->testFilePath);
|
|
|
|
$this->assertNotNull($url);
|
|
$this->assertStringContainsString('test/file.jpg', $url);
|
|
}
|
|
|
|
public function test_can_check_if_file_is_offloaded(): void
|
|
{
|
|
$this->assertFalse($this->offloadService->isOffloaded($this->testFilePath));
|
|
|
|
$this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertTrue($this->offloadService->isOffloaded($this->testFilePath));
|
|
}
|
|
|
|
public function test_can_get_offload_record(): void
|
|
{
|
|
$uploaded = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$record = $this->offloadService->getRecord($this->testFilePath);
|
|
|
|
$this->assertInstanceOf(StorageOffloadModel::class, $record);
|
|
$this->assertEquals($uploaded->id, $record->id);
|
|
}
|
|
|
|
public function test_get_record_returns_null_for_non_offloaded_file(): void
|
|
{
|
|
$record = $this->offloadService->getRecord('/nonexistent/file.jpg');
|
|
|
|
$this->assertNull($record);
|
|
}
|
|
|
|
public function test_can_verify_file_integrity(): void
|
|
{
|
|
$this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$valid = $this->offloadService->verifyIntegrity($this->testFilePath);
|
|
|
|
$this->assertTrue($valid);
|
|
}
|
|
|
|
public function test_integrity_verification_fails_for_corrupted_file(): void
|
|
{
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
// Corrupt the remote file
|
|
Storage::disk('testing')->put($result->remote_path, 'corrupted content');
|
|
|
|
$valid = $this->offloadService->verifyIntegrity($this->testFilePath);
|
|
|
|
$this->assertFalse($valid);
|
|
}
|
|
|
|
public function test_can_get_storage_statistics(): void
|
|
{
|
|
// Upload a few files
|
|
file_put_contents($this->testFilePath, str_repeat('x', 1024)); // 1KB
|
|
$this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$file2 = storage_path('app/test-file-2.jpg');
|
|
file_put_contents($file2, str_repeat('y', 2048)); // 2KB
|
|
$this->offloadService->upload($file2, null, 'avatar');
|
|
|
|
$stats = $this->offloadService->getStats();
|
|
|
|
$this->assertEquals(2, $stats['total_files']);
|
|
$this->assertEquals(3072, $stats['total_size']); // 1KB + 2KB
|
|
$this->assertStringContainsString('KB', $stats['total_size_human']);
|
|
$this->assertIsArray($stats['by_category']);
|
|
$this->assertCount(2, $stats['by_category']); // media and avatar
|
|
|
|
// Clean up
|
|
@unlink($file2);
|
|
}
|
|
|
|
public function test_model_has_human_readable_file_size(): void
|
|
{
|
|
file_put_contents($this->testFilePath, str_repeat('x', 1024)); // 1KB
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$this->assertEquals('1 KB', $result->file_size_human);
|
|
}
|
|
|
|
public function test_model_can_detect_image_files(): void
|
|
{
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
$result->mime_type = 'image/jpeg';
|
|
$result->save();
|
|
|
|
$this->assertTrue($result->isImage());
|
|
$this->assertFalse($result->isVideo());
|
|
$this->assertFalse($result->isAudio());
|
|
}
|
|
|
|
public function test_model_scopes_work_correctly(): void
|
|
{
|
|
$this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
$file2 = storage_path('app/test-file-2.jpg');
|
|
file_put_contents($file2, 'test content');
|
|
$this->offloadService->upload($file2, null, 'avatar');
|
|
|
|
$mediaFiles = StorageOffloadModel::inCategory('media')->get();
|
|
$testingDiskFiles = StorageOffloadModel::forDisk('testing')->get();
|
|
|
|
$this->assertCount(1, $mediaFiles);
|
|
$this->assertCount(2, $testingDiskFiles);
|
|
|
|
// Clean up
|
|
@unlink($file2);
|
|
}
|
|
|
|
public function test_url_caching_works(): void
|
|
{
|
|
Config::set('offload.cache.enabled', true);
|
|
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'media');
|
|
|
|
// First call - should cache
|
|
$url1 = $this->offloadService->url($this->testFilePath);
|
|
$this->assertNotNull($url1);
|
|
|
|
// Second call - should return cached URL (database query not needed)
|
|
$url2 = $this->offloadService->url($this->testFilePath);
|
|
$this->assertEquals($url1, $url2);
|
|
|
|
// After deletion, cache should be cleared and URL should be null
|
|
$this->offloadService->delete($this->testFilePath);
|
|
$url3 = $this->offloadService->url($this->testFilePath);
|
|
|
|
$this->assertNull($url3);
|
|
}
|
|
|
|
public function test_remote_path_generation_organises_by_category(): void
|
|
{
|
|
$result = $this->offloadService->upload($this->testFilePath, null, 'page');
|
|
|
|
$this->assertStringStartsWith('pages/', $result->remote_path);
|
|
}
|
|
|
|
public function test_can_get_disk_instance(): void
|
|
{
|
|
$disk = $this->offloadService->getDisk();
|
|
|
|
$this->assertInstanceOf(Filesystem::class, $disk);
|
|
}
|
|
|
|
public function test_can_get_disk_name(): void
|
|
{
|
|
$diskName = $this->offloadService->getDiskName();
|
|
|
|
$this->assertEquals('testing', $diskName);
|
|
}
|
|
}
|