@@ -332,9 +332,10 @@ describe('setupUser validation', () => {
332332 vi . unstubAllEnvs ( ) ;
333333 } ) ;
334334
335- it ( 'should throw error if LoadCodeAssist returns ineligible tiers and no current tier' , async ( ) => {
335+ it ( 'should throw error if LoadCodeAssist returns ineligible tiers, no current tier, and no allowed tiers ' , async ( ) => {
336336 mockLoad . mockResolvedValue ( {
337337 currentTier : null ,
338+ allowedTiers : [ ] ,
338339 ineligibleTiers : [
339340 {
340341 reasonMessage : 'User is not eligible' ,
@@ -350,6 +351,110 @@ describe('setupUser validation', () => {
350351 ) ;
351352 } ) ;
352353
354+ it ( 'should continue if LoadCodeAssist returns ineligible tiers but has allowed tiers' , async ( ) => {
355+ const mockOnboardUser = vi . fn ( ) . mockResolvedValue ( {
356+ done : true ,
357+ response : {
358+ cloudaicompanionProject : {
359+ id : 'server-project' ,
360+ } ,
361+ } ,
362+ } ) ;
363+ vi . mocked ( CodeAssistServer ) . mockImplementation (
364+ ( ) =>
365+ ( {
366+ loadCodeAssist : mockLoad ,
367+ onboardUser : mockOnboardUser ,
368+ } ) as unknown as CodeAssistServer ,
369+ ) ;
370+
371+ mockLoad . mockResolvedValue ( {
372+ currentTier : null ,
373+ allowedTiers : [ mockPaidTier ] ,
374+ ineligibleTiers : [
375+ {
376+ reasonMessage : 'Not eligible for free tier' ,
377+ reasonCode : 'INELIGIBLE_ACCOUNT' ,
378+ tierId : 'free-tier' ,
379+ tierName : 'free' ,
380+ } ,
381+ ] ,
382+ } ) ;
383+
384+ // Should not throw - should proceed to onboarding with the allowed tier
385+ const result = await setupUser ( { } as OAuth2Client ) ;
386+ expect ( result ) . toEqual ( {
387+ projectId : 'server-project' ,
388+ userTier : 'standard-tier' ,
389+ userTierName : 'paid' ,
390+ } ) ;
391+ expect ( mockOnboardUser ) . toHaveBeenCalled ( ) ;
392+ } ) ;
393+
394+ it ( 'should throw error when only ineligible tiers exist and allowedTiers is undefined' , async ( ) => {
395+ mockLoad . mockResolvedValue ( {
396+ currentTier : null ,
397+ allowedTiers : undefined ,
398+ ineligibleTiers : [
399+ {
400+ reasonMessage : 'User is not eligible' ,
401+ reasonCode : 'INELIGIBLE_ACCOUNT' ,
402+ tierId : 'standard-tier' ,
403+ tierName : 'standard' ,
404+ } ,
405+ ] ,
406+ } ) ;
407+
408+ await expect ( setupUser ( { } as OAuth2Client ) ) . rejects . toThrow (
409+ 'User is not eligible' ,
410+ ) ;
411+ } ) ;
412+
413+ it ( 'should throw ValidationRequiredError even if allowed tiers exist' , async ( ) => {
414+ mockLoad . mockResolvedValue ( {
415+ currentTier : null ,
416+ allowedTiers : [ mockPaidTier ] ,
417+ ineligibleTiers : [
418+ {
419+ reasonMessage : 'Please verify your account' ,
420+ reasonCode : 'VALIDATION_REQUIRED' ,
421+ tierId : 'free-tier' ,
422+ tierName : 'free' ,
423+ validationUrl : 'https://example.com/verify' ,
424+ } ,
425+ ] ,
426+ } ) ;
427+
428+ await expect ( setupUser ( { } as OAuth2Client ) ) . rejects . toThrow (
429+ ValidationRequiredError ,
430+ ) ;
431+ } ) ;
432+
433+ it ( 'should combine multiple ineligible tier messages when no allowed tiers' , async ( ) => {
434+ mockLoad . mockResolvedValue ( {
435+ currentTier : null ,
436+ allowedTiers : [ ] ,
437+ ineligibleTiers : [
438+ {
439+ reasonMessage : 'Not eligible for standard' ,
440+ reasonCode : 'INELIGIBLE_ACCOUNT' ,
441+ tierId : 'standard-tier' ,
442+ tierName : 'standard' ,
443+ } ,
444+ {
445+ reasonMessage : 'Not eligible for free' ,
446+ reasonCode : 'INELIGIBLE_ACCOUNT' ,
447+ tierId : 'free-tier' ,
448+ tierName : 'free' ,
449+ } ,
450+ ] ,
451+ } ) ;
452+
453+ await expect ( setupUser ( { } as OAuth2Client ) ) . rejects . toThrow (
454+ 'Not eligible for standard, Not eligible for free' ,
455+ ) ;
456+ } ) ;
457+
353458 it ( 'should retry if validation handler returns verify' , async ( ) => {
354459 // First call fails
355460 mockLoad . mockResolvedValueOnce ( {
0 commit comments