@@ -9,23 +9,18 @@ import {
99 organizationProviderMockFns ,
1010} from '@sim/testing/mocks/organization-provider.mock'
1111import { createRoot , type Root } from 'react-dom/client'
12- import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
12+ import { afterEach , beforeEach , expect , it , vi } from 'vitest'
13+ import type { OrganizationAccountsSettings } from '@/lib/api/contracts/organization-accounts'
14+ import { LiveMemberIntegrations } from '@/app/o/[organizationId]/integrations/live-member-integrations'
1315
14- const mocks = vi . hoisted ( ( ) => ( {
15- policies : vi . fn ( ) ,
16- secrets : vi . fn ( ) ,
17- saveSecrets : vi . fn ( ) ,
18- connect : vi . fn ( ) ,
19- reconnect : vi . fn ( ) ,
20- refetch : vi . fn ( ) ,
21- } ) )
16+ const mocks = vi . hoisted ( ( ) => ( { connect : vi . fn ( ) } ) )
2217vi . mock ( '@/hooks/queries/organization-accounts' , ( ) => organizationAccountsQueriesMock )
2318vi . mock ( '@/hooks/queries/organization-secrets' , ( ) => ( {
24- useOrganizationSecretSource : mocks . secrets ,
25- useConfigureOrganizationSecretSource : ( ) => ( { mutate : mocks . saveSecrets } ) ,
26- useRemoveOrganizationSecretSource : ( ) => ( { mutate : vi . fn ( ) } ) ,
19+ useOrganizationSecretSource : ( ) => ( { data : { source : null } } ) ,
20+ } ) )
21+ vi . mock ( '@/hooks/queries/search-integrations' , ( ) => ( {
22+ useSearchIntegrations : ( ) => ( { data : [ { connectorType : 'coda' , approved : true } ] } ) ,
2723} ) )
28- vi . mock ( '@/hooks/queries/search-integrations' , ( ) => ( { useSearchIntegrations : mocks . policies } ) )
2924vi . mock ( '@/app/o/[organizationId]/providers/organization-provider' , ( ) => organizationProviderMock )
3025vi . mock ( '@/app/o/[organizationId]/integrations/disconnect-account-menu' , ( ) => ( {
3126 DisconnectAccountMenu : ( { accounts } : { accounts : { displayName : string } [ ] } ) => (
@@ -36,50 +31,16 @@ vi.mock('@/app/workspace/[workspaceId]/integrations/components/integrations-show
3631 IntegrationTile : ( ) => null ,
3732} ) )
3833
39- import { defaultLiveSearchPolicy } from '@/lib/sim-search/live/policy-schema'
40- import { LiveMemberIntegrations } from '@/app/o/[organizationId]/integrations/live-member-integrations'
41-
42- const mockInventory = organizationAccountsQueriesMockFns . mockUseOrganizationAccounts
43- organizationAccountsQueriesMockFns . mockUseConnectOrganizationAccount . mockReturnValue ( {
44- mutate : mocks . connect ,
45- } )
46- organizationAccountsQueriesMockFns . mockUseReconnectPersonalOrganizationAccount . mockReturnValue ( {
47- mutate : mocks . reconnect ,
48- } )
49- organizationProviderMockFns . mockUseOrganizationContext . mockReturnValue ( {
50- organization : { id : 'org' , name : 'Example Organization' } ,
51- } )
52-
5334let root : Root
5435let container : HTMLDivElement
55- const group = {
56- status : 'active' ,
57- options : [
58- {
59- id : 'github-option' ,
60- provider : 'github-repositories' ,
61- status : 'active' ,
62- configurationStatus : 'ready' ,
63- } ,
64- ] ,
65- mcpServers : [ ] ,
66- }
67- const inventory = ( overrides = { } ) => ( {
68- credentialGroup : group ,
69- availableMcpConnectors : [ ] ,
70- viewerAccounts : [ ] ,
71- viewerMcpAccounts : [ ] ,
72- canManage : false ,
73- ...overrides ,
74- } )
7536beforeEach ( ( ) => {
76- mocks . secrets . mockReturnValue ( { data : { source : null } } )
77- vi . stubGlobal ( 'IS_REACT_ACT_ENVIRONMENT' , true )
78- mockInventory . mockReturnValue ( { data : inventory ( ) , refetch : mocks . refetch } )
79- mocks . policies . mockReturnValue ( {
80- data : [ { connectorType : 'github' , approved : true } ] ,
81- refetch : mocks . refetch ,
37+ organizationAccountsQueriesMockFns . mockUseConnectOrganizationAccount . mockReturnValue ( {
38+ mutate : mocks . connect ,
39+ } )
40+ organizationProviderMockFns . mockUseOrganizationContext . mockReturnValue ( {
41+ organization : { id : 'org' , name : 'Example Organization' } ,
8242 } )
43+ vi . stubGlobal ( 'IS_REACT_ACT_ENVIRONMENT' , true )
8344 container = document . createElement ( 'div' )
8445 document . body . appendChild ( container )
8546 root = createRoot ( container )
@@ -88,151 +49,56 @@ afterEach(() => {
8849 act ( ( ) => root . unmount ( ) )
8950 container . remove ( )
9051} )
91- const render = async ( search = '' ) => {
92- await act ( async ( ) =>
93- root . render ( < LiveMemberIntegrations organizationId = 'org' search = { search } /> )
94- )
95- }
96- const button = ( label : string ) =>
97- [ ...container . querySelectorAll ( 'button' ) ] . find ( ( button ) => button . textContent === label )
98- describe ( 'live member connection states' , ( ) => {
99- it . each ( [ 'organization' , 'member' ] as const ) (
100- 'shows Generic Secrets in %s mode without OAuth sources' ,
101- async ( mode ) => {
102- mocks . policies . mockReturnValue ( { data : [ ] } )
103- mockInventory . mockReturnValue ( { data : inventory ( { credentialGroup : null } ) } )
104- mocks . secrets . mockReturnValue ( { data : { source : { id : 'source' , mode } } } )
105- await render ( )
106- expect ( container . textContent ) . toContain ( 'Generic Secrets' )
107- if ( mode === 'member' )
108- expect ( container . querySelector ( 'a' ) ?. getAttribute ( 'href' ) ) . toBe (
109- '/o/org/integrations/secrets'
110- )
111- else {
112- expect ( container . textContent ) . toContain ( 'Organization managed' )
113- expect ( container . querySelector ( 'a' ) ) . toBeNull ( )
114- }
115- expect ( button ( 'Configure' ) ) . toBeUndefined ( )
116- expect ( button ( 'Connect' ) ) . toBeUndefined ( )
117- await render ( 'unrelated' )
118- expect ( container . textContent ) . not . toContain ( 'Generic Secrets' )
119- }
120- )
121- it ( 'keeps GitLab organization-managed with no personal token or connect action' , async ( ) => {
122- mocks . policies . mockReturnValue ( { data : [ { connectorType : 'gitlab' , approved : true } ] } )
123- await render ( )
124- expect ( container . textContent ) . toContain ( 'Organization managed' )
125- expect ( container . textContent ) . not . toContain ( 'personal access token' )
126- expect ( button ( 'Connect' ) ) . toBeUndefined ( )
127- } )
128- it ( 'connects the active provider option in this organization' , async ( ) => {
129- await render ( )
130- await act ( async ( ) => button ( 'Connect' ) ! . click ( ) )
131- expect ( mocks . connect ) . toHaveBeenCalledWith (
132- { organizationId : 'org' , optionId : 'github-option' } ,
133- expect . any ( Object )
134- )
135- } )
136- it . each ( [
137- [ 'disabled' , { credentialGroup : { ...group , status : 'disabled' } } ] ,
138- [ 'unconfigured' , { credentialGroup : null } ] ,
139- ] ) ( 'disables connection when %s' , async ( _name , overrides ) => {
140- mockInventory . mockReturnValue ( { data : inventory ( overrides ) } )
141- await render ( )
142- expect ( button ( 'Connect' ) ?. disabled ) . toBe ( true )
143- await act ( async ( ) => button ( 'Connect' ) ! . click ( ) )
144- expect ( mocks . connect ) . not . toHaveBeenCalled ( )
145- } )
146- it ( 'shows the current account and reconnect action without enabling a disabled integration' , async ( ) => {
147- mockInventory . mockReturnValue ( {
148- data : inventory ( {
149- viewerAccounts : [
150- {
151- credentialId : 'own' ,
152- providerId : 'github-repositories' ,
153- displayName : 'reader' ,
154- status : 'needs_reauth' ,
155- } ,
156- ] ,
157- } ) ,
158- } )
159- await render ( )
160- await act ( async ( ) => button ( 'Reconnect' ) ! . click ( ) )
161- expect ( mocks . reconnect ) . toHaveBeenCalledWith ( 'own' , expect . any ( Object ) )
162- mocks . policies . mockReturnValue ( { data : [ { connectorType : 'github' , approved : false } ] } )
163- await render ( )
164- expect ( button ( 'Reconnect' ) ?. disabled ) . toBe ( true )
165- expect ( button ( 'Add account' ) ) . toBeUndefined ( )
166- } )
167- it ( 'never presents another MCP provider as a Coda connection' , async ( ) => {
168- mocks . policies . mockReturnValue ( { data : [ { connectorType : 'coda' , approved : true } ] } )
169- mockInventory . mockReturnValue ( {
170- data : inventory ( {
171- credentialGroup : {
172- ...group ,
173- mcpServers : [ { id : 'coda-server' , managedConnectorId : 'coda' , enabled : true } ] ,
174- } ,
175- viewerMcpAccounts : [
176- {
177- credentialId : 'other' ,
178- mcpServerId : 'other-server' ,
179- displayName : 'Other private account' ,
180- status : 'active' ,
181- } ,
182- ] ,
183- } ) ,
184- } )
185- await render ( )
186- expect ( container . textContent ) . not . toContain ( 'Other private account' )
187- await act ( async ( ) => button ( 'Connect' ) ! . click ( ) )
188- expect ( mocks . connect ) . toHaveBeenCalledWith (
189- { organizationId : 'org' , mcpServerId : 'coda-server' } ,
190- expect . any ( Object )
191- )
192- } )
193- it . each ( [ false , true ] ) (
194- 'keeps Slack app setup out of member Integrations, admin=%s' ,
195- async ( canManage ) => {
196- mocks . policies . mockReturnValue ( { data : [ { connectorType : 'slack' , approved : true } ] } )
197- mockInventory . mockReturnValue ( { data : inventory ( { canManage } ) } )
198- await render ( )
199- expect ( container . textContent ) . toContain ( 'Slack' )
200- expect ( container . textContent ) . toContain ( 'Not configured' )
201- expect ( container . textContent ) . not . toContain ( 'Finish setup' )
202- expect ( container . querySelector ( 'a' ) ) . toBeNull ( )
203- expect ( button ( 'Connect' ) ?. disabled ) . toBe ( true )
204- await act ( async ( ) => button ( 'Connect' ) ! . click ( ) )
205- expect ( mocks . connect ) . not . toHaveBeenCalled ( )
206- }
207- )
208- it ( 'explains service-account scope without hiding member connection controls' , async ( ) => {
209- mocks . policies . mockReturnValue ( {
210- data : [
52+ it ( 'never presents another MCP provider as a Coda connection' , async ( ) => {
53+ const inventory = {
54+ credentialGroup : {
55+ id : 'group' ,
56+ workspaceId : null ,
57+ organizationId : 'org' ,
58+ name : 'Organization accounts' ,
59+ description : null ,
60+ status : 'active' ,
61+ options : [ ] ,
62+ mcpServers : [
21163 {
212- connectorType : 'github' ,
213- approved : true ,
214- policy : {
215- ...defaultLiveSearchPolicy ( ) ,
216- accessMode : 'service_account' ,
217- sourceId : 'source' ,
218- } ,
64+ id : 'coda-server' ,
65+ name : 'Coda' ,
66+ description : null ,
67+ authType : 'oauth' ,
68+ managedConnectorId : 'coda' ,
69+ enabled : true ,
21970 } ,
22071 ] ,
221- } )
222- mockInventory . mockReturnValue ( {
223- data : inventory ( {
224- viewerAccounts : [
225- {
226- credentialId : 'own' ,
227- providerId : 'github-repositories' ,
228- displayName : 'reader' ,
229- status : 'active' ,
230- } ,
231- ] ,
232- } ) ,
233- } )
234- await render ( )
235- expect ( container . textContent ) . toContain ( 'Selected resources you can access' )
236- expect ( button ( 'Add account' ) ) . toBeDefined ( )
72+ createdAt : '2026-01-01T00:00:00.000Z' ,
73+ updatedAt : '2026-01-01T00:00:00.000Z' ,
74+ } ,
75+ availableProviders : [ ] ,
76+ assistantProviders : [ ] ,
77+ availableMcpConnectors : [ 'coda' ] ,
78+ viewerAccounts : [ ] ,
79+ viewerMcpAccounts : [
80+ {
81+ credentialId : 'other' ,
82+ mcpServerId : 'other-server' ,
83+ displayName : 'Other private account' ,
84+ status : 'active' ,
85+ } ,
86+ ] ,
87+ canManage : false ,
88+ indexingAvailable : true ,
89+ } satisfies OrganizationAccountsSettings
90+ organizationAccountsQueriesMockFns . mockUseOrganizationAccounts . mockReturnValue ( {
91+ data : inventory ,
23792 } )
93+ await act ( async ( ) => root . render ( < LiveMemberIntegrations organizationId = 'org' search = '' /> ) )
94+ expect ( container . textContent ) . not . toContain ( 'Other private account' )
95+ const connect = [ ...container . querySelectorAll ( 'button' ) ] . find (
96+ ( button ) => button . textContent === 'Connect'
97+ )
98+ if ( ! connect ) throw new Error ( 'Missing Coda connection control' )
99+ await act ( async ( ) => connect . click ( ) )
100+ expect ( mocks . connect ) . toHaveBeenCalledWith (
101+ { organizationId : 'org' , mcpServerId : 'coda-server' } ,
102+ expect . any ( Object )
103+ )
238104} )
0 commit comments