@@ -19,15 +19,21 @@ vi.mock("@/lib/keys/getApiKeyDetails", () => ({
1919 getApiKeyDetails : vi . fn ( ) ,
2020} ) ) ;
2121
22+ vi . mock ( "@/lib/organizations/validateOrganizationAccess" , ( ) => ( {
23+ validateOrganizationAccess : vi . fn ( ) ,
24+ } ) ) ;
25+
2226import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId" ;
2327import { getAuthenticatedAccountId } from "@/lib/auth/getAuthenticatedAccountId" ;
2428import { validateOverrideAccountId } from "@/lib/accounts/validateOverrideAccountId" ;
2529import { getApiKeyDetails } from "@/lib/keys/getApiKeyDetails" ;
30+ import { validateOrganizationAccess } from "@/lib/organizations/validateOrganizationAccess" ;
2631
2732const mockGetApiKeyAccountId = vi . mocked ( getApiKeyAccountId ) ;
2833const mockGetAuthenticatedAccountId = vi . mocked ( getAuthenticatedAccountId ) ;
2934const mockValidateOverrideAccountId = vi . mocked ( validateOverrideAccountId ) ;
3035const mockGetApiKeyDetails = vi . mocked ( getApiKeyDetails ) ;
36+ const mockValidateOrganizationAccess = vi . mocked ( validateOrganizationAccess ) ;
3137
3238// Helper to create mock NextRequest
3339function createMockRequest ( body : unknown , headers : Record < string , string > = { } ) : Request {
@@ -417,4 +423,127 @@ describe("validateChatRequest", () => {
417423 expect ( result . success ) . toBe ( false ) ;
418424 } ) ;
419425 } ) ;
426+
427+ describe ( "organizationId override" , ( ) => {
428+ it ( "accepts organizationId in schema" , ( ) => {
429+ const result = chatRequestSchema . safeParse ( {
430+ prompt : "test" ,
431+ organizationId : "org-123" ,
432+ } ) ;
433+ expect ( result . success ) . toBe ( true ) ;
434+ } ) ;
435+
436+ it ( "uses provided organizationId when user is member of org (bearer token)" , async ( ) => {
437+ mockGetAuthenticatedAccountId . mockResolvedValue ( "user-account-123" ) ;
438+ mockValidateOrganizationAccess . mockResolvedValue ( true ) ;
439+
440+ const request = createMockRequest (
441+ { prompt : "Hello" , organizationId : "org-456" } ,
442+ { authorization : "Bearer valid-jwt-token" } ,
443+ ) ;
444+
445+ const result = await validateChatRequest ( request as any ) ;
446+
447+ expect ( result ) . not . toBeInstanceOf ( NextResponse ) ;
448+ expect ( ( result as any ) . orgId ) . toBe ( "org-456" ) ;
449+ expect ( mockValidateOrganizationAccess ) . toHaveBeenCalledWith ( {
450+ accountId : "user-account-123" ,
451+ organizationId : "org-456" ,
452+ } ) ;
453+ } ) ;
454+
455+ it ( "uses provided organizationId when user is member of org (API key)" , async ( ) => {
456+ mockGetApiKeyAccountId . mockResolvedValue ( "api-key-account-123" ) ;
457+ mockGetApiKeyDetails . mockResolvedValue ( {
458+ accountId : "api-key-account-123" ,
459+ orgId : null ,
460+ } ) ;
461+ mockValidateOrganizationAccess . mockResolvedValue ( true ) ;
462+
463+ const request = createMockRequest (
464+ { prompt : "Hello" , organizationId : "org-789" } ,
465+ { "x-api-key" : "personal-api-key" } ,
466+ ) ;
467+
468+ const result = await validateChatRequest ( request as any ) ;
469+
470+ expect ( result ) . not . toBeInstanceOf ( NextResponse ) ;
471+ expect ( ( result as any ) . orgId ) . toBe ( "org-789" ) ;
472+ expect ( mockValidateOrganizationAccess ) . toHaveBeenCalledWith ( {
473+ accountId : "api-key-account-123" ,
474+ organizationId : "org-789" ,
475+ } ) ;
476+ } ) ;
477+
478+ it ( "overwrites API key orgId with provided organizationId when user is member" , async ( ) => {
479+ mockGetApiKeyAccountId . mockResolvedValue ( "org-account-123" ) ;
480+ mockGetApiKeyDetails . mockResolvedValue ( {
481+ accountId : "org-account-123" ,
482+ orgId : "original-org-123" ,
483+ } ) ;
484+ mockValidateOrganizationAccess . mockResolvedValue ( true ) ;
485+
486+ const request = createMockRequest (
487+ { prompt : "Hello" , organizationId : "different-org-456" } ,
488+ { "x-api-key" : "org-api-key" } ,
489+ ) ;
490+
491+ const result = await validateChatRequest ( request as any ) ;
492+
493+ expect ( result ) . not . toBeInstanceOf ( NextResponse ) ;
494+ expect ( ( result as any ) . orgId ) . toBe ( "different-org-456" ) ;
495+ } ) ;
496+
497+ it ( "rejects organizationId when user is NOT a member of org" , async ( ) => {
498+ mockGetAuthenticatedAccountId . mockResolvedValue ( "user-account-123" ) ;
499+ mockValidateOrganizationAccess . mockResolvedValue ( false ) ;
500+
501+ const request = createMockRequest (
502+ { prompt : "Hello" , organizationId : "org-not-member" } ,
503+ { authorization : "Bearer valid-jwt-token" } ,
504+ ) ;
505+
506+ const result = await validateChatRequest ( request as any ) ;
507+
508+ expect ( result ) . toBeInstanceOf ( NextResponse ) ;
509+ const json = await ( result as NextResponse ) . json ( ) ;
510+ expect ( json . status ) . toBe ( "error" ) ;
511+ expect ( json . message ) . toBe ( "Access denied to specified organizationId" ) ;
512+ } ) ;
513+
514+ it ( "uses API key orgId when no organizationId is provided" , async ( ) => {
515+ mockGetApiKeyAccountId . mockResolvedValue ( "org-account-123" ) ;
516+ mockGetApiKeyDetails . mockResolvedValue ( {
517+ accountId : "org-account-123" ,
518+ orgId : "api-key-org-123" ,
519+ } ) ;
520+
521+ const request = createMockRequest (
522+ { prompt : "Hello" } ,
523+ { "x-api-key" : "org-api-key" } ,
524+ ) ;
525+
526+ const result = await validateChatRequest ( request as any ) ;
527+
528+ expect ( result ) . not . toBeInstanceOf ( NextResponse ) ;
529+ expect ( ( result as any ) . orgId ) . toBe ( "api-key-org-123" ) ;
530+ // Should not validate org access when no organizationId is provided
531+ expect ( mockValidateOrganizationAccess ) . not . toHaveBeenCalled ( ) ;
532+ } ) ;
533+
534+ it ( "returns null orgId when no organizationId provided and bearer token auth" , async ( ) => {
535+ mockGetAuthenticatedAccountId . mockResolvedValue ( "user-account-123" ) ;
536+
537+ const request = createMockRequest (
538+ { prompt : "Hello" } ,
539+ { authorization : "Bearer valid-jwt-token" } ,
540+ ) ;
541+
542+ const result = await validateChatRequest ( request as any ) ;
543+
544+ expect ( result ) . not . toBeInstanceOf ( NextResponse ) ;
545+ expect ( ( result as any ) . orgId ) . toBeNull ( ) ;
546+ expect ( mockValidateOrganizationAccess ) . not . toHaveBeenCalled ( ) ;
547+ } ) ;
548+ } ) ;
420549} ) ;
0 commit comments