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  });