lthn.io/app/Core/Tests/Feature/StorageOffloadTest.php
Claude 41a90cbff8
feat: lthn.io API serving live chain data
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>
2026-04-03 17:17:42 +01:00

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);
}
}