import { jest } from '@jest/globals'; import { HttpClient, RequestResponseError, RequestError } from '../../dist/index.js'; import * as fetcher from '@cloudpss/fetch'; import { fetch, Response, FormData } from '@cloudpss/fetch'; describe('restful basic', () => { const mockFetch = jest.fn(fetch); const client = new HttpClient({ token: 'xx.yy.zz', altTokens: ['token1.x.y', 'token2.x.y'], fetcher: { ...fetcher, fetch: mockFetch, }, }); const headers = { 'content-type': 'application/json' }; it('should throw on bad url', async () => { await expect(client.restful('xx')).rejects.toThrow(`Url must starts with '/', 'http://' or 'https://'`); }); it('GET', async () => { const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = await client.restful('/xx'); expect(response.status).toEqual(200); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith( 'https://cloudpss.net/api/xx', expect.objectContaining({ method: 'GET', headers: expect.hasHeaders({ authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', }), }), ); }); it('GET 500', async () => { const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers, status: 500 })); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toHaveProperty('status', 500); await expect(response).rejects.toHaveProperty('data', expectedResponse); }); it('GET 404', async () => { const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers, status: 404 })); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toHaveProperty('status', 404); await expect(response).rejects.toHaveProperty('data', expectedResponse); }); it('GET error response', async () => { const expectedResponse = { error: 'NOTFOUND', message: 'xx', statusCode: 60001 }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers, status: 404 })); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toHaveProperty('status', 404); await expect(response).rejects.toHaveProperty('name', 'NOTFOUND(60001)'); await expect(response).rejects.toHaveProperty('message', 'xx'); await expect(response).rejects.toHaveProperty('data', expectedResponse); }); it('GET error response json', async () => { mockFetch.mockResolvedValueOnce(new Response('NOT JSON', { headers, status: 404 })); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toHaveProperty('status', 404); await expect(response).rejects.toHaveProperty('data', undefined); await expect(response).rejects.toHaveProperty('message', `Unexpected token 'N', "NOT JSON" is not valid JSON`); }); it('GET error response text', async () => { mockFetch.mockResolvedValueOnce( new Response('NOT JSON', { headers: { 'content-type': 'text/plain' }, status: 404 }), ); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toHaveProperty('status', 404); await expect(response).rejects.toHaveProperty('data', 'NOT JSON'); await expect(response).rejects.toHaveProperty('message', 'Server Error 404: '); expect(mockFetch).toHaveBeenCalledTimes(1); }); it('GET error request', async () => { mockFetch.mockRejectedValue(new Error('failed to fetch')); const response = client.restful(undefined); await expect(response).rejects.toBeInstanceOf(RequestError); await expect(response).rejects.not.toBeInstanceOf(RequestResponseError); await expect(response).rejects.toHaveProperty('requestUrl', 'https://cloudpss.net/api'); await expect(response).rejects.toHaveProperty('message', 'failed to fetch'); await expect(response).rejects.toHaveProperty('cause.message', 'failed to fetch'); await expect(response).rejects.toHaveProperty('cause', expect.any(Error)); expect(mockFetch).toHaveBeenCalledTimes(3); }); it('POST', async () => { const requestData = { req: 'REQ' }; const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce(Response.json(expectedResponse, { headers })); const response = await client.restful('http://xx.xx/test', requestData); expect(response.status).toEqual(200); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith('http://xx.xx/test', { 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', }), body: JSON.stringify(requestData), }); }); it('POST url', async () => { const requestData = new URLSearchParams('a=1&b=2'); const expectedResponse = new FormData(); mockFetch.mockResolvedValueOnce(new Response(expectedResponse, {})); const response = await client.restful('http://xx.xx/testurl', requestData); expect(response.status).toEqual(200); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith('http://xx.xx/testurl', { method: 'POST', headers: expect.hasHeaders({ authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', }), body: requestData, }); }); it('POST custom header', async () => { const requestData = { req: 'REQ' }; const expectedResponse = { test: 'TEST' }; mockFetch.mockResolvedValueOnce( Response.json(expectedResponse, { headers: { 'content-type': 'application/ld+json;charset=utf-8' } }), ); const response = await client.restful('/', requestData, { headers: { 'content-type': 'application/x-json', 'x-custom': 'custom header', }, }); expect(response.status).toEqual(200); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith( 'https://cloudpss.net/api/', 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/x-json', 'x-custom': 'custom header', }), body: JSON.stringify(requestData), }), ); }); it('PUT', async () => { const requestData = { req: 'REQ' }; const expectedResponse = ''; mockFetch.mockResolvedValueOnce(new Response('', { status: 201, headers: { 'content-length': '0' } })); const response = await client.restful('http://xx.xx/test', requestData, { method: 'PUT' }); expect(response.status).toEqual(201); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith( 'http://xx.xx/test', expect.objectContaining({ method: 'PUT', 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', }), body: JSON.stringify(requestData), }), ); }); it('PUT buffer', async () => { const requestData = new Uint8Array(10); const expectedResponse = new Uint8Array(10); mockFetch.mockResolvedValueOnce(new Response(expectedResponse, { status: 201 })); const response = await client.restful('http://xx.xx/test', requestData, { method: 'PUT' }); expect(response.status).toEqual(201); expect(new Uint8Array(response.data as Uint8Array)).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith('http://xx.xx/test', { method: 'PUT', headers: expect.hasHeaders({ authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', 'Content-Type': 'application/octet-stream', }), body: requestData, }); }); it('DELETE', async () => { const requestData = { req: 'REQ' }; const expectedResponse = undefined; mockFetch.mockResolvedValueOnce(new Response(undefined, { status: 204 })); const response = await client.restful('http://xx.xx/test', requestData, { method: 'DELETE' }); expect(response.status).toEqual(204); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith( 'http://xx.xx/test', expect.objectContaining({ method: 'DELETE', 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', }), body: JSON.stringify(requestData), }), ); }); it('DELETE string', async () => { const requestData = '{}'; const expectedResponse = undefined; mockFetch.mockResolvedValueOnce(new Response(undefined, { status: 204 })); const response = await client.restful('http://xx.xx/test', requestData, { method: 'DELETE', headers }); expect(response.status).toEqual(204); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith( 'http://xx.xx/test', expect.objectContaining({ method: 'DELETE', headers: expect.hasHeaders({ 'content-type': 'application/json', authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', }), body: requestData, }), ); }); it('DELETE form data', async () => { const requestData = new FormData(); globalThis.FormData = FormData; const expectedResponse = undefined; mockFetch.mockResolvedValueOnce(new Response(undefined, { status: 204 })); const response = await client.restful('http://xx.xx/test', requestData, { method: 'DELETE', headers }); expect(response.status).toEqual(204); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith( 'http://xx.xx/test', expect.objectContaining({ method: 'DELETE', headers: expect.hasHeaders({ 'content-type': 'application/json', authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', }), body: requestData, }), ); }); it('DELETE blob', async () => { const requestData = new Blob([]); globalThis.Blob = Blob; const expectedResponse = undefined; mockFetch.mockResolvedValueOnce(new Response(undefined, { status: 204 })); const response = await client.restful('http://xx.xx/test', requestData, { method: 'DELETE' }); expect(response.status).toEqual(204); expect(response.data).toEqual(expectedResponse); expect(mockFetch).toHaveBeenCalledWith( 'http://xx.xx/test', expect.objectContaining({ method: 'DELETE', headers: expect.hasHeaders({ 'content-type': 'application/octet-stream', authorization: 'Bearer xx.yy.zz', 'x-authorization': 'Bearer token1.x.y, Bearer token2.x.y', }), body: requestData, }), ); }); });