make(ApiRepository::class), $userRepo ?? $this->make(UserRepository::class), $projectRepo ?? $this->make(ProjectRepository::class), $menuRepo ?? $this->make(MenuRepository::class), ); } public function test_get_project_relation_ids_extracts_project_ids(): void { $projectRepo = $this->make(ProjectRepository::class, [ 'getUserProjectRelation' => fn () => [ ['projectId' => 5], ['projectId' => 9], ], ]); $result = $this->makeService(projectRepo: $projectRepo)->getProjectRelationIds(3); $this->assertSame([5, 9], $result); } public function test_create_api_key_with_projects_sets_relations_when_projects_selected(): void { $editCalledWith = null; $deleteCalled = false; $userRepo = $this->make(UserRepository::class, [ 'addUser' => fn () => '77', ]); $projectRepo = $this->make(ProjectRepository::class, [ 'editUserProjectRelations' => function ($id, $projects) use (&$editCalledWith) { $editCalledWith = [$id, $projects]; return true; }, 'deleteAllProjectRelations' => function () use (&$deleteCalled) { $deleteCalled = true; }, ]); $result = $this->makeService(userRepo: $userRepo, projectRepo: $projectRepo) ->createApiKeyWithProjects(['firstname' => 'Key', 'role' => '20'], ['3', '4']); $this->assertIsArray($result); $this->assertSame('77', $result['id']); // id is cast to int when reconciling relations. $this->assertSame([77, ['3', '4']], $editCalledWith); $this->assertFalse($deleteCalled); } public function test_create_api_key_with_projects_clears_relations_when_leading_zero(): void { $editCalled = false; $deleteCalledWith = null; $userRepo = $this->make(UserRepository::class, [ 'addUser' => fn () => '88', ]); $projectRepo = $this->make(ProjectRepository::class, [ 'editUserProjectRelations' => function () use (&$editCalled) { $editCalled = true; return true; }, 'deleteAllProjectRelations' => function ($id) use (&$deleteCalledWith) { $deleteCalledWith = $id; }, ]); $this->makeService(userRepo: $userRepo, projectRepo: $projectRepo) ->createApiKeyWithProjects(['firstname' => 'Key'], ['0']); $this->assertFalse($editCalled); $this->assertSame(88, $deleteCalledWith); } public function test_create_api_key_with_projects_skips_reconcile_when_no_projects(): void { $touched = false; $userRepo = $this->make(UserRepository::class, [ 'addUser' => fn () => '5', ]); $projectRepo = $this->make(ProjectRepository::class, [ 'editUserProjectRelations' => function () use (&$touched) { $touched = true; return true; }, 'deleteAllProjectRelations' => function () use (&$touched) { $touched = true; }, ]); // null projects and empty array both mean "do nothing". $this->makeService(userRepo: $userRepo, projectRepo: $projectRepo) ->createApiKeyWithProjects(['firstname' => 'Key'], null); $this->assertFalse($touched); } public function test_create_api_key_with_projects_returns_false_when_user_not_created(): void { $userRepo = $this->make(UserRepository::class, [ 'addUser' => fn () => false, ]); $result = $this->makeService(userRepo: $userRepo) ->createApiKeyWithProjects(['firstname' => 'Key'], ['3']); $this->assertFalse($result); } public function test_update_api_key_edits_user_and_reconciles_relations(): void { $editUserCalledWith = null; $editRelationsCalledWith = null; $userRepo = $this->make(UserRepository::class, [ 'getUser' => fn () => [ 'firstname' => 'Old', 'username' => 'lt_old', 'status' => 'i', 'role' => '10', ], 'editUser' => function ($values, $id) use (&$editUserCalledWith) { $editUserCalledWith = [$values, $id]; return true; }, ]); $projectRepo = $this->make(ProjectRepository::class, [ 'editUserProjectRelations' => function ($id, $projects) use (&$editRelationsCalledWith) { $editRelationsCalledWith = [$id, $projects]; return true; }, ]); $result = $this->makeService(userRepo: $userRepo, projectRepo: $projectRepo) ->updateApiKey(12, ['firstname' => 'New', 'status' => 'a', 'role' => '20'], ['7']); $this->assertTrue($result); // Posted firstname/status/role applied, username preserved from row, source forced to 'api'. $this->assertSame(12, $editUserCalledWith[1]); $this->assertSame('New', $editUserCalledWith[0]['firstname']); $this->assertSame('a', $editUserCalledWith[0]['status']); $this->assertSame('20', $editUserCalledWith[0]['role']); $this->assertSame('lt_old', $editUserCalledWith[0]['user']); $this->assertSame('api', $editUserCalledWith[0]['source']); $this->assertSame([12, ['7']], $editRelationsCalledWith); } public function test_update_api_key_falls_back_to_row_values_when_not_posted(): void { $editUserCalledWith = null; $userRepo = $this->make(UserRepository::class, [ 'getUser' => fn () => [ 'firstname' => 'Old', 'username' => 'lt_old', 'status' => 'i', 'role' => '10', ], 'editUser' => function ($values) use (&$editUserCalledWith) { $editUserCalledWith = $values; return true; }, ]); $projectRepo = $this->make(ProjectRepository::class, [ 'deleteAllProjectRelations' => fn () => null, ]); $this->makeService(userRepo: $userRepo, projectRepo: $projectRepo) ->updateApiKey(12, [], null); $this->assertSame('Old', $editUserCalledWith['firstname']); $this->assertSame('i', $editUserCalledWith['status']); $this->assertSame('10', $editUserCalledWith['role']); } public function test_update_api_key_throws_on_invalid_id(): void { $this->expectException(\Exception::class); $this->makeService()->updateApiKey(0, [], null); } }