|
| 1 | +import { GraphQLQueryPurifier } from '..'; |
| 2 | +import { Request, Response, NextFunction } from 'express'; |
| 3 | +import fs from 'fs'; |
| 4 | +import glob from 'glob'; |
| 5 | + |
| 6 | +jest.mock('fs'); |
| 7 | +jest.mock('glob'); |
| 8 | + |
| 9 | +describe('GraphQLQueryPurifier', () => { |
| 10 | + let purifier: GraphQLQueryPurifier; |
| 11 | + const gqlPath = './graphql/queries'; |
| 12 | + const mockReq = {} as Request; |
| 13 | + const mockRes = {} as Response; |
| 14 | + const mockNext = jest.fn() as NextFunction; |
| 15 | + |
| 16 | + beforeEach(() => { |
| 17 | + purifier = new GraphQLQueryPurifier({ gqlPath, debug: true }); |
| 18 | + mockReq.body = {}; |
| 19 | + mockRes.status = jest.fn().mockReturnThis(); |
| 20 | + mockRes.send = jest.fn(); |
| 21 | + |
| 22 | + // Mock the necessary functions |
| 23 | + (fs.watch as jest.Mock).mockImplementation((path, options, callback) => { |
| 24 | + // Simulate file change |
| 25 | + callback('change', 'test.gql'); |
| 26 | + return { close: jest.fn() }; |
| 27 | + }); |
| 28 | + |
| 29 | + (fs.readFileSync as jest.Mock).mockReturnValue(` |
| 30 | + query getUser { |
| 31 | + user { |
| 32 | + id |
| 33 | + name |
| 34 | + } |
| 35 | + } |
| 36 | + `); |
| 37 | + |
| 38 | + (glob.sync as jest.Mock).mockReturnValue(['./graphql/queries/test.gql']); |
| 39 | + }); |
| 40 | + |
| 41 | + afterEach(() => { |
| 42 | + jest.clearAllMocks(); |
| 43 | + }); |
| 44 | + |
| 45 | + test('should allow all queries if allowAll is true', () => { |
| 46 | + purifier = new GraphQLQueryPurifier({ gqlPath, allowAll: true }); |
| 47 | + purifier.filter(mockReq, mockRes, mockNext); |
| 48 | + expect(mockNext).toHaveBeenCalled(); |
| 49 | + }); |
| 50 | + |
| 51 | + test('should allow Apollo Studio introspection if allowStudio is true', () => { |
| 52 | + purifier = new GraphQLQueryPurifier({ gqlPath, allowStudio: true }); |
| 53 | + mockReq.body = { operationName: 'IntrospectionQuery' }; |
| 54 | + purifier.filter(mockReq, mockRes, mockNext); |
| 55 | + expect(mockNext).toHaveBeenCalled(); |
| 56 | + }); |
| 57 | + |
| 58 | + test('should block Apollo Studio introspection if allowStudio is false', () => { |
| 59 | + purifier = new GraphQLQueryPurifier({ gqlPath, allowStudio: false }); |
| 60 | + mockReq.body = { operationName: 'IntrospectionQuery' }; |
| 61 | + purifier.filter(mockReq, mockRes, mockNext); |
| 62 | + expect(mockRes.status).toHaveBeenCalledWith(403); |
| 63 | + expect(mockRes.send).toHaveBeenCalledWith( |
| 64 | + 'Access to studio is disabled by GraphQLQueryPurifier, pass { allowStudio: true }' |
| 65 | + ); |
| 66 | + }); |
| 67 | + |
| 68 | + test('should block queries not in the allowed list', () => { |
| 69 | + purifier = new GraphQLQueryPurifier({ gqlPath }); |
| 70 | + purifier['queryMap'] = {}; |
| 71 | + mockReq.body = { query: '{ user { id, name, email } }' }; |
| 72 | + purifier.filter(mockReq, mockRes, mockNext); |
| 73 | + expect(mockReq.body.query).toBe('{ __typename }'); |
| 74 | + expect(mockNext).toHaveBeenCalled(); |
| 75 | + }); |
| 76 | + |
| 77 | + test('should log and block queries that result in empty filtered queries', () => { |
| 78 | + purifier = new GraphQLQueryPurifier({ gqlPath, debug: true }); |
| 79 | + purifier['queryMap'] = { |
| 80 | + 'getUser.user': '{ user { id } }', |
| 81 | + }; |
| 82 | + mockReq.body = { query: '{ user { name } }' }; |
| 83 | + purifier.filter(mockReq, mockRes, mockNext); |
| 84 | + expect(mockReq.body.query).toBe('{ __typename }'); |
| 85 | + expect(mockNext).toHaveBeenCalled(); |
| 86 | + }); |
| 87 | + |
| 88 | + test('should handle empty request query', () => { |
| 89 | + purifier = new GraphQLQueryPurifier({ gqlPath }); |
| 90 | + mockReq.body = { query: '' }; |
| 91 | + purifier.filter(mockReq, mockRes, mockNext); |
| 92 | + expect(mockReq.body.query).toBe(''); |
| 93 | + expect(mockNext).toHaveBeenCalled(); |
| 94 | + }); |
| 95 | + |
| 96 | + test('should handle request query with only whitespace', () => { |
| 97 | + purifier = new GraphQLQueryPurifier({ gqlPath }); |
| 98 | + mockReq.body = { query: ' ' }; |
| 99 | + purifier.filter(mockReq, mockRes, mockNext); |
| 100 | + expect(mockReq.body.query).toBe('{ __typename }'); |
| 101 | + expect(mockNext).toHaveBeenCalled(); |
| 102 | + }); |
| 103 | +}); |
0 commit comments