- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
158 lines
5.2 KiB
PHP
158 lines
5.2 KiB
PHP
<?php
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class ContentUploadTest extends TestCase
|
|
{
|
|
private $storageDir;
|
|
private $originalUploadDir;
|
|
private $originalUploadUrl;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->storageDir = __DIR__ . '/../temp/uploads';
|
|
if (!is_dir($this->storageDir)) {
|
|
mkdir($this->storageDir, 0755, true);
|
|
}
|
|
|
|
global $cfg;
|
|
$this->originalUploadDir = $cfg['upload_files_dir'] ?? null;
|
|
$this->originalUploadUrl = $cfg['upload_files_url'] ?? null;
|
|
$cfg['upload_files_dir'] = $this->storageDir;
|
|
$cfg['upload_files_url'] = 'https://example.com/uploads';
|
|
|
|
// Reset singleton for clean state between tests
|
|
$reflection = new ReflectionProperty(VContent::class, 'instance');
|
|
$reflection->setAccessible(true);
|
|
$reflection->setValue(null);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
global $cfg;
|
|
if ($this->originalUploadDir !== null) {
|
|
$cfg['upload_files_dir'] = $this->originalUploadDir;
|
|
}
|
|
if ($this->originalUploadUrl !== null) {
|
|
$cfg['upload_files_url'] = $this->originalUploadUrl;
|
|
}
|
|
|
|
if (is_dir($this->storageDir)) {
|
|
$this->removeDirectory($this->storageDir);
|
|
}
|
|
|
|
$reflection = new ReflectionProperty(VContent::class, 'instance');
|
|
$reflection->setAccessible(true);
|
|
$reflection->setValue(null);
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function testHandleUploadStoresFileSuccessfully(): void
|
|
{
|
|
$content = VContent::getInstance();
|
|
|
|
$tmpFile = tempnam(sys_get_temp_dir(), 'upload');
|
|
file_put_contents($tmpFile, str_repeat('A', 1024));
|
|
|
|
$file = [
|
|
'name' => 'test-video.mp4',
|
|
'tmp_name' => $tmpFile,
|
|
'size' => filesize($tmpFile),
|
|
'error' => UPLOAD_ERR_OK,
|
|
'type' => 'video/mp4'
|
|
];
|
|
|
|
$result = $content->handleUpload($file, 123, [
|
|
'type' => 'video',
|
|
'store_metadata' => false
|
|
]);
|
|
|
|
$this->assertTrue($result['success'], 'Expected upload to succeed');
|
|
$this->assertArrayHasKey('data', $result);
|
|
|
|
$data = $result['data'];
|
|
$this->assertEquals('video', $data['type']);
|
|
$this->assertNotEmpty($data['stored_path']);
|
|
|
|
$storedPath = $this->storageDir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $data['stored_path']);
|
|
$this->assertFileExists($storedPath, 'Stored file should exist on disk');
|
|
|
|
$progress = $content->getUploadProgress($data['upload_id']);
|
|
$this->assertEquals('completed', $progress['status']);
|
|
$this->assertEquals(100, $progress['progress']);
|
|
}
|
|
|
|
public function testHandleUploadRejectsInvalidMimeType(): void
|
|
{
|
|
$content = VContent::getInstance();
|
|
|
|
$tmpFile = tempnam(sys_get_temp_dir(), 'upload');
|
|
file_put_contents($tmpFile, "<?php echo 'malicious'; ?>");
|
|
|
|
$file = [
|
|
'name' => 'malware.php',
|
|
'tmp_name' => $tmpFile,
|
|
'size' => filesize($tmpFile),
|
|
'error' => UPLOAD_ERR_OK,
|
|
'type' => 'text/x-php'
|
|
];
|
|
|
|
$result = $content->handleUpload($file, 321, [
|
|
'type' => 'document',
|
|
'store_metadata' => false
|
|
]);
|
|
|
|
$this->assertFalse($result['success'], 'Upload should fail for dangerous MIME types');
|
|
$this->assertNotEmpty($result['message']);
|
|
}
|
|
|
|
public function testProgressLifecycle(): void
|
|
{
|
|
$content = VContent::getInstance();
|
|
$uploadId = 'progress_test_' . bin2hex(random_bytes(4));
|
|
|
|
$content->initProgress($uploadId, 2000);
|
|
$progress = $content->getUploadProgress($uploadId);
|
|
$this->assertEquals('in_progress', $progress['status']);
|
|
$this->assertEquals(0, $progress['progress']);
|
|
|
|
$content->updateProgress($uploadId, 1000, 2000);
|
|
$progress = $content->getUploadProgress($uploadId);
|
|
$this->assertEquals(50, $progress['progress']);
|
|
|
|
$content->completeProgress($uploadId);
|
|
$progress = $content->getUploadProgress($uploadId);
|
|
$this->assertEquals('completed', $progress['status']);
|
|
$this->assertEquals(100, $progress['progress']);
|
|
|
|
$content->cleanupProgress($uploadId);
|
|
$progress = $content->getUploadProgress($uploadId);
|
|
$this->assertEquals('unknown', $progress['status']);
|
|
}
|
|
|
|
private function removeDirectory($dir): void
|
|
{
|
|
if (!is_dir($dir)) {
|
|
return;
|
|
}
|
|
|
|
$items = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
|
RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
|
|
foreach ($items as $item) {
|
|
if ($item->isDir()) {
|
|
@rmdir($item->getRealPath());
|
|
} else {
|
|
@unlink($item->getRealPath());
|
|
}
|
|
}
|
|
|
|
@rmdir($dir);
|
|
}
|
|
}
|