import { jest } from '@jest/globals'; import { HttpClient, RequestResponseError, GraphqlError, GraphqlAggregateError } from '../../dist/index.js'; import * as fetcher from '@cloudpss/fetch'; import { fetch, Response } from '@cloudpss/fetch'; describe('gql basic', () => { const headers = { 'content-type': 'application/json' }; const mockFetch = jest.fn(fetch); const client = new HttpClient({ token: 'xx.yy.zz', altTokens: ['token1.x.y', 'token2.x.y'], fetcher: { ...fetcher, fetch: mockFetch, }, dev: true, }); it('query bad response malformed json', async () => { mockFetch.mockResolvedValueOnce(new Response('NOT JSON', { headers })); const response = client.gql('xx'); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.not.toBeInstanceOf(GraphqlError); await expect(response).rejects.toHaveProperty('status', 200); await expect(response).rejects.toHaveProperty('message', `Unexpected token 'N', "NOT JSON" is not valid JSON`); }); it('query 500', async () => { const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { status: 500, headers })); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.not.toBeInstanceOf(GraphqlError); await expect(response).rejects.toHaveProperty('status', 500); }); it('query server error', async () => { const expectedResponse = { error: 'NOTFOUND', message: 'xx', statusCode: 60001 }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { status: 500, headers })); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.not.toBeInstanceOf(GraphqlError); await expect(response).rejects.toHaveProperty('status', 500); await expect(response).rejects.toHaveProperty('name', `NOTFOUND(60001)`); await expect(response).rejects.toHaveProperty('message', `xx`); }); it('query bad response error', async () => { const expectedResponse = { errors: [1] }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql('xx'); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toThrow(`Invalid response payload, 'errors[0]' should be an object`); await expect(response).rejects.not.toBeInstanceOf(GraphqlError); }); it('query bad response fields', async () => { const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql('xx'); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toThrow( `Invalid response payload, 'data' should be presented while 'errors' is empty`, ); await expect(response).rejects.not.toBeInstanceOf(GraphqlError); expect(mockFetch).toHaveBeenCalledWith( 'https://cloudpss.net/graphql', expect.objectContaining({ method: 'POST', headers: expect.hasHeaders({ authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', 'x-compressed-size': '23', 'x-raw-size': '14', }), body: expect.isArrayBuffer(JSON.stringify({ query: 'xx' })), }), ); }); it('query data', async () => { const expectedResponse = { data: { x: 'TEST' } }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = await client.gql('xx'); expect(response.data).toEqual(expectedResponse.data); }); it('query error', async () => { const expectedResponse = { errors: [{ x: 'TEST' }] }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql('xx'); await expect(response).rejects.toBeInstanceOf(GraphqlError); await expect(response).rejects.toEqual(expect.objectContaining(expectedResponse.errors[0])); }); it('query errors', async () => { const expectedResponse = { errors: [{ x: 'TEST' }, { y: 'TEST2' }] }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql('xx'); await expect(response).rejects.toBeInstanceOf(GraphqlAggregateError); await expect(response).rejects.toEqual(expect.objectContaining(expectedResponse.errors[0])); await expect(response).rejects.toHaveProperty( 'errors', expectedResponse.errors.map((err) => expect.objectContaining(err) as unknown), ); }); it('query errors data', async () => { const expectedResponse = { data: { res: 0 }, errors: [{ x: 'TEST' }, { y: 'TEST2' }], }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = await client.gql('xx'); expect(response.data).toEqual(expectedResponse.data); expect(response.errors).toEqual(expectedResponse.errors.map((err) => expect.objectContaining(err) as unknown)); expect(response.extensions).toBeUndefined(); }); it('query ext data', async () => { const expectedResponse = { data: { res: 0 }, extensions: [{ x: 'TEST' }, { y: 'TEST2' }], }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = await client.gql('xx'); expect(response.data).toEqual(expectedResponse.data); expect(response.errors).toBeUndefined(); expect(response.extensions).toEqual(expectedResponse.extensions); }); it('query templates bad response type', async () => { const expectedResponse = 'x'; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql.query`xx`; await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toThrow(`Invalid response data, response body is not an object`); await expect(response).rejects.not.toBeInstanceOf(GraphqlError); expect(mockFetch).toHaveBeenCalledWith( 'https://cloudpss.net/graphql', expect.objectContaining({ method: 'POST', body: expect.isArrayBuffer(JSON.stringify({ query: 'query{xx}' })), }), ); }); it('mutation templates bad response errors', async () => { const expectedResponse = { errors: {} }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql.mutation`xx`; await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toThrow(`Invalid response payload, 'errors' should be an array`); await expect(response).rejects.not.toBeInstanceOf(GraphqlError); expect(mockFetch).toHaveBeenCalledWith( 'https://cloudpss.net/graphql', expect.objectContaining({ method: 'POST', body: expect.isArrayBuffer(JSON.stringify({ query: 'mutation{xx}' })), }), ); }); it('query with prod', async () => { const client = new HttpClient({ token: 'xx.yy.zz', altTokens: ['token1.x.y', 'token2.x.y'], fetcher: { ...fetcher, fetch: mockFetch, }, dev: false, }); const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql('xx'); await expect(response).rejects.toThrow(); expect(mockFetch).toHaveBeenCalledWith( 'https://cloudpss.net/graphql', expect.objectContaining({ method: 'POST', headers: expect.hasHeaders({ authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', 'content-type': 'application/ubjson+zstd', }), body: expect.any(Blob) as Blob, }), ); }); it('query with additional headers', async () => { const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = client.gql('xx', { headers: { x: 'test' } }); await expect(response).rejects.toThrow(); expect(mockFetch).toHaveBeenCalledWith( 'https://cloudpss.net/graphql', expect.objectContaining({ method: 'POST', headers: expect.hasHeaders({ authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', 'content-type': 'application/json;charset=utf-8', x: 'test', }), body: expect.any(String) as string, }), ); }); });