@@ -42,6 +42,8 @@ vi.mock('@/lib/embeddings', async () => ({
4242} ) )
4343
4444import { decryptApiKey } from '@/lib/api-key/crypto'
45+ import { resolveBillingAttribution } from '@/lib/billing/core/billing-attribution'
46+ import { knowledgeBaseServerTool } from '@/lib/copilot/tools/server/knowledge/knowledge-base'
4547import { encryptSecret } from '@/lib/core/security/encryption'
4648import {
4749 createKnowledgeAclFixtureIds ,
@@ -119,22 +121,58 @@ afterAll(async () => {
119121 await db . $client . end ( )
120122} )
121123
122- it . each ( [ input . apiKey , '{{GITLAB_PAT}}' ] ) (
123- 'creates, syncs, edits, and searches a workspace GitLab source using %s' ,
124- async ( apiKey ) => {
125- const { connector } = await createKnowledgeConnector . execute ( {
126- principal,
127- input : { ...input , apiKey } ,
128- } )
129- expect ( connector . accessMode ) . toBe ( 'workspace' )
130- expect ( JSON . stringify ( connector ) ) . not . toContain ( input . apiKey )
124+ it . each ( [
125+ { surface : 'application' , apiKey : input . apiKey } ,
126+ { surface : 'application' , apiKey : '{{GITLAB_PAT}}' } ,
127+ { surface : 'mothership' , apiKey : input . apiKey } ,
128+ { surface : 'mothership' , apiKey : '{{GITLAB_PAT}}' } ,
129+ ] ) (
130+ 'creates, syncs, edits, and searches a workspace GitLab source through $surface using $apiKey' ,
131+ async ( { surface, apiKey } ) => {
132+ let connectorId : string
133+ if ( surface === 'mothership' ) {
134+ const result = await knowledgeBaseServerTool . execute (
135+ {
136+ operation : 'add_connector' ,
137+ args : {
138+ knowledgeBaseId : ids . knowledgeBaseId ,
139+ connectorType : 'gitlab' ,
140+ apiKey,
141+ sourceConfig,
142+ } ,
143+ } ,
144+ {
145+ userId : ids . aliceId ,
146+ workspaceId : ids . workspaceId ,
147+ chatId : generateId ( ) ,
148+ executionId : generateId ( ) ,
149+ toolCallId : generateId ( ) ,
150+ copilotToolExecution : true ,
151+ billingAttribution : await resolveBillingAttribution ( {
152+ actorUserId : ids . aliceId ,
153+ workspaceId : ids . workspaceId ,
154+ } ) ,
155+ }
156+ )
157+ expect ( result . success , result . message ) . toBe ( true )
158+ expect ( JSON . stringify ( result ) ) . not . toContain ( input . apiKey )
159+ if ( typeof result . data ?. id !== 'string' ) throw new Error ( 'Expected a connector ID' )
160+ connectorId = result . data . id
161+ } else {
162+ const { connector } = await createKnowledgeConnector . execute ( {
163+ principal,
164+ input : { ...input , apiKey } ,
165+ } )
166+ expect ( JSON . stringify ( connector ) ) . not . toContain ( input . apiKey )
167+ connectorId = connector . id
168+ }
131169 await expect
132170 . poll (
133171 async ( ) => {
134172 const [ row ] = await db
135173 . select ( )
136174 . from ( knowledgeConnector )
137- . where ( eq ( knowledgeConnector . id , connector . id ) )
175+ . where ( eq ( knowledgeConnector . id , connectorId ) )
138176 return { status : row . status , error : row . lastSyncError , synced : Boolean ( row . lastSyncAt ) }
139177 } ,
140178 { timeout : 15000 }
@@ -143,20 +181,21 @@ it.each([input.apiKey, '{{GITLAB_PAT}}'])(
143181 const [ stored ] = await db
144182 . select ( )
145183 . from ( knowledgeConnector )
146- . where ( eq ( knowledgeConnector . id , connector . id ) )
184+ . where ( eq ( knowledgeConnector . id , connectorId ) )
185+ expect ( stored . accessMode ) . toBe ( 'workspace' )
147186 expect ( stored . syncIntervalMinutes ) . toBe ( 1440 )
148187 expect ( stored . encryptedApiKey ) . not . toBe ( input . apiKey )
149188 expect ( ( await decryptApiKey ( stored . encryptedApiKey ! ) ) . decrypted ) . toBe ( input . apiKey )
150189 expect (
151190 await db
152191 . select ( )
153192 . from ( knowledgeConnectorPermissionSnapshot )
154- . where ( eq ( knowledgeConnectorPermissionSnapshot . connectorId , connector . id ) )
193+ . where ( eq ( knowledgeConnectorPermissionSnapshot . connectorId , connectorId ) )
155194 ) . toEqual ( [ ] )
156195 const docs = await db
157196 . select ( )
158197 . from ( document )
159- . where ( and ( eq ( document . connectorId , connector . id ) , isNull ( document . deletedAt ) ) )
198+ . where ( and ( eq ( document . connectorId , connectorId ) , isNull ( document . deletedAt ) ) )
160199 expect ( docs ) . toHaveLength ( 1 )
161200 expect ( docs [ 0 ] . externalId ) . toBe ( 'file:orion.md' )
162201 expect ( docs [ 0 ] . acl ) . toEqual ( [ 'ws' ] )
@@ -198,7 +237,7 @@ it.each([input.apiKey, '{{GITLAB_PAT}}'])(
198237 await updateKnowledgeConnector . execute ( {
199238 principal,
200239 input : {
201- connectorId : connector . id ,
240+ connectorId,
202241 updates : { sourceConfig : { ...sourceConfig , ref : 'master' } } ,
203242 } ,
204243 } )
@@ -208,7 +247,7 @@ it.each([input.apiKey, '{{GITLAB_PAT}}'])(
208247 const [ row ] = await db
209248 . select ( )
210249 . from ( knowledgeConnector )
211- . where ( eq ( knowledgeConnector . id , connector . id ) )
250+ . where ( eq ( knowledgeConnector . id , connectorId ) )
212251 return {
213252 status : row . status ,
214253 error : row . lastSyncError ,
0 commit comments