Created
November 1, 2024 11:27
-
-
Save brandonbryant12/52a951e9b2af2933b1dab925da892bac to your computer and use it in GitHub Desktop.
graphql-proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { GraphQLRelayService } from './GraphQLRelayService'; | |
import superagent, { Response } from 'superagent'; | |
import { jest } from '@jest/globals'; | |
jest.mock('superagent'); | |
interface TokenResponse { | |
body: { | |
access_token: string; | |
expires_in: number; | |
}; | |
} | |
interface GraphQLResponse { | |
body: { | |
data: { | |
hello: string; | |
}; | |
}; | |
} | |
describe('GraphQLRelayService', () => { | |
const clientId = 'test-client-id'; | |
const clientSecret = 'test-client-secret'; | |
let cache: any; | |
let service: GraphQLRelayService; | |
beforeEach(() => { | |
jest.clearAllMocks(); | |
cache = { | |
get: jest.fn(), | |
set: jest.fn(), | |
delete: jest.fn(), | |
}; | |
service = new GraphQLRelayService(clientId, clientSecret, cache); | |
}); | |
it('should retrieve access token from cache', async () => { | |
cache.get.mockResolvedValue('cached-token'); | |
const token = await (service as any)['retrieveAccessToken'](); | |
expect(token).toBe('cached-token'); | |
expect(cache.get).toHaveBeenCalledWith('access_token'); | |
}); | |
it('should fetch new access token when cache is empty', async () => { | |
cache.get.mockResolvedValue(undefined); | |
const mockPostResponse: TokenResponse = { | |
body: { | |
access_token: 'new-token', | |
expires_in: 3600, | |
}, | |
}; | |
const mockSend = jest.fn<() => Promise<TokenResponse>>().mockResolvedValue(mockPostResponse); | |
const mockType = jest.fn().mockReturnValue({ send: mockSend }); | |
const mockPost = jest.fn().mockReturnValue({ type: mockType }); | |
(superagent.post as jest.Mock).mockImplementation(mockPost); | |
const token = await (service as any)['retrieveAccessToken'](); | |
expect(token).toBe('new-token'); | |
expect(superagent.post).toHaveBeenCalledWith('https://oauth.provider/token'); | |
expect(cache.set).toHaveBeenCalledWith('access_token', 'new-token', { ttl: 3540 }); | |
}); | |
it('should forward GraphQL request with access token', async () => { | |
cache.get.mockResolvedValue('cached-token'); | |
const mockGraphQLResponse: GraphQLResponse = { | |
body: { data: { hello: 'world' } }, | |
}; | |
const mockSend = jest.fn<() => Promise<GraphQLResponse>>().mockResolvedValue(mockGraphQLResponse); | |
const mockSet = jest.fn().mockReturnValue({ send: mockSend }); | |
const mockPost = jest.fn().mockReturnValue({ set: mockSet }); | |
(superagent.post as jest.Mock).mockImplementation(mockPost); | |
const response = await service.forwardGraphqlRequest('{ hello }', {}); | |
expect(response).toEqual({ data: { hello: 'world' } }); | |
expect(superagent.post).toHaveBeenCalledWith('https://graphql.api/endpoint'); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// src/services/GraphQLRelayService.ts | |
import superagent from 'superagent'; | |
import { CacheClient } from '@backstage/backend-common'; | |
export class GraphQLRelayService { | |
private readonly clientId: string; | |
private readonly clientSecret: string; | |
private readonly cache: CacheClient; | |
private readonly tokenCacheKey = 'access_token'; | |
constructor(clientId: string, clientSecret: string, cache: CacheClient) { | |
this.clientId = clientId; | |
this.clientSecret = clientSecret; | |
this.cache = cache; | |
} | |
private async getCachedAccessToken(): Promise<string | undefined> { | |
const cachedToken = await this.cache.get<string>(this.tokenCacheKey); | |
return cachedToken; | |
} | |
private async fetchAccessToken(): Promise<{ token: string; expiresIn: number }> { | |
const response = await superagent | |
.post('https://oauth.provider/token') | |
.type('form') | |
.send({ | |
grant_type: 'client_credentials', | |
client_id: this.clientId, | |
client_secret: this.clientSecret, | |
}); | |
const { access_token, expires_in } = response.body; | |
return { token: access_token, expiresIn: expires_in }; | |
} | |
private async retrieveAccessToken(): Promise<string> { | |
let accessToken = await this.getCachedAccessToken(); | |
if (!accessToken) { | |
const { token, expiresIn } = await this.fetchAccessToken(); | |
accessToken = token; | |
const cacheExpiry = expiresIn - 60; | |
await this.cache.set(this.tokenCacheKey, accessToken, { ttl: cacheExpiry }); | |
} | |
return accessToken; | |
} | |
public async forwardGraphqlRequest(query: string, variables: any): Promise<any> { | |
const accessToken = await this.retrieveAccessToken(); | |
const response = await superagent | |
.post('https://graphql.api/endpoint') | |
.set('Authorization', `Bearer ${accessToken}`) | |
.send({ query, variables }); | |
return response.body; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment