github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/services/base.spec.ts (about) 1 import { Result } from '@webapp/util/fp'; 2 import path from 'path'; 3 import { ZodError } from 'zod'; 4 import { 5 request, 6 mountRequest, 7 RequestNotOkError, 8 RequestNotOkWithErrorsList, 9 ResponseOkNotInJSONFormat, 10 RequestIncompleteError, 11 ResponseNotOkInHTMLFormat, 12 } from './base'; 13 import { setupServer, rest } from './testUtils'; 14 import basename from '../util/baseurl'; 15 16 jest.mock('../util/baseurl', () => jest.fn()); 17 18 describe('Base HTTP', () => { 19 let server: ReturnType<typeof setupServer> | null; 20 21 afterEach(() => { 22 if (server) { 23 server.close(); 24 } 25 server = null; 26 }); 27 28 describe('Server responds', () => { 29 it('with valid JSON data', async () => { 30 server = setupServer( 31 rest.get(`http://localhost/test`, (req, res, ctx) => { 32 return res( 33 ctx.status(200), 34 ctx.json({ 35 foo: 'bar', 36 }) 37 ); 38 }) 39 ); 40 server.listen(); 41 const res = await request('/test'); 42 43 expect(res).toMatchObject( 44 Result.ok({ 45 foo: 'bar', 46 }) 47 ); 48 }); 49 50 it('with invalid JSON data', async () => { 51 server = setupServer( 52 rest.get(`http://localhost/test`, (req, res, ctx) => { 53 return res(ctx.status(200), ctx.text('bla')); 54 }) 55 ); 56 server.listen(); 57 const res = await request('/test'); 58 59 expect(res.error).toBeInstanceOf(ResponseOkNotInJSONFormat); 60 expect(res.error.message).toBe( 61 "Server returned with code: '200'. The body that could not be parsed contains 'bla'" 62 ); 63 }); 64 }); 65 66 describe('Server never responded', () => { 67 it('fails', async () => { 68 const res = await request('/test'); 69 70 expect(res.error).toBeInstanceOf(RequestIncompleteError); 71 expect(res.error.message).toBe( 72 "Request failed to be completed. Description: 'request to http://localhost/test failed, reason: connect ECONNREFUSED 127.0.0.1:80'" 73 ); 74 }); 75 }); 76 77 describe('Server responded with 2xx and data', () => { 78 it('works', () => { 79 server = setupServer( 80 rest.get(`http://localhost/test`, (req, res, ctx) => { 81 return res(ctx.status(200), ctx.json({})); 82 }) 83 ); 84 server.listen(); 85 }); 86 }); 87 88 describe('Server responded with statusCode outside 2xx range', () => { 89 it(`Returns a default message if theres no body`, async () => { 90 server = setupServer( 91 rest.get(`http://localhost/test`, (req, res, ctx) => { 92 return res(ctx.status(500)); 93 }) 94 ); 95 server.listen(); 96 97 const res = await request('/test'); 98 99 expect(res.error).toBeInstanceOf(RequestNotOkError); 100 expect(res.error.message).toBe( 101 "Request failed with statusCode: '500' and description: 'No description available'" 102 ); 103 }); 104 105 it(`Returns an error list if available`, async () => { 106 server = setupServer( 107 rest.get(`http://localhost/test`, (req, res, ctx) => { 108 return res( 109 ctx.status(500), 110 ctx.json({ 111 errors: ['error1', 'error2'], 112 }) 113 ); 114 }) 115 ); 116 server.listen(); 117 118 const res = await request('/test'); 119 120 expect(res.error).toBeInstanceOf(RequestNotOkWithErrorsList); 121 expect(res.error.message).toBe('Error(s) were found: "error1", "error2"'); 122 }); 123 124 it(`Returns an error message if available`, async () => { 125 server = setupServer( 126 rest.get(`http://localhost/test`, (req, res, ctx) => { 127 return res( 128 ctx.status(500), 129 ctx.json({ 130 error: 'error', 131 }) 132 ); 133 }) 134 ); 135 server.listen(); 136 137 const res = await request('/test'); 138 139 expect(res.error).toBeInstanceOf(RequestNotOkError); 140 expect(res.error.message).toBe( 141 "Request failed with statusCode: '500' and description: 'error'" 142 ); 143 }); 144 145 it(`Returns an error message if available`, async () => { 146 server = setupServer( 147 rest.get(`http://localhost/test`, (req, res, ctx) => { 148 return res( 149 ctx.status(500), 150 ctx.json({ 151 message: 'error', 152 }) 153 ); 154 }) 155 ); 156 server.listen(); 157 158 const res = await request('/test'); 159 160 expect(res.error).toBeInstanceOf(RequestNotOkError); 161 expect(res.error.message).toBe( 162 "Request failed with statusCode: '500' and description: 'error'" 163 ); 164 }); 165 166 it(`Returns a bunch of data`, async () => { 167 server = setupServer( 168 rest.get(`http://localhost/test`, (req, res, ctx) => { 169 return res( 170 ctx.status(500), 171 ctx.json({ 172 foo: 'foo', 173 bar: 'bar', 174 }) 175 ); 176 }) 177 ); 178 server.listen(); 179 180 const res = await request('/test'); 181 182 expect(res.error).toBeInstanceOf(RequestNotOkError); 183 expect(res.error.message).toBe( 184 // eslint-disable-next-line no-useless-escape 185 `Request failed with statusCode: '500' and description: 'Could not identify an error message. Payload is {\"foo\":\"foo\",\"bar\":\"bar\"}'` 186 ); 187 }); 188 189 it(`Returns the text body as message when there's response NOT in JSON format`, async () => { 190 server = setupServer( 191 rest.get(`http://localhost/test`, (req, res, ctx) => { 192 return res(ctx.status(500), ctx.text('text error')); 193 }) 194 ); 195 server.listen(); 196 197 const res = await request('/test'); 198 199 expect(res.error).toBeInstanceOf(RequestNotOkError); 200 expect(res.error.message).toBe( 201 "Request failed with statusCode: '500' and description: 'text error'" 202 ); 203 }); 204 205 it('Returns a generic message when respond with HTML data', async () => { 206 const htmlData = require('fs').readFileSync( 207 path.resolve(__dirname, './testdata/example.html'), 208 'utf8' 209 ); 210 211 server = setupServer( 212 rest.get(`http://localhost/test`, (req, res, ctx) => { 213 return res(ctx.status(500), ctx.text(htmlData)); 214 }) 215 ); 216 server.listen(); 217 const res = await request('/test'); 218 219 expect(res.error).toBeInstanceOf(ResponseNotOkInHTMLFormat); 220 expect(res.error.message).toBe( 221 "Server returned with code: '500'. The body contains an HTML page" 222 ); 223 }); 224 }); 225 }); 226 227 // Normally this wouldn't be tested 228 // But since implementation is complex enough 229 // It's better to expose and test it 230 // TODO test when req is an object 231 describe('mountRequest', () => { 232 describe('basename is set', () => { 233 it('prepends browserURL with basename', () => { 234 (basename as any).mockImplementationOnce(() => { 235 return '/pyroscope'; 236 }); 237 238 const got = mountRequest('my-request'); 239 expect(got).toBe('http://localhost/pyroscope/my-request'); 240 }); 241 }); 242 243 describe('basename is NOT set', () => { 244 it('returns the browser url', () => { 245 (basename as any).mockImplementationOnce(() => { 246 return null; 247 }); 248 249 const got = mountRequest('my-request'); 250 expect(got).toBe('http://localhost/my-request'); 251 }); 252 }); 253 });