@@ -7,8 +7,12 @@ vi.unmock('@/blocks/registry')
77
88import * as blocksBarrel from '@/blocks'
99import { getAllBlocks , getBlock as getRealBlock } from '@/blocks/registry'
10- import { buildSelectorContextFromBlock , SELECTOR_CONTEXT_FIELDS } from './context'
11- import { buildCanonicalIndex , isCanonicalPair } from './visibility'
10+ import {
11+ buildSelectorContextFromBlock ,
12+ getSelectorContextSubBlocks ,
13+ SELECTOR_CONTEXT_FIELDS ,
14+ } from './context'
15+ import { buildCanonicalIndex , isCanonicalPair , resolveDependencyValue } from './visibility'
1216
1317/**
1418 * Under `isolate: false` the module under test may already be cached from an
@@ -23,6 +27,10 @@ afterAll(() => {
2327 getBlockSpy . mockRestore ( )
2428} )
2529
30+ function subBlocksFromValues ( values : Record < string , unknown > ) : Record < string , { value : unknown } > {
31+ return Object . fromEntries ( Object . entries ( values ) . map ( ( [ id , value ] ) => [ id , { value } ] ) )
32+ }
33+
2634describe ( 'buildSelectorContextFromBlock' , ( ) => {
2735 it ( 'should extract knowledgeBaseId from knowledgeBaseSelector via canonical mapping' , ( ) => {
2836 const ctx = buildSelectorContextFromBlock ( 'knowledge' , {
@@ -152,6 +160,67 @@ describe('buildSelectorContextFromBlock', () => {
152160 ) . toBe ( 'advanced-team' )
153161 } )
154162
163+ it ( 'preserves Gmail action credential resolution in basic and advanced modes' , ( ) => {
164+ const subBlocks = subBlocksFromValues ( {
165+ credential : 'action-basic' ,
166+ manualCredential : 'action-advanced' ,
167+ } )
168+
169+ expect ( buildSelectorContextFromBlock ( 'gmail' , subBlocks ) . oauthCredential ) . toBe ( 'action-basic' )
170+ expect (
171+ buildSelectorContextFromBlock ( 'gmail' , subBlocks , {
172+ canonicalModes : { oauthCredential : 'advanced' } ,
173+ } ) . oauthCredential
174+ ) . toBe ( 'action-advanced' )
175+ expect (
176+ buildSelectorContextFromBlock (
177+ 'gmail' ,
178+ subBlocksFromValues ( { credential : '' , triggerCredentials : 'dormant-trigger' } )
179+ ) . oauthCredential
180+ ) . toBeUndefined ( )
181+ } )
182+
183+ it ( 'uses trigger credentials with and without canonical metadata after action conversion' , ( ) => {
184+ const clickupValues = {
185+ selectedTriggerId : 'clickup_task_created' ,
186+ credential : 'dormant-action' ,
187+ triggerCredentials : 'active-trigger' ,
188+ }
189+ const cases = [
190+ { blockType : 'clickup' , values : clickupValues } ,
191+ {
192+ blockType : 'airtable' ,
193+ values : { credential : 'dormant-action' , triggerCredentials : 'active-trigger' } ,
194+ } ,
195+ ]
196+
197+ for ( const { blockType, values } of cases ) {
198+ expect (
199+ buildSelectorContextFromBlock ( blockType , subBlocksFromValues ( values ) , {
200+ triggerMode : true ,
201+ } ) . oauthCredential
202+ ) . toBe ( 'active-trigger' )
203+ }
204+
205+ const clickupConfig = getRealBlock ( 'clickup' )
206+ const triggerCanonicalIndex = buildCanonicalIndex (
207+ getSelectorContextSubBlocks ( clickupConfig ?. subBlocks ?? [ ] , clickupValues , true )
208+ )
209+ expect ( resolveDependencyValue ( 'triggerCredentials' , clickupValues , triggerCanonicalIndex ) ) . toBe (
210+ 'active-trigger'
211+ )
212+ } )
213+
214+ it ( 'does not leak a dormant action credential when an unmapped trigger credential is blank' , ( ) => {
215+ const ctx = buildSelectorContextFromBlock (
216+ 'airtable' ,
217+ subBlocksFromValues ( { credential : 'dormant-action' , triggerCredentials : '' } ) ,
218+ { triggerMode : true }
219+ )
220+
221+ expect ( ctx . oauthCredential ) . toBeUndefined ( )
222+ } )
223+
155224 it ( 'should ignore subblock keys not in SELECTOR_CONTEXT_FIELDS' , ( ) => {
156225 const ctx = buildSelectorContextFromBlock ( 'knowledge' , {
157226 operation : { id : 'operation' , type : 'dropdown' , value : 'search' } ,
0 commit comments