diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..ccff21c --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +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.018,"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,"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.005,"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.005,"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.001,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_requires_authentication":0.061,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_module_id":0.024,"App\\Containers\\VikonIntegration\\Tests\\Feature\\UpdatePartTest::test_update_part_validates_part":0.01,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_completed_when_status_is_1":0.021,"App\\Containers\\VikonIntegration\\Tests\\Unit\\PollPartStatusTaskTest::test_returns_failed_when_status_is_negative_1":0.001}} \ No newline at end of file diff --git a/app/Containers/VikonIntegration/Actions/CheckAccessAction.php b/app/Containers/VikonIntegration/Actions/CheckAccessAction.php index 10e6494..2124afe 100644 --- a/app/Containers/VikonIntegration/Actions/CheckAccessAction.php +++ b/app/Containers/VikonIntegration/Actions/CheckAccessAction.php @@ -49,10 +49,24 @@ class CheckAccessAction ]; } + // Extract parts per module from the API response + $partsByModule = []; + if (isset($body['tree_access']) && is_array($body['tree_access'])) { + foreach ($body['tree_access'] as $moduleId => $moduleData) { + if (isset($moduleData['parts']) && is_array($moduleData['parts'])) { + $partsByModule[$moduleId] = array_map( + fn($p) => ['id' => $p['id'], 'name' => $p['name'] ?? $p['id'], 'access' => $p['access'] ?? true], + $moduleData['parts'] + ); + } + } + } + return [ 'has_access' => true, 'error' => null, 'modules_tree' => $body['tree_access'], + 'parts' => $partsByModule, ]; } catch (\Throwable $e) { Log::error('Vikon access check failed', ['error' => $e->getMessage()]); diff --git a/app/Containers/VikonIntegration/Actions/UpdatePartAction.php b/app/Containers/VikonIntegration/Actions/UpdatePartAction.php index 7addeb6..53c7322 100644 --- a/app/Containers/VikonIntegration/Actions/UpdatePartAction.php +++ b/app/Containers/VikonIntegration/Actions/UpdatePartAction.php @@ -24,11 +24,6 @@ class UpdatePartAction { $config = $this->modulesConfig[$moduleId] ?? throw new \RuntimeException("Unknown module: {$moduleId}"); - $allowedParts = config('vikon.parts', [])[$moduleId] ?? []; - if (!in_array($part, $allowedParts, true)) { - throw new \RuntimeException("Invalid part '{$part}' for module {$moduleId}"); - } - Log::info('Vikon: starting part update', ['module' => $moduleId, 'part' => $part]); // Step 1: Request generation diff --git a/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php b/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php index 0e93b24..cabb24e 100644 --- a/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php +++ b/app/Containers/VikonIntegration/Tests/Unit/UpdatePartActionTest.php @@ -51,21 +51,6 @@ class UpdatePartActionTest extends TestCase $action->run(999, 'common', 'token'); } - public function test_rejects_invalid_part(): void - { - $http = Mockery::mock(HttpTask::class); - $fs = Mockery::mock(FilesystemTask::class); - $poll = Mockery::mock(PollPartStatusTask::class); - - $action = new UpdatePartAction($http, $fs, $poll, $this->tempDir, $this->tempDir, [ - 1 => ['path' => 'sveden', 'allowed_folders' => ['common']], - ]); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage("Invalid part 'nonexistent' for module 1"); - $action->run(1, 'nonexistent', 'token'); - } - public function test_requests_generation_and_polls_status(): void { $http = Mockery::mock(HttpTask::class); @@ -117,8 +102,6 @@ class UpdatePartActionTest extends TestCase ->once() ->andReturn(true); - config(['vikon.parts' => [1 => ['common']]]); - $action = new UpdatePartAction($http, $fs, $poll, $this->tempDir, $this->tempDir, [ 1 => ['path' => 'sveden', 'allowed_folders' => ['common']], ]); @@ -141,8 +124,6 @@ class UpdatePartActionTest extends TestCase 'message' => 'Generation not available', ])); - config(['vikon.parts' => [1 => ['common']]]); - $action = new UpdatePartAction($http, $fs, $poll, $this->tempDir, $this->tempDir, [ 1 => ['path' => 'sveden', 'allowed_folders' => ['common']], ]); @@ -169,8 +150,6 @@ class UpdatePartActionTest extends TestCase ->once() ->andReturn(['status' => 'failed', 'error' => 'Server error']); - config(['vikon.parts' => [1 => ['common']]]); - $action = new UpdatePartAction($http, $fs, $poll, $this->tempDir, $this->tempDir, [ 1 => ['path' => 'sveden', 'allowed_folders' => ['common']], ]); diff --git a/app/Containers/VikonIntegration/UI/WEB/Controllers/VikonController.php b/app/Containers/VikonIntegration/UI/WEB/Controllers/VikonController.php index ebb54bb..ace3afd 100644 --- a/app/Containers/VikonIntegration/UI/WEB/Controllers/VikonController.php +++ b/app/Containers/VikonIntegration/UI/WEB/Controllers/VikonController.php @@ -36,12 +36,18 @@ class VikonController extends Controller { $token = Session::get('vikon_access_token'); $isAuth = $token ? $this->validateToken->run($token) : false; + $parts = []; + + if ($isAuth && $token) { + $accessResult = $this->checkAccess->run($token); + $parts = $accessResult['parts'] ?? []; + } return inertia()->render('Dashboard/VikonUpdates/Index', [ 'is_authenticated' => $isAuth, 'current_version' => config('vikon.current_version'), 'modules' => config('vikon.modules'), - 'parts' => config('vikon.parts'), + 'parts' => $parts, 'vikon_api_domain' => config('vikon.api_domain'), 'vikon_client_id' => config('vikon.client_id'), ]); @@ -70,12 +76,18 @@ class VikonController extends Controller $token = Session::get('vikon_access_token'); $isAuth = $token ? $this->validateToken->run($token) : false; + $parts = []; + + if ($isAuth && $token) { + $accessResult = $this->checkAccess->run($token); + $parts = $accessResult['parts'] ?? []; + } return inertia()->render('Dashboard/VikonUpdates/Index', [ 'is_authenticated' => $isAuth, 'current_version' => config('vikon.current_version'), 'modules' => config('vikon.modules'), - 'parts' => config('vikon.parts'), + 'parts' => $parts, 'vikon_api_domain' => config('vikon.api_domain'), 'vikon_client_id' => config('vikon.client_id'), ]); diff --git a/config/vikon.php b/config/vikon.php index 28d7a5f..24a5e3e 100644 --- a/config/vikon.php +++ b/config/vikon.php @@ -49,12 +49,6 @@ return [ ], ], - 'parts' => [ - 1 => ['common', 'struct', 'document', 'education', 'managers', 'employees', 'objects', 'paid_edu', 'budget', 'vacant', 'grants', 'inter', 'catering', 'eduStandarts', 'corruption', 'antiterrorism'], - 2 => ['abitur'], - 6 => ['general', 'structure', 'faq', 'procedures', 'results-and-reports', 'plans', 'survey'], - ], - 'poll_interval' => (int) env('VIKON_POLL_INTERVAL', 3), 'poll_max_attempts' => (int) env('VIKON_POLL_MAX_ATTEMPTS', 50), diff --git a/resources/js/Pages/Dashboard/VikonUpdates/Index.vue b/resources/js/Pages/Dashboard/VikonUpdates/Index.vue index 1619a4f..770fa58 100644 --- a/resources/js/Pages/Dashboard/VikonUpdates/Index.vue +++ b/resources/js/Pages/Dashboard/VikonUpdates/Index.vue @@ -87,9 +87,9 @@ Выбрать все