Project: doctolib-doctolib · Branch: .invalid · Mode: ` · **Model:**claude-opus-4-7`
Stats: 1 user messages, 33 tool calls, tools used: Bash, Read, ToolSearch.
Conversation
Turn 1 — User
Analyze test coverage for PR #241763 (doctolib/doctolib) titled "fix(LION-1307): refactor to prepare account.organizations getter removal".
Context: Ruby/Rails refactor in the ti_connect engine. It changes policies and controllers to use a new organization membership pattern instead of account.organizations. The PR ships +140/-101 lines, the majority in test files.
Where to read:
- Worktree (post-change): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect
- Full unified diff: /tmp/pr-241763.diff
Source under change: - engines/ti_connect/app/controllers/ti_connect/organizations/connector_configurations_controller.rb - engines/ti_connect/app/controllers/ti_connect/practices/connector_configurations_controller.rb - engines/ti_connect/app/policies/ti_connect/application_scope.rb - engines/ti_connect/app/policies/ti_connect/configuration_policy.rb - engines/ti_connect/app/policies/ti_connect/connector_policy.rb
Tests added/changed — read these in full and judge whether they actually exercise the new behaviour: - engines/ti_connect/test/controllers/ti_connect/organizations/connector_configurations_controller_test.rb - engines/ti_connect/test/controllers/ti_connect/practices/connector_configurations_controller_test.rb - engines/ti_connect/test/controllers/ti_connect/practices/connectors_controller_test.rb - engines/ti_connect/test/controllers/ti_connect/workplaces_controller_test.rb - engines/ti_connect/test/policies/ti_connect/connector_policy_test.rb - engines/ti_connect/test/policies/ti_connect/workplace_policy_test.rb - engines/ti_connect/test/public/ti_connect/api/connector_test.rb - test/integration/configuration/ti_connect/ti_connect_configuration_page_test.rb - test/integration/configuration/ti_connect/ti_workplace_configuration_page_test.rb
What to evaluate:
1. Authorization semantics: are there tests proving access is denied for accounts NOT in the new organization membership (negative cases), not just allowed for valid ones?
2. The application_scope.rb change adds branching logic (~+12 lines). Is each branch covered?
3. configuration_policy.rb adds 4 new lines. Are the new permission paths tested?
4. Are there missing edge cases: no organization, multiple organizations, soft-deleted memberships, cross-organization access attempts?
5. Are tests doing real DB integration vs over-mocked? Doctolib prefers integration over heavy mocks for policy work.
DO NOT post anything to GitHub. Output to the terminal.
Output format — Conventional Comments (<label> [decorations]: <subject>), labels: issue, suggestion, question, praise, nitpick. Anchor each comment to file:line. Start with a 3-5 line verdict on coverage adequacy, then per-test-file comments. List concrete missing test scenarios as suggestion (test): ....
Turn 1 — Assistant
I'll analyze the test coverage for this PR by examining the source changes and corresponding tests.
Tools used:
Bash:ls /tmp/pr-241763.diff /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect 2>&1 | head -5
Tool results:
→ result (ok): /tmp/pr-241763.diff
/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect: AGENTS.md app
Tools used:
Bash:wc -l /tmp/pr-241763.diff
Tool results:
→ result (ok): 729 /tmp/pr-241763.diff
Tools used:
Read:/tmp/pr-241763.diff
Tool results:
→ result (ok): 1 diff --git a/engines/ti_connect/app/controllers/ti_connect/organizations/connector_configurations_controller.rb b/engines/ti_connect/app/controllers/ti_connect/organizations/connector_configurations_controller.rb 2 index cc7dd1c6c2acd..c76ed5b78ed80 100644 3 --- a/engines/ti_connect/app/controllers/ti_connect/organizations/connector_configurations_controller.rb 4 +++ b/engines/ti_connect/app/controllers/ti_connect/organizations/connector_configurations_controller.rb 5 @@ -8,7 +8,10 @@ class Or …(truncated, 41579 more chars)
Now let me read the full source files (post-change) and the test files to evaluate coverage.
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/policies/ti_connect/application_scope.rb
Tool results:
→ result (ok): 1 module TiConnect
2 class ApplicationScope < ::V2::ApplicationScope
3 def current_account_practice_ids
4 CalendarManagement::Base::Practice.where(
5 organization_id: ::Organizations::Api::Get.organization_ids_for(account_id: current_account.id),
6 ).pluck(:id)
7 end
8
9 def current_account_managed_practice_ids
10 CalendarManagement::Base::Practice.where(organization_id: current_account.managed_organization_ids).pluck(:id)
11 end
12
13 def with_p
…(truncated, 204 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/policies/ti_connect/configuration_policy.rb
Tool results:
→ result (ok): 1 class TiConnect::ConfigurationPolicy < TiConnect::ApplicationPolicy
2 allow(:write) { to(:doctor) { authorized_organization_admin? } }
3
4 allow(:read) { to(:doctor) { authorized_organization_admin? } }
5
6 class Scope < TiConnect::ApplicationScope
7 scope_for(:doctor) { with_preloaded_associations(scope).where(practice_id: current_account_managed_practice_ids) }
8 end
9
10 private
11
12 def authorized_organization_admin?
13 practice = CalendarManagement::Base::Practice
…(truncated, 160 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/policies/ti_connect/connector_policy.rb
Tool results:
→ result (ok): 1 class TiConnect::ConnectorPolicy < TiConnect::ApplicationPolicy 2 class Scope < TiConnect::ApplicationScope 3 scope_for(:doctor) { with_preloaded_associations(scope).where(practice_id: current_account_practice_ids) } 4 end 5 end 6
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/controllers/ti_connect/organizations/connector_configurations_controller.rb
Tool results:
→ result (ok): 1 module TiConnect
2 class Organizations::ConnectorConfigurationsController < TiConnect::ApplicationController
3 before_action :validate_params!
4 after_action :verify_policy_scoped, only: %i[index]
5 after_action :verify_authorized, except: %i[index]
6
7 IndexContract = Dry::Schema.Params { required(:organization_id).filled(:string) }
8
9 def index
10 practice_ids = CalendarManagement::Base::Practice.where(organization_id: safe_params[:organization_id]).pluck(:id)
…(truncated, 4660 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/controllers/ti_connect/practices/connector_configurations_controller.rb
Tool results:
→ result (ok): 1 module TiConnect
2 class Practices::ConnectorConfigurationsController < TiConnect::ApplicationController
3 before_action :validate_params!
4 before_action :fetch_scoped_connectors
5 after_action :verify_policy_scoped, only: %i[index]
6 after_action :verify_authorized, except: %i[index]
7
8 IndexContract = Dry::Schema.Params { required(:practice_id).filled(:string) }
9
10 def index
11 connectors = @connectors.where(practice_id: safe_params[:practice_id]).order(
…(truncated, 507 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/connector_policy_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 class TiConnect::ConnectorPolicyTest < AuthorizationMinitestSpec::Policy::TestCase
4 self.policy_class = TiConnect::ConnectorPolicy
5
6 let!(:organization) { create(:organization, :with_german_practice) }
7 let!(:practice) { organization.practices.first }
8 let!(:account) { create(:hcp_account) }
9 let!(:connector) { create(:ti_connect_connector, deprecated_practice: practice) }
10
11 describe 'scope' do
12 user_type(:doctor) do
13 describe "w
…(truncated, 1922 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/workplace_policy_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 class TiConnect::WorkplacePolicyTest < AuthorizationMinitestSpec::Policy::TestCase
4 self.policy_class = TiConnect::WorkplacePolicy
5
6 let!(:organization) { create(:organization, :with_german_practice) }
7 let!(:practice) { organization.practices.first }
8 let!(:account) { create(:hcp_account) }
9 let!(:workplace) { create(:ti_connect_workplace, deprecated_practice: practice) }
10
11 describe 'scope' do
12 user_type(:doctor) do
13 describe "w
…(truncated, 1531 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/controllers/ti_connect/organizations/connector_configurations_controller_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 class TiConnect::Organizations::ConnectorConfigurationsControllerTest < Doctolib::Testing::ActionDispatch::IntegrationTest
4 self.subdomain = :pro
5
6 let!(:doctor_account) { create(:hcp_account, :doctor, :verified, :with_unique_email, locale: :de) }
7 let!(:organization) { Organization.sole }
8 let!(:patient_base) do
9 PatientBases::Api::PatientBaseRepository.find_by_query({ organization_id: organization.id }).value_or([]).sole
10 end
11 let!(:agend
…(truncated, 15413 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/controllers/ti_connect/practices/connector_configurations_controller_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 class TiConnect::Practices::ConnectorConfigurationsControllerTest < Doctolib::Testing::ActionDispatch::IntegrationTest
4 self.subdomain = :pro
5
6 describe '#index' do
7 let!(:organization) { create(:organization, country: Country::DE) }
8 let!(:doctor_account) { create(:hcp_account, :verified, :with_unique_email, locale: :de) }
9 let!(:agenda) { create(:agenda, organization_id: organization.id, practitioner_id: doctor_account.id) }
10 let!(:pati
…(truncated, 2764 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/controllers/ti_connect/practices/connectors_controller_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 class TiConnect::Practices::ConnectorsControllerTest < Doctolib::Testing::ActionDispatch::IntegrationTest
4 self.subdomain = :pro
5
6 let!(:practice) { create(:practice, :de) }
7 let!(:doctor_account) { create(:hcp_account, :verified, :with_unique_email, locale: :de) }
8
9 describe '#index' do
10 before { login_session(doctor_account) }
11
12 describe 'when practice organization is not managed by user' do
13 let!(:connector) { create(:ti_conn
…(truncated, 3173 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/controllers/ti_connect/workplaces_controller_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 class TiConnect::WorkplacesControllerTest < Doctolib::Testing::ActionDispatch::IntegrationTest
4 self.subdomain = :pro
5
6 let!(:doctor_account) { create(:hcp_account, :verified, :with_unique_email, locale: :de) }
7 let!(:practice) { create(:practice, :de) }
8
9 describe '#show' do
10 let!(:workplace_1) { create(:ti_connect_workplace, practice_id: practice.id) }
11 let!(:workplace_2) { create(:ti_connect_workplace, practice_id: practice.id) }
12
13
…(truncated, 12868 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/public/ti_connect/api/connector_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 module TiConnect::Api
4 class ConnectorTest < Doctolib::Testing::ActiveSupport::TestCase
5 describe '#information_for' do
6 let!(:account) { create(:hcp_account, :doctor) }
7 let!(:first_organization) { create(:organization, country: Country::DE) }
8
9 describe 'invalid authorization context' do
10 describe 'when current user type is not compatible with account' do
11 it 'raises an error' do
12 connector_api = Ti
…(truncated, 5691 more chars)
Now let me understand the difference between current_account_practice_ids (used in ConnectorPolicy) and current_account_managed_practice_ids (used in ConfigurationPolicy). I need to check what each method covers and how they differ.
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect && grep -rn "managed_organization_ids\b" --include="*.rb" -l 2>/dev/null | head -10
Tool results:
→ result (ERROR): Permission to use Bash has been denied. IMPORTANT: You may attempt to accomplish this action using other tools that might naturally be used to accomplish this goal, e.g. using head instead of cat. But you should not attempt to work around this denial in malicious ways, e.g. do not use your ability to run tests to execute non-test actions. You should only try to work around this restriction in reasonable ways that do not attempt to bypass the intent behind this denial. If you believe this cap …(truncated, 178 more chars)
Tools used:
Bash:cd /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect && grep -rn "def managed_organization_ids\|def organization_ids_for" --include="*.rb" 2>/dev/null | head -10
Tool results:
→ result (ERROR): Permission to use Bash has been denied. IMPORTANT: You may attempt to accomplish this action using other tools that might naturally be used to accomplish this goal, e.g. using head instead of cat. But you should not attempt to work around this denial in malicious ways, e.g. do not use your ability to run tests to execute non-test actions. You should only try to work around this restriction in reasonable ways that do not attempt to bypass the intent behind this denial. If you believe this cap …(truncated, 178 more chars)
Tools used:
ToolSearch(inputs: query, max_results)
Tool results:
→ result (ok): No matching deferred tools found
Tools used:
Bash:grep -rn "def managed_organization_ids" /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect --include="*.rb" 2>/dev/null | head -10
Tools used:
Bash:grep -rn "def organization_ids_for\|def self.organization_ids_for" /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect --include="*.rb" 2>/dev/null | head -10
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/know_your_organization/app/public/know_your_organization/api/user.rb:17: def organization_ids_for(account_id:) /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/organizations/app/models/organizations/membership.rb:128: def organization_ids_for(account_id:, use_interservice_api: false) /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/organizations/a …(truncated, 1522 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/app/models/concerns/account_authorization.rb:88: def managed_organization_ids
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/app/models/concerns/account_authorization.rb(75–105)
Tool results:
→ result (ok): 75
76 def agenda_unique_organizations
77 AgendaAccess::Api::Get.organizations_for(account_ids: id)
78 end
79
80 def agenda_unique_organization_ids
81 agenda_unique_organizations.distinct.pluck(:id)
82 end
83
84 def practice_ids
85 CalendarManagement::Base::Practice.where(organization_id: agenda_unique_organization_ids).distinct.pluck(:id)
86 end
87
88 def managed_organization_ids
89 managed_organizations.pluck(:id)
90 end
91
92 # @deprecated Use {#managed_o
…(truncated, 419 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/organizations/app/public/organizations/api/get.rb(115–140)
Tool results:
→ result (ok): 115 #
116 # @example
117 # Organizations::Api::Get.organization_ids_for(account_id: 5678)
118 #
119 # @param account_id [Integer] The account Id.
120 # @param use_interservice_api [Boolean] Whether to use the interservice API.
121 # @return Array[Integer] Organization Ids
122 sig do
123 params(account_id: T.nilable(Integer), use_interservice_api: T.nilable(T::Boolean)).returns(T::Array[Integer])
124 end
125
…(truncated, 595 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/app/models/concerns/account_authorization.rb(1–76)
Tool results:
→ result (ok): 1 module AccountAuthorization
2 extend ActiveSupport::Concern
3
4 included do
5 attr_accessor :current_agenda_authorizations, :agenda_ids_for_current_account
6
7 scope(
8 :for_organizations,
9 lambda do |organization_ids|
10 account_ids =
11 Array(organization_ids)
12 .flat_map { |organization_id| Organizations::Api::Get.member_ids(organization_id: organization_id) }
13 .uniq
14 where(id: account_ids)
15 end,
16
…(truncated, 2356 more chars)
Now I have a clear picture. Let me verify a few additional things about the integration tests:
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/test/integration/configuration/ti_connect/ti_connect_configuration_page_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 module Integration::Configuration::TiConnect
4 class TiConnectConfigurationPageTest < Doctolib::Testing::Legacy::ActionDispatch::SystemTestCase
5 include ToastsTestHelper
6
7 self.subdomain = :pro
8
9 let!(:patient_base_one) { create(:patient_base, name: 'Patient Base A') }
10 let!(:organization) { patient_base_one.organization }
11 let!(:practice) { create(:practice, :de, formal_name: 'Practice A', organization: organization) }
12 let!(:a
…(truncated, 15024 more chars)
Now let me check the original (pre-change) versions to understand what was removed and whether negative tests existed for the new policy structure:
Tools used:
Bash:grep -rn "with_preloaded_associations\|current_account_managed_practice_ids" /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect --include="*.rb" 2>/dev/null | head -20
Tools used:
Bash:grep -rn "ConfigurationPolicy::Scope\|ConfigurationPolicy.new" /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect --include="*.rb" 2>/dev/null | head -20
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/app/controllers/admin/secretaryship_configurations_controller.rb:50: policy_scope(SecretaryshipConfiguration, policy_scope_class: V2::Admin::SecretaryshipConfigurationPolicy::Scope) /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/test/policies/configuration/custom_field_configuration_policy_test.rb:28: Configuration::CustomFieldConfigurationPolicy::Scope /Users/USER/workspace/doct …(truncated, 4593 more chars)
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/policies/ti_connect/configuration_policy.rb:7: scope_for(:doctor) { with_preloaded_associations(scope).where(practice_id: current_account_managed_practice_ids) } /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/policies/ti_connect/application_scope.rb:9: def current_account_managed_practice_ids /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307 …(truncated, 368 more chars)
Let me check if there's a test for ConfigurationPolicy::Scope (which is new):
Tools used:
Bash:find /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test -name "configuration_policy_test*" -type f 2>/dev/null
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/configuration_policy_test.rb
Tools used:
Bash:grep -rn "ConfigurationPolicy" /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test --include="*.rb" 2>/dev/null | head -10
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/signature_configuration_policy_test.rb:3:class TiConnect::SignatureConfigurationPolicyTest < AuthorizationMinitestSpec::Policy::TestCase /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/signature_configuration_policy_test.rb:4: self.policy_class = TiConnect::SignatureConfigurationPolicy /Users/USER/workspace/doctolib …(truncated, 401 more chars)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/configuration_policy_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 class TiConnect::ConfigurationPolicyTest < AuthorizationMinitestSpec::Policy::TestCase
4 self.policy_class = TiConnect::ConfigurationPolicy
5
6 let!(:organization) { create(:organization, :with_german_practice) }
7 let!(:practice) { organization.practices.first }
8 let!(:account) { create(:hcp_account) }
9 let!(:connector) { create(:ti_connect_connector, deprecated_practice: practice) }
10
11 describe 'policy' do
12 user_type(:doctor) do
13 ac
…(truncated, 1020 more chars)
This is significant - the existing ConfigurationPolicyTest does NOT test the new Scope class that was added in this PR. Let me also check the integration test and also check how add_as_admin_for interacts with the membership for organizations.
Tools used:
Bash:grep -rn "def add_as_admin_for" /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect --include="*.rb" 2>/dev/null | head -5
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/app/models/concerns/account_authorization.rb:107: def add_as_admin_for(organization:)
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/app/models/concerns/account_authorization.rb(105–130)
Tool results:
→ result (ok): 105 end 106 107 def add_as_admin_for(organization:) 108 OrganizationAdmin::Api::Manage.create!(organization_id: organization.id, account_id: id) 109 end 110 111 def clear_agenda_authorizations_memoization 112 @agenda_ids_for_current_account = nil 113 @current_agenda_authorizations = nil 114 end 115 116 def agendas=(new_agendas) 117 updated_agendas_ids = new_agendas.pluck(:id) 118 current_agendas_ids = AgendaAccess::Api::Get.agenda_ids_for(account_ids: id) 119
…(truncated, 387 more chars)
Now I understand the discrepancy. add_as_admin_for uses OrganizationAdmin::Api::Manage.create!, and managed_organizations looks up via OrganizationAdmin::Api::Get.organizations_for. So the current_account_managed_practice_ids (used in ConfigurationPolicy::Scope) is based on org admin status. But current_account_practice_ids (used in ConnectorPolicy::Scope, unchanged path) now uses Organizations::Api::Get.organization_ids_for which is the new membership API.
This is a CRITICAL semantic difference: in the controller, index previously used ConnectorPolicy::Scope (which was based on all_current_organizations_ids = agenda + admin). After the change:
- index uses ConfigurationPolicy::Scope → only managed_organization_ids (org admin)
- destroy/update use base_scope (unrestricted, with explicit authorize for write)
- ConnectorPolicy::Scope now uses the new Organizations API but is still current_account_practice_ids
Let me verify the connector policy test cases - the agenda case is testing the ConnectorPolicy not the ConfigurationPolicy::Scope:
Tools used:
Bash:grep -rn "policy_scope.*ConnectorPolicy\|policy_scope.*ConfigurationPolicy" /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect --include="*.rb" 2>/dev/null | head -10
Tool results:
→ result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/app/controllers/admin/secretaryship_configurations_controller.rb:50: policy_scope(SecretaryshipConfiguration, policy_scope_class: V2::Admin::SecretaryshipConfigurationPolicy::Scope) /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/public/ti_connect/api/connector.rb:23: .policy_scope(@current_context, TiConnect::Connector, policy_class: TiConnect::ConnectorPolicy) /Users …(truncated, 2055 more chars)
Now I have everything I need. Let me also look quickly at the ti_workplace_configuration_page_test.rb:
Tools used:
Read:/Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/test/integration/configuration/ti_connect/ti_workplace_configuration_page_test.rb
Tool results:
→ result (ok): 1 require 'test_helper'
2
3 module Integration::Configuration::TiWorkplace
4 class TiWorkplaceConfigurationPageTest < Doctolib::Testing::Legacy::ActionDispatch::SystemTestCase
5 include ToastsTestHelper
6
7 self.subdomain = :pro
8
9 let!(:organization) { create(:organization, country: Country::DE) }
10 let!(:practice) { create(:practice, :de, formal_name: 'Practice A', organization: organization) }
11 let!(:account) { create(:hcp_account, :admin, for_organizations: [orga
…(truncated, 7839 more chars)
I have enough information now. Let me deliver the analysis.
Verdict (3-5 lines)
Coverage is moderate-to-good for behavioural happy paths, but insufficient for the new ConfigurationPolicy::Scope it introduces. The new Scope class is exercised only via controller integration tests; there is no direct policy-level test, no negative test for non-admin doctors with agenda-only access (which is the exact semantic divergence between ConnectorPolicy::Scope and ConfigurationPolicy::Scope), and the dual-method split (current_account_practice_ids via Organizations API vs current_account_managed_practice_ids via OrganizationAdmin) is not covered as two distinct branches. Tests rely on real DB integration (good, matches Doctolib policy preference), but the discriminating behaviour of the refactor is silently untested.
Critical Gaps (rating 8-10)
file: /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/configuration_policy_test.rb:43
issue (critical, rating 9): No test for the new ConfigurationPolicy::Scope — The PR introduces class Scope < TiConnect::ApplicationScope inside ConfigurationPolicy (configuration_policy.rb:6-8), backed by the brand-new current_account_managed_practice_ids helper (application_scope.rb:9-11). This file ends at line 42 and tests only permit(account, connector) for :write. The Scope class is invoked from two controllers (organizations/connector_configurations_controller.rb:12 and practices/connector_configurations_controller.rb:26) but never asserted directly. A direct policy-scope test mirroring connector_policy_test.rb's structure would catch a regression where, e.g., the scope returns connectors for organizations the doctor only has agenda access in (which would be an authorization escalation).
suggestion (test): Add test "TiConnect::ConfigurationPolicyTest > scope > returns empty when doctor has only agenda access (no admin)" — Critical because this is the exact semantic difference vs ConnectorPolicy::Scope. ConnectorPolicy uses current_account_practice_ids (membership API → wider) while ConfigurationPolicy uses current_account_managed_practice_ids (admin-only → narrower). Without this test, swapping one back to the other would be silent.
suggestion (test): Add test "TiConnect::ConfigurationPolicyTest > scope > returns connectors only for organizations where account is admin" — Mirror of connector_policy_test.rb:35-43 but for the new Scope. Should also include the multi-practice-per-org case (lines 45-54 of connector_policy_test.rb).
suggestion (test): Add test "TiConnect::ConfigurationPolicyTest > scope > excludes connectors from other organizations the account is admin of when querying via .where(practice_id:)" — Directly exercises the cross-org isolation that the production code relies on (organizations/connector_configurations_controller.rb:9-13).
file: /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/policies/ti_connect/connector_policy_test.rb:33
issue (rating 8): The "agenda authorization" branch no longer asserts the new behavioural contract — Line 22 now adds Organizations::Api::Manage.add_member(...) next to the agenda creation. The test passes today but it doesn't separate the two preconditions: agenda + membership are now both required (or only membership matters?). If the add_member line is removed by accident, does the test still pass? If yes, the test is over-permissive; if no, then membership alone (without AgendaAccess::Api::Manage.create!) should also be tested.
suggestion (test): Add test "scope > when doctor is only a member (no agenda, no admin) of practice's organization > returns scope for connectors" — Because current_account_practice_ids was switched from account.all_current_organizations_ids (agenda + admin) to Organizations::Api::Get.organization_ids_for(account_id) (membership table). This is a semantic widening: a member who is neither agenda-authorized nor admin now gets the scope. That is either intentional or a bug — currently no test pins the answer.
suggestion (test): Add test "scope > when doctor has agenda access but is NOT a member of the organization > returns empty scope" — Tests the converse direction. Today the test at line 19-32 adds both membership and agenda access, so we cannot tell whether the agenda branch is still needed or merely incidental.
file: /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/controllers/ti_connect/organizations/connector_configurations_controller_test.rb:33
issue (rating 8): #index has no negative test for "admin of a different organization" — The test on line 22-31 ("when user is not an admin of the orga of the practice") asserts an empty parsed_body for an unauthorized user, but never tests cross-org leakage: a doctor who is admin of organization B requesting organization_id=A should get an empty list. With the refactor, index now does policy_scope(...).where(practice_id: practice_ids_of_A) — if current_account_managed_practice_ids accidentally returned IDs across all admin orgs (it does), the .where filters correctly only because of the AND. A test pinning that intent is needed.
suggestion (test): Add test "#index > when user is admin of a different organization > returns empty array (no cross-org leak)" — Critical: prevents data leakage if the controller's .where(practice_id: practice_ids) filter is ever removed under refactor. Concrete scenario: create org A with connector, create org B; admin doctor on B requests ?organization_id=A.id → expect empty.
Important Improvements (rating 5-7)
file: /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/app/policies/ti_connect/application_scope.rb:13
suggestion (test, rating 6): No coverage for with_preloaded_associations(scope) — The helper exists to avoid cross-database joins (per the comment), but no test asserts that calling .preload is performed. A direct policy-scope test that does assert_predicate scope.first.association(:deprecated_practice), :loaded? would catch a regression where preload is silently dropped or replaced with joins (which would re-introduce the cross-database join failure the comment warns about).
file: /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/controllers/ti_connect/organizations/connector_configurations_controller_test.rb:7
issue (rating 6): Heavy reliance on Organization.sole / .agendas.sole / patient_base.sole — These calls assume exactly one Organization/agenda/patient_base exist in the DB at test time. This couples the test to side-effects of the :hcp_account, :doctor factory chain. If a future factory change creates a second auxiliary organization (e.g., for support), every test in this file breaks at setup. Brittleness rating: 6.
suggestion: Replace .sole calls with explicit creation: organization = create(:organization, country: Country::DE); doctor_account = create(:hcp_account, :doctor, organizations: [organization], ...) — Or align with practices/connector_configurations_controller_test.rb:7-11 which uses an explicit create(:organization, ...).
file: /Users/USER/workspace/doctolib/worktrees/doctolib/LION-1307-ti-connect/engines/ti_connect/test/controllers/ti_connect/organizations/connector_configurations_controller_test.rb:117
suggestion (test, rating 7): #destroy and #update have no test for "admin of one org, attempting to delete connector from another org" — The refactor moved #destroy and #update from scoped_connectors.find(id) to base_scope.find(id) + explicit authorize (controller lines 102, 118). The change is meaningful: previously a wrong-org admin would get 404 (record not in scope), now they get 403 (record foun
…(truncated, 7180 more chars)