diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 463059f..b8f23c1 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":2,"defects":{"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_replaces_existing_entry":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_creates_new_entry_when_not_exists":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_returns_false_on_path_traversal":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_completed_when_status_is_completed":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_failed_when_status_is_failed":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_timeout_after_max_attempts":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_requests_generation_and_polls_status":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_generation_failure":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_poll_failure":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_restore_after_fail_removes_new_and_restores_old":8,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_module_id":8,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_part":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_rejects_invalid_part":7},"times":{"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_replaces_existing_entry":0.017,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_creates_new_entry_when_not_exists":0.002,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_returns_false_on_path_traversal":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_completed_when_status_is_completed":0.027,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_failed_when_status_is_failed":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_timeout_after_max_attempts":0.002,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_rejects_invalid_module_id":0.002,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_rejects_invalid_part":0.011,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_requests_generation_and_polls_status":0.002,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_generation_failure":0,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_poll_failure":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_restore_after_fail_removes_new_and_restores_old":0.002,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_requires_authentication":0.071,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_module_id":0.02,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_part":0.008,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_completed_when_status_is_1":0.022,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_failed_when_status_is_negative_1":0.001}} \ No newline at end of file +{"version":2,"defects":{"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_replaces_existing_entry":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_creates_new_entry_when_not_exists":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_returns_false_on_path_traversal":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_completed_when_status_is_completed":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_failed_when_status_is_failed":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_timeout_after_max_attempts":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_requests_generation_and_polls_status":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_generation_failure":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_poll_failure":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_restore_after_fail_removes_new_and_restores_old":8,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_module_id":8,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_part":8,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_rejects_invalid_part":7,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_rejects_invalid_module_id":7},"times":{"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_replaces_existing_entry":0.005,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_creates_new_entry_when_not_exists":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_atomic_swap_returns_false_on_path_traversal":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_completed_when_status_is_completed":0.027,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_failed_when_status_is_failed":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_timeout_after_max_attempts":0.002,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_rejects_invalid_module_id":0.003,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_rejects_invalid_part":0.011,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_requests_generation_and_polls_status":0.003,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_generation_failure":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\UpdatePartActionTest::test_throws_on_poll_failure":0.001,"App\\Containers\\VikonIntegration\\Tests\\Unit\\FilesystemTaskTest::test_restore_after_fail_removes_new_and_restores_old":0.004,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_requires_authentication":0.073,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_module_id":0.025,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_part":0.009,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_completed_when_status_is_1":0.026,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_failed_when_status_is_negative_1":0}} \ No newline at end of file diff --git a/app/Containers/VikonIntegration/Actions/UpdatePartAction.php b/app/Containers/VikonIntegration/Actions/UpdatePartAction.php index 53c7322..9b88386 100644 --- a/app/Containers/VikonIntegration/Actions/UpdatePartAction.php +++ b/app/Containers/VikonIntegration/Actions/UpdatePartAction.php @@ -22,7 +22,7 @@ class UpdatePartAction public function run(int $moduleId, string $part, string $accessToken): array { - $config = $this->modulesConfig[$moduleId] ?? throw new \RuntimeException("Unknown module: {$moduleId}"); + $config = $this->modulesConfig[$moduleId] ?? throw new \RuntimeException("Неизвестный модуль: {$moduleId}"); Log::info('Vikon: starting part update', ['module' => $moduleId, 'part' => $part]); @@ -35,7 +35,7 @@ class UpdatePartAction $genBody = $genResponse->json(); if (empty($genBody['operation_identity'])) { - throw new \RuntimeException('Failed to request part generation: ' . ($genBody['message'] ?? 'Unknown')); + throw new \RuntimeException('Не удалось запросить генерацию: ' . ($genBody['message'] ?? 'Неизвестная ошибка')); } $operationIdentity = $genBody['operation_identity']; @@ -47,10 +47,10 @@ class UpdatePartAction if ($pollResult['status'] !== 'completed') { $error = $pollResult['error'] ?? $pollResult['status']; - throw new \RuntimeException("Part generation failed: {$error}"); + throw new \RuntimeException("Ошибка генерации: {$error}"); } - // Step 3: Check result (API returns HTTP 200 on success, no JSON body needed) + // Step 3: Check result $checkResponse = $this->http->getWithToken( "pull_updates/checkPartGenerationByNewCoreResultJson?operation_identity={$operationIdentity}&part={$part}", $accessToken @@ -58,7 +58,7 @@ class UpdatePartAction if ($checkResponse->failed()) { $body = $checkResponse->json([]); - throw new \RuntimeException('Part not ready: ' . ($body['message'] ?? 'HTTP ' . $checkResponse->status())); + throw new \RuntimeException('Раздел не готов: ' . ($body['message'] ?? 'HTTP ' . $checkResponse->status())); } // Step 4: Download ZIP @@ -75,7 +75,7 @@ class UpdatePartAction $zip = new ZipArchive(); if ($zip->open($zipFile) !== true) { - throw new \RuntimeException('Failed to open part ZIP'); + throw new \RuntimeException('Не удалось открыть ZIP-архив'); } $zip->extractTo($tempPath); $zip->close(); @@ -85,7 +85,7 @@ class UpdatePartAction $blocked = $this->fs->validateFileTypes($tempPath); if (!empty($blocked)) { File::deleteDirectory($tempPath); - throw new \RuntimeException('Blocked files in ZIP: ' . implode(', ', $blocked)); + throw new \RuntimeException('Запрещённые файлы в архиве: ' . implode(', ', $blocked)); } $modulePath = $this->basePath . '/' . $config['path']; @@ -94,7 +94,7 @@ class UpdatePartAction // Step 5: Apply $syncedCount = $this->applyPart($part, $tempPath, $modulePath, $moduleId, $config); } finally { - // Step 6: Clean temp (always runs, even on exception) + // Step 6: Clean temp File::deleteDirectory($tempPath); } @@ -102,7 +102,7 @@ class UpdatePartAction return [ 'success' => true, - 'message' => "Part '{$part}' updated successfully.", + 'message' => "Раздел «{$part}» обновлён.", 'synced_count' => $syncedCount, ]; } @@ -138,13 +138,13 @@ class UpdatePartAction } if (!File::exists($partSource)) { - throw new \RuntimeException("Part directory not found in ZIP: {$part}"); + throw new \RuntimeException("Директория «{$part}» не найдена в архиве"); } $result = $this->fs->atomicSwap($partSource, $partTarget, $modulePath, $moduleId); if (!$result) { $this->fs->restoreAfterFail($modulePath, [$part], $moduleId); - throw new \RuntimeException("Failed to apply part: {$part}"); + throw new \RuntimeException("Не удалось применить раздел: {$part}"); } return 1; @@ -164,7 +164,7 @@ class UpdatePartAction } if (!File::exists($abiturSource)) { - throw new \RuntimeException('ABITUR directory not found in ZIP'); + throw new \RuntimeException('Директория abitur не найдена в архиве'); } $entries = File::allFiles($abiturSource); @@ -188,7 +188,7 @@ class UpdatePartAction if (!$result) { $this->fs->restoreAfterFail($modulePath, ['abitur'], $moduleId); - throw new \RuntimeException("Failed to sync ABITUR file: {$relative}"); + throw new \RuntimeException("Ошибка синхронизации файла: {$relative}"); } $synced++; diff --git a/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php b/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php index cabb24e..8f0dfba 100644 --- a/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php +++ b/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php @@ -47,7 +47,7 @@ class UpdatePartActionTest extends TestCase ]); $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Unknown module: 999'); + $this->expectExceptionMessage('Неизвестный модуль: 999'); $action->run(999, 'common', 'token'); } @@ -109,7 +109,7 @@ class UpdatePartActionTest extends TestCase $result = $action->run(1, 'common', 'token'); $this->assertTrue($result['success']); - $this->assertEquals("Part 'common' updated successfully.", $result['message']); + $this->assertEquals("Раздел «common» обновлён.", $result['message']); } public function test_throws_on_generation_failure(): void @@ -129,7 +129,7 @@ class UpdatePartActionTest extends TestCase ]); $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Failed to request part generation'); + $this->expectExceptionMessage('Не удалось запросить генерацию'); $action->run(1, 'common', 'token'); } @@ -155,7 +155,7 @@ class UpdatePartActionTest extends TestCase ]); $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Part generation failed: Server error'); + $this->expectExceptionMessage('Ошибка генерации: Server error'); $action->run(1, 'common', 'token'); } } diff --git a/resources/js/Pages/Dashboard/VikonUpdates/Index.vue b/resources/js/Pages/Dashboard/VikonUpdates/Index.vue index 1653bde..9b611e0 100644 --- a/resources/js/Pages/Dashboard/VikonUpdates/Index.vue +++ b/resources/js/Pages/Dashboard/VikonUpdates/Index.vue @@ -89,18 +89,25 @@ Выбрать все -
{{ progress }}%
- -