http-init-msw.test.ts 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
  2. import { http, HttpResponse } from 'msw';
  3. import { httpRequest, setupHttp } from '@/api/http-init';
  4. import { CSRF_TOKEN } from './msw/handlers';
  5. import { server } from './msw/server';
  6. const ORIGIN = 'http://localhost';
  7. describe('httpRequest against the MSW-mocked network', () => {
  8. beforeEach(() => {
  9. vi.stubGlobal('document', { querySelector: () => null });
  10. window.X_UI_BASE_PATH = ORIGIN;
  11. setupHttp();
  12. });
  13. afterEach(() => {
  14. vi.unstubAllGlobals();
  15. delete window.X_UI_BASE_PATH;
  16. });
  17. it('fetches a CSRF token, then refetches and retries once after a 403', async () => {
  18. let posts = 0;
  19. const sentTokens: (string | null)[] = [];
  20. server.use(
  21. http.get(`${ORIGIN}/csrf-token`, () => HttpResponse.json({ success: true, obj: CSRF_TOKEN })),
  22. http.post(`${ORIGIN}/panel/api/test`, ({ request }) => {
  23. posts += 1;
  24. sentTokens.push(request.headers.get('X-CSRF-Token'));
  25. if (posts === 1) return new HttpResponse(null, { status: 403 });
  26. return HttpResponse.json({ success: true, obj: 'saved' });
  27. }),
  28. );
  29. const res = await httpRequest('POST', '/panel/api/test', { hello: 'world' });
  30. expect(posts).toBe(2);
  31. expect(sentTokens).toEqual([CSRF_TOKEN, CSRF_TOKEN]);
  32. expect(res.status).toBe(200);
  33. expect(res.data).toEqual({ success: true, obj: 'saved' });
  34. });
  35. it('on a 403 refetches a fresh token from the server even when a stale meta tag is present', async () => {
  36. const STALE = 'stale-meta-token';
  37. const FRESH = 'fresh-server-token';
  38. vi.stubGlobal('document', {
  39. querySelector: (selector: string) =>
  40. selector === 'meta[name="csrf-token"]' ? { getAttribute: () => STALE } : null,
  41. });
  42. setupHttp();
  43. let posts = 0;
  44. const sentTokens: (string | null)[] = [];
  45. server.use(
  46. http.get(`${ORIGIN}/csrf-token`, () => HttpResponse.json({ success: true, obj: FRESH })),
  47. http.post(`${ORIGIN}/panel/api/test`, ({ request }) => {
  48. posts += 1;
  49. const token = request.headers.get('X-CSRF-Token');
  50. sentTokens.push(token);
  51. if (token !== FRESH) return new HttpResponse(null, { status: 403 });
  52. return HttpResponse.json({ success: true, obj: 'saved' });
  53. }),
  54. );
  55. const res = await httpRequest('POST', '/panel/api/test', { hello: 'world' });
  56. expect(sentTokens).toEqual([STALE, FRESH]);
  57. expect(posts).toBe(2);
  58. expect(res.status).toBe(200);
  59. });
  60. it('resolves a safe GET without requesting a CSRF token', async () => {
  61. let csrfHits = 0;
  62. server.use(
  63. http.get(`${ORIGIN}/csrf-token`, () => {
  64. csrfHits += 1;
  65. return HttpResponse.json({ success: true, obj: CSRF_TOKEN });
  66. }),
  67. http.get(`${ORIGIN}/panel/api/status`, () => HttpResponse.json({ success: true, obj: { up: true } })),
  68. );
  69. const res = await httpRequest('GET', '/panel/api/status');
  70. expect(csrfHits).toBe(0);
  71. expect(res.status).toBe(200);
  72. expect(res.data).toEqual({ success: true, obj: { up: true } });
  73. });
  74. });