1- import { LuigiCoreService } from '@openmfp/portal-ui-lib' ;
2- import { ApolloFactory } from './apollo-factory' ;
3- import { GatewayService } from './gateway.service' ;
4- import { ResourceNodeContext } from './resource-node-context' ;
51import { NgZone } from '@angular/core' ;
62import { TestBed } from '@angular/core/testing' ;
7- import { InMemoryCache } from '@apollo/client/core' ;
3+ import { ApolloLink , InMemoryCache , execute } from '@apollo/client/core' ;
4+ import { parse } from 'graphql' ;
5+ import { LuigiCoreService } from '@openmfp/portal-ui-lib' ;
86import { Apollo } from 'apollo-angular' ;
97import { HttpLink } from 'apollo-angular/http' ;
8+ import { createClient } from 'graphql-sse' ;
109import { mock } from 'jest-mock-extended' ;
10+ import { ApolloFactory } from './apollo-factory' ;
11+ import { GatewayService } from './gateway.service' ;
12+ import { ResourceNodeContext } from './resource-node-context' ;
13+
14+ // Mock graphql-sse client to capture provided options
15+ jest . mock ( 'graphql-sse' , ( ) => ( {
16+ createClient : jest . fn ( ) ,
17+ } ) ) ;
1118
1219global . fetch = ( ...args ) =>
1320 // @ts -ignore
@@ -31,7 +38,7 @@ describe('ApolloFactory', () => {
3138 } ) ,
3239 getGlobalContext : jest . fn ( ) . mockReturnValue ( { token : 'fake-token' } ) ,
3340 } ;
34- gatewayServiceMock = mock ( ) ;
41+ gatewayServiceMock = mock < GatewayService > ( ) ;
3542 TestBed . configureTestingModule ( {
3643 providers : [
3744 ApolloFactory ,
@@ -56,4 +63,115 @@ describe('ApolloFactory', () => {
5663 const options = ( factory as any ) . createApolloOptions ( ) ;
5764 expect ( options . cache ) . toBeInstanceOf ( InMemoryCache ) ;
5865 } ) ;
66+
67+ it ( 'should create HttpLink with default options' , ( ) => {
68+ ( factory as any ) . createApolloOptions ( { token : 't' } as unknown as ResourceNodeContext ) ;
69+ expect ( httpLinkMock . create ) . toHaveBeenCalledWith ( { } ) ;
70+ } ) ;
71+
72+ it ( 'should configure SSE client with dynamic url and auth header' , ( ) => {
73+ // reset call count since previous tests may initialize SSE link too
74+ ( createClient as jest . Mock ) . mockClear ( ) ;
75+ const subscribeMock = jest . fn ( ) . mockReturnValue ( ( ) => void 0 ) ;
76+ ( createClient as jest . Mock ) . mockReturnValue ( { subscribe : subscribeMock } ) ;
77+
78+ const nodeContext : ResourceNodeContext = {
79+ token : 'fake-token' ,
80+ } as unknown as ResourceNodeContext ;
81+
82+ gatewayServiceMock . getGatewayUrl . mockReturnValue ( 'http://example.com/graphql' ) ;
83+
84+ ( factory as any ) . createApolloOptions ( nodeContext , false ) ;
85+
86+ expect ( createClient ) . toHaveBeenCalledTimes ( 1 ) ;
87+ const clientOptions = ( createClient as jest . Mock ) . mock . calls [ 0 ] [ 0 ] ;
88+
89+ expect ( typeof clientOptions . url ) . toBe ( 'function' ) ;
90+ expect ( typeof clientOptions . headers ) . toBe ( 'function' ) ;
91+
92+ // url() should call GatewayService.getGatewayUrl lazily
93+ expect ( gatewayServiceMock . getGatewayUrl ) . not . toHaveBeenCalled ( ) ;
94+ const resolvedUrl = clientOptions . url ( ) ;
95+ expect ( gatewayServiceMock . getGatewayUrl ) . toHaveBeenCalledWith ( nodeContext , false ) ;
96+ expect ( resolvedUrl ) . toBe ( 'http://example.com/graphql' ) ;
97+
98+ // headers() should include bearer token
99+ const headers = clientOptions . headers ( ) ;
100+ expect ( headers ) . toEqual ( { Authorization : 'Bearer fake-token' } ) ;
101+ } ) ;
102+
103+ it ( 'should pass readFromParentKcpPath flag to SSE url resolver' , ( ) => {
104+ ( createClient as jest . Mock ) . mockClear ( ) ;
105+ const subscribeMock = jest . fn ( ) . mockReturnValue ( ( ) => void 0 ) ;
106+ ( createClient as jest . Mock ) . mockReturnValue ( { subscribe : subscribeMock } ) ;
107+
108+ const nodeContext : ResourceNodeContext = { token : 't' } as unknown as ResourceNodeContext ;
109+ gatewayServiceMock . getGatewayUrl . mockReturnValue ( 'http://gw/graphql' ) ;
110+
111+ ( factory as any ) . createApolloOptions ( nodeContext , true ) ;
112+
113+ const clientOptions = ( createClient as jest . Mock ) . mock . calls . at ( - 1 ) [ 0 ] ;
114+ clientOptions . url ( ) ;
115+ expect ( gatewayServiceMock . getGatewayUrl ) . toHaveBeenCalledWith ( nodeContext , true ) ;
116+ } ) ;
117+
118+ it ( 'should pass readFromParentKcpPath from apollo() to options builder' , ( ) => {
119+ const nodeContext = { token : 'x' } as unknown as ResourceNodeContext ;
120+ const spy = jest . spyOn < any , any > ( factory as any , 'createApolloOptions' ) ;
121+ factory . apollo ( nodeContext , true ) ;
122+ expect ( spy ) . toHaveBeenCalledWith ( nodeContext , true ) ;
123+ } ) ;
124+
125+ it ( 'should create a new Apollo instance per call' , ( ) => {
126+ const ctx = { token : 'a' } as unknown as ResourceNodeContext ;
127+ const a1 = factory . apollo ( ctx ) ;
128+ const a2 = factory . apollo ( ctx ) ;
129+ expect ( a1 ) . not . toBe ( a2 ) ;
130+ } ) ;
131+
132+ it ( 'should compose a valid ApolloLink chain' , ( ) => {
133+ const options = ( factory as any ) . createApolloOptions ( { token : 't' } as unknown as ResourceNodeContext ) ;
134+ expect ( options . link ) . toBeInstanceOf ( ApolloLink ) ;
135+ expect ( typeof ( options . link as ApolloLink ) . request ) . toBe ( 'function' ) ;
136+ } ) ;
137+
138+ it ( 'should not eagerly resolve gateway URL during options creation' , ( ) => {
139+ gatewayServiceMock . getGatewayUrl . mockClear ( ) ;
140+ ( factory as any ) . createApolloOptions ( { token : 't' } as unknown as ResourceNodeContext ) ;
141+ expect ( gatewayServiceMock . getGatewayUrl ) . not . toHaveBeenCalled ( ) ;
142+ } ) ;
143+
144+ it ( 'routes query operations without errors' , ( ) => {
145+ const httpReturnLink = new ApolloLink ( ( ) => ( { subscribe : jest . fn ( ) } as any ) ) ;
146+ httpLinkMock . create . mockReturnValue ( httpReturnLink as any ) ;
147+
148+ const nodeContext = {
149+ token : 't' ,
150+ portalContext : { crdGatewayApiUrl : 'http://x/:kcp/graphql' } ,
151+ } as unknown as ResourceNodeContext ;
152+
153+ const options = ( factory as any ) . createApolloOptions ( nodeContext , false ) ;
154+ const queryDoc = parse ( 'query Q { x }' ) ;
155+ const obs = execute ( options . link , { query : queryDoc } as any ) as any ;
156+ expect ( obs ) . toBeTruthy ( ) ;
157+ expect ( typeof obs . subscribe ) . toBe ( 'function' ) ;
158+ expect ( ( ) => obs . subscribe ( { } ) ) . not . toThrow ( ) ;
159+ } ) ;
160+
161+ it ( 'routes subscription operations without errors' , ( ) => {
162+ ( createClient as jest . Mock ) . mockClear ( ) ;
163+ ( createClient as jest . Mock ) . mockReturnValue ( { subscribe : jest . fn ( ) . mockReturnValue ( ( ) => void 0 ) } ) ;
164+
165+ const nodeContext = {
166+ token : 't' ,
167+ portalContext : { crdGatewayApiUrl : 'http://x/:kcp/graphql' } ,
168+ } as unknown as ResourceNodeContext ;
169+
170+ const options = ( factory as any ) . createApolloOptions ( nodeContext , false ) ;
171+ const subDoc = parse ( 'subscription S { x }' ) ;
172+ const obs = execute ( options . link , { query : subDoc } as any ) as any ;
173+ expect ( obs ) . toBeTruthy ( ) ;
174+ expect ( typeof obs . subscribe ) . toBe ( 'function' ) ;
175+ expect ( ( ) => obs . subscribe ( { } ) ) . not . toThrow ( ) ;
176+ } ) ;
59177} ) ;
0 commit comments