--- /dev/null
+<?php
+
+declare(strict_types=1);
+
+namespace tests\unit\services;
+
+use Codeception\Test\Unit;
+
+/**
+ * Тесты FileService для видео-конвертации
+ *
+ * Тестируют метод convertToMp4() и обработку AVI файлов.
+ * TDD RED phase: тесты написаны ДО реализации метода.
+ *
+ * @group services
+ * @group file
+ * @group video
+ */
+class FileServiceVideoTest extends Unit
+{
+ /**
+ * Путь к FileService
+ */
+ private string $fileServicePath;
+
+ protected function _before(): void
+ {
+ $this->fileServicePath = dirname(__DIR__, 3) . '/services/FileService.php';
+ }
+
+ /**
+ * Проверяет наличие метода convertToMp4 в FileService
+ *
+ * TDD RED: Этот тест должен ПРОВАЛИТЬСЯ пока метод не реализован.
+ */
+ public function testConvertToMp4MethodExists(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем наличие метода convertToMp4
+ $hasMethod = preg_match(
+ '/public\s+static\s+function\s+convertToMp4\s*\(/s',
+ $content
+ );
+
+ $this->assertEquals(
+ 1,
+ $hasMethod,
+ 'FileService should have public static method convertToMp4()'
+ );
+ }
+
+ /**
+ * Проверяет сигнатуру метода convertToMp4
+ *
+ * Ожидаемая сигнатура:
+ * public static function convertToMp4(string $sourcePath, string $targetPath): ?string
+ */
+ public function testConvertToMp4MethodSignature(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем сигнатуру метода с типами параметров и возвращаемым типом
+ $hasCorrectSignature = preg_match(
+ '/public\s+static\s+function\s+convertToMp4\s*\(\s*string\s+\$\w+\s*,\s*string\s+\$\w+\s*\)\s*:\s*\?string/s',
+ $content
+ );
+
+ $this->assertEquals(
+ 1,
+ $hasCorrectSignature,
+ 'convertToMp4 should have signature: (string $sourcePath, string $targetPath): ?string'
+ );
+ }
+
+ /**
+ * Проверяет что convertToMp4 проверяет наличие FFmpeg
+ *
+ * Метод должен использовать `which ffmpeg` для проверки.
+ */
+ public function testConvertToMp4ChecksFfmpegAvailability(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем что метод проверяет наличие FFmpeg
+ $this->assertStringContainsString(
+ 'which ffmpeg',
+ $content,
+ 'convertToMp4 should check FFmpeg availability using "which ffmpeg"'
+ );
+ }
+
+ /**
+ * Проверяет что convertToMp4 использует escapeshellarg для безопасности
+ */
+ public function testConvertToMp4UsesEscapeshellarg(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем использование escapeshellarg для безопасности shell-команд
+ $this->assertStringContainsString(
+ 'escapeshellarg',
+ $content,
+ 'convertToMp4 should use escapeshellarg() for security'
+ );
+ }
+
+ /**
+ * Проверяет что convertToMp4 использует флаг -y для перезаписи
+ */
+ public function testConvertToMp4UsesFfmpegOverwriteFlag(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем использование флага -y для перезаписи без подтверждения
+ $hasFfmpegCommand = preg_match(
+ '/ffmpeg\s+-y\s+-i/s',
+ $content
+ );
+
+ $this->assertEquals(
+ 1,
+ $hasFfmpegCommand,
+ 'convertToMp4 should use ffmpeg -y flag for overwrite without confirmation'
+ );
+ }
+
+ /**
+ * Проверяет что convertToMp4 использует movflags для быстрого старта
+ */
+ public function testConvertToMp4UsesFaststartFlag(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем использование -movflags +faststart для быстрого старта воспроизведения
+ $this->assertStringContainsString(
+ 'faststart',
+ $content,
+ 'convertToMp4 should use -movflags +faststart for quick playback start'
+ );
+ }
+
+ /**
+ * Проверяет что convertToMp4 логирует при отсутствии FFmpeg
+ */
+ public function testConvertToMp4LogsWhenFfmpegNotInstalled(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем что есть логирование при отсутствии FFmpeg
+ $hasWarningLog = preg_match(
+ '/Yii::warning\s*\([^)]*FFmpeg[^)]*\)/si',
+ $content
+ );
+
+ $this->assertEquals(
+ 1,
+ $hasWarningLog,
+ 'convertToMp4 should log warning when FFmpeg is not installed'
+ );
+ }
+
+ /**
+ * Проверяет что AVI файлы определяются как video тип
+ *
+ * TDD RED: AVI должен быть добавлен в switch-case.
+ */
+ public function testAviFileTypeIsVideo(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем наличие avi в типах
+ $this->assertStringContainsString(
+ "'avi'",
+ $content,
+ 'FileService should support .avi file type'
+ );
+
+ // Проверяем, что avi определяется как video
+ // Паттерн: case 'avi': ... $type = 'video'
+ $hasAviAsVideo = preg_match(
+ "/case\s+'avi'.*?'video'/s",
+ $content
+ );
+
+ $this->assertEquals(
+ 1,
+ $hasAviAsVideo,
+ '.avi files should be classified as video type'
+ );
+ }
+
+ /**
+ * Проверяет авто-конвертацию MOV/AVI после saveAs
+ */
+ public function testSaveUploadedFileAutoConvertsMovAvi(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем что saveUploadedFile вызывает convertToMp4 для mov/avi
+ $hasAutoConvert = preg_match(
+ "/in_array\s*\([^)]*\['mov',\s*'avi'\]|in_array\s*\([^)]*\['avi',\s*'mov'\]/s",
+ $content
+ );
+
+ $this->assertEquals(
+ 1,
+ $hasAutoConvert,
+ 'saveUploadedFile should auto-convert mov/avi files using convertToMp4'
+ );
+ }
+
+ /**
+ * Проверяет что оригинальный файл удаляется только после успешной конвертации
+ */
+ public function testOriginalFileDeletedOnlyAfterSuccessfulConversion(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем паттерн: if ($converted !== null) { @unlink(...) }
+ // Это означает что удаление происходит только при успешной конвертации
+ $hasCorrectDeleteLogic = preg_match(
+ '/if\s*\(\s*\$\w+\s*!==\s*null\s*\)\s*\{[^}]*@?unlink/s',
+ $content
+ );
+
+ $this->assertEquals(
+ 1,
+ $hasCorrectDeleteLogic,
+ 'Original file should only be deleted after successful conversion (when result !== null)'
+ );
+ }
+
+ /**
+ * Проверяет что convertToMp4 использует h264 видеокодек
+ */
+ public function testConvertToMp4UsesH264Codec(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем использование h264 кодека
+ $this->assertStringContainsString(
+ 'h264',
+ $content,
+ 'convertToMp4 should use h264 video codec for browser compatibility'
+ );
+ }
+
+ /**
+ * Проверяет что convertToMp4 использует aac аудиокодек
+ */
+ public function testConvertToMp4UsesAacCodec(): void
+ {
+ if (!file_exists($this->fileServicePath)) {
+ $this->markTestSkipped('FileService.php not found');
+ }
+
+ $content = file_get_contents($this->fileServicePath);
+
+ // Проверяем использование aac кодека
+ $this->assertStringContainsString(
+ 'aac',
+ $content,
+ 'convertToMp4 should use aac audio codec for browser compatibility'
+ );
+ }
+}