@uxland/primary-shell 4.3.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/index.js +13397 -12875
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.umd.cjs +1748 -1506
  4. package/dist/index.umd.cjs.map +1 -1
  5. package/dist/primary/shell/src/UI/components/primaria-shell/primaria-shell.d.ts +4 -0
  6. package/dist/primary/shell/src/UI/components/quick-actions-menu/quick-actions-menu.d.ts +1 -0
  7. package/dist/primary/shell/src/UI/shared-components/index.d.ts +0 -1
  8. package/dist/primary/shell/src/api/api.d.ts +4 -2
  9. package/dist/primary/shell/src/api/http-client/http-client.d.ts +7 -1
  10. package/dist/primary/shell/src/api/interaction-service/confirmation-message.d.ts +3 -0
  11. package/dist/primary/shell/src/api/interaction-service/index.d.ts +1 -0
  12. package/dist/primary/shell/src/api/interaction-service/interaction-service-impl.d.ts +7 -0
  13. package/dist/primary/shell/src/api/interaction-service/interaction-service.d.ts +32 -0
  14. package/dist/primary/shell/src/api/notification-service/notification-service.d.ts +6 -0
  15. package/dist/primary/shell/src/api/notification-service/notification.service-impl.d.ts +9 -0
  16. package/dist/primary/shell/src/api/plugin-busy-manager/plugin-busy-list/component.d.ts +2 -4
  17. package/dist/primary/shell/src/api/plugin-busy-manager/plugin-busy-manager.d.ts +17 -1
  18. package/dist/primary/shell/src/api/region-manager/region-manager.d.ts +1 -0
  19. package/dist/primary/shell/src/events.d.ts +2 -0
  20. package/dist/primary/shell/src/features/bootstrapper.d.ts +1 -1
  21. package/dist/primary/shell/src/index.d.ts +1 -2
  22. package/dist/primary/shell/src/internal-plugins/activity-history/activity-history-item/export-to-pdf/export-pdf-modal/export-pdf-modal.d.ts +1 -5
  23. package/dist/primary/shell/src/internal-plugins/activity-history/localization.d.ts +0 -1
  24. package/dist/primary/shell/src/locales.d.ts +4 -0
  25. package/dist/style.css +1 -1
  26. package/package.json +5 -2
  27. package/src/UI/components/clinical-monitoring/styles.css +4 -2
  28. package/src/UI/components/primaria-shell/primaria-shell.ts +33 -7
  29. package/src/UI/components/primaria-shell/styles.css +15 -2
  30. package/src/UI/components/primaria-shell/template.ts +7 -3
  31. package/src/UI/components/quick-actions-menu/quick-actions-menu.ts +13 -3
  32. package/src/UI/components/shell-header/template.ts +1 -1
  33. package/src/UI/shared-components/index.ts +0 -1
  34. package/src/api/api.ts +12 -9
  35. package/src/api/http-client/http-client.test.ts +188 -76
  36. package/src/api/http-client/http-client.ts +62 -11
  37. package/src/api/interaction-service/confirmation-message.tsx +5 -0
  38. package/src/api/interaction-service/index.ts +1 -0
  39. package/src/api/interaction-service/interaction-service-impl.tsx +203 -0
  40. package/src/api/interaction-service/interaction-service.ts +46 -0
  41. package/src/api/interaction-service/modal-styles.css +69 -0
  42. package/src/api/notification-service/notification-service.ts +6 -0
  43. package/src/api/notification-service/notification.service-impl.ts +45 -0
  44. package/src/api/plugin-busy-manager/plugin-busy-list/component.ts +3 -4
  45. package/src/api/plugin-busy-manager/plugin-busy-list/template.ts +1 -1
  46. package/src/api/plugin-busy-manager/plugin-busy-manager.test.ts +93 -31
  47. package/src/api/plugin-busy-manager/plugin-busy-manager.ts +44 -1
  48. package/src/api/region-manager/region-manager.ts +5 -0
  49. package/src/disposer.ts +1 -1
  50. package/src/events.ts +2 -0
  51. package/src/features/bootstrapper.ts +6 -3
  52. package/src/features/exit/handler.ts +7 -12
  53. package/src/features/get-user-info/handler.ts +3 -4
  54. package/src/index.ts +1 -2
  55. package/src/internal-plugins/activity-history/activity-history-item/export-to-pdf/export-pdf-modal/export-pdf-modal.ts +1 -10
  56. package/src/internal-plugins/activity-history/activity-history-item/export-to-pdf/export-pdf-modal/template.ts +0 -7
  57. package/src/internal-plugins/activity-history/activity-history-item/export-to-pdf/handler.ts +8 -8
  58. package/src/internal-plugins/activity-history/activity-history-item/list/UI/main-view/template.ts +1 -1
  59. package/src/internal-plugins/activity-history/activity-history-item/search/handler.ts +5 -3
  60. package/src/internal-plugins/activity-history/localization.ts +0 -1
  61. package/src/locales.ts +5 -0
  62. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/components/dialog-component.d.ts +0 -19
  63. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/components/notifier-component.d.ts +0 -12
  64. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/confirm-mixin.d.ts +0 -11
  65. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/confirm.d.ts +0 -3
  66. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/index.d.ts +0 -5
  67. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/notify.d.ts +0 -4
  68. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/open-dialog.d.ts +0 -3
  69. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/shared.d.ts +0 -3
  70. package/dist/primary/shell/src/UI/shared-components/primaria-interaction/typings.d.ts +0 -40
  71. package/dist/primary/shell/src/api/interaction-manager/interaction.d.ts +0 -9
  72. package/src/UI/shared-components/primaria-interaction/components/dialog-component-styles.css +0 -104
  73. package/src/UI/shared-components/primaria-interaction/components/dialog-component.ts +0 -138
  74. package/src/UI/shared-components/primaria-interaction/components/notifier-component-styles.css +0 -136
  75. package/src/UI/shared-components/primaria-interaction/components/notifier-component.ts +0 -69
  76. package/src/UI/shared-components/primaria-interaction/confirm-mixin.ts +0 -35
  77. package/src/UI/shared-components/primaria-interaction/confirm.ts +0 -9
  78. package/src/UI/shared-components/primaria-interaction/index.ts +0 -5
  79. package/src/UI/shared-components/primaria-interaction/notify.ts +0 -153
  80. package/src/UI/shared-components/primaria-interaction/open-dialog.ts +0 -8
  81. package/src/UI/shared-components/primaria-interaction/shared.ts +0 -29
  82. package/src/UI/shared-components/primaria-interaction/typings.ts +0 -46
  83. package/src/api/interaction-manager/interaction.ts +0 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxland/primary-shell",
3
- "version": "4.3.1",
3
+ "version": "5.1.0",
4
4
  "description": "Primaria Shell",
5
5
  "author": "UXLand <dev@uxland.es>",
6
6
  "homepage": "https://github.com/uxland/harmonix/tree/app#readme",
@@ -40,8 +40,11 @@
40
40
  "@uxland/utilities": "^1.0.5",
41
41
  "axios-mock-adapter": "^2.0.0",
42
42
  "inversify": "^6.0.2",
43
+ "react": "^19.0.0",
44
+ "react-dom": "^19.0.0",
45
+ "@types/react": "^19.0.12",
43
46
  "inversify-inject-decorators": "^3.1.0",
44
- "@salut/design-system-salut": "../../design-system-salut-2.2.1.tgz",
47
+ "@salut/design-system-salut": "../../design-system-salut-2.3.0.tgz",
45
48
  "jwt-decode": "^4.0.0",
46
49
  "lit": "^3.1.0",
47
50
  "mediatr-ts": "^1.2.1",
@@ -9,15 +9,17 @@
9
9
  min-height: 1px;
10
10
  height: 100%;
11
11
  display: flex;
12
- flex-direction: row;
12
+ flex-direction: row;
13
+
13
14
  #widgets-sidebar-region {
15
+ z-index: 1;
14
16
  width: 25%;
15
17
  border-left: 1px solid rgb(189, 189, 189);
16
18
  }
17
19
  .content {
18
20
  flex: 1;
19
21
  display: flex;
20
- flex-direction: column;
22
+ flex-direction: column;
21
23
  #header-widgets-region {
22
24
  display: grid;
23
25
  grid-template-columns: repeat(3, 1fr);
@@ -9,6 +9,8 @@ import { shellViews } from "./constants";
9
9
  import { shellEvents } from "../../../events";
10
10
  import { activateDefaultView } from "../../../handle-views";
11
11
  import { BrokerDisposableHandler } from "harmonix/core/dist";
12
+ import { translate } from "../../../locales";
13
+ import { disposeShell } from "../../../disposer";
12
14
 
13
15
  //@ts-ignore
14
16
  @customElement("primaria-shell")
@@ -65,6 +67,9 @@ export class PrimariaShell extends PrimariaRegionHost(LitElement) {
65
67
  @state()
66
68
  error: { message: string };
67
69
 
70
+ @state()
71
+ quickActionBusy = false;
72
+
68
73
  _toggleSidebar() {
69
74
  this.sidebarExpanded = !this.sidebarExpanded;
70
75
  }
@@ -72,14 +77,35 @@ export class PrimariaShell extends PrimariaRegionHost(LitElement) {
72
77
  private subscriptions: BrokerDisposableHandler[] = [];
73
78
 
74
79
  _subscribeEvents() {
75
- const subscription = shellApi.broker.subscribe(
76
- shellEvents.appCrashed,
77
- (error: { message: string }) => {
78
- this.viewSelected = shellViews.error;
79
- this.error = error;
80
- },
80
+ this.subscriptions.push(
81
+ shellApi.broker.subscribe(shellEvents.appCrashed, (error: { message: string }) => {
82
+ this._handleError(error);
83
+ }),
84
+ );
85
+
86
+ this.subscriptions.push(
87
+ shellApi.broker.subscribe(shellEvents.refreshTokenFailed, (detail: any) => {
88
+ this._handleError({ message: translate("errors.session") });
89
+ }),
90
+ );
91
+
92
+ this.subscriptions.push(
93
+ shellApi.broker.subscribe(shellEvents.mpidHeaderInvalid, (detail: any) => {
94
+ this._handleError({ message: translate("errors.invalidPatient") });
95
+ }),
96
+ );
97
+
98
+ this.subscriptions.push(
99
+ shellApi.broker.subscribe(shellEvents.quickActionBusyChanged, (detail: { busy: boolean }) => {
100
+ this.quickActionBusy = detail.busy;
101
+ }),
81
102
  );
82
- this.subscriptions.push(subscription);
103
+ }
104
+
105
+ _handleError(error: { message: string }) {
106
+ this.viewSelected = shellViews.error;
107
+ this.error = error;
108
+ disposeShell();
83
109
  }
84
110
 
85
111
  _unsubscribeEvents() {
@@ -29,15 +29,28 @@
29
29
  width: 40px;
30
30
  height: 100%;
31
31
  color: white;
32
- .top-content {
32
+ .quick-actions-content {
33
33
  padding-top: 24px;
34
- padding-bottom: 24px;
34
+ padding-bottom: 22px;
35
35
  border-radius: 8px;
36
+ .create-button-icon-badge{
37
+ position: relative;
38
+ dss-notification-badge {
39
+ position: absolute;
40
+ top: -7px;
41
+ right: -6px;
42
+ z-index: 1;
43
+ }
44
+ dss-icon {
45
+ position: relative;
46
+ }
47
+ }
36
48
  }
37
49
  .icon {
38
50
  margin-left: 8px;
39
51
  }
40
52
  #menu-region-container {
53
+ padding-top: 24px;
41
54
  min-height: 1px;
42
55
  height: 100%;
43
56
  width: 100%;
@@ -13,10 +13,15 @@ export const template = (props: PrimariaShell) => html`
13
13
  <shell-header></shell-header>
14
14
  <div class="main-container">
15
15
  <div class="sidebar" ?expanded=${props.sidebarExpanded}>
16
- <div class="top-content">
16
+ <div id="menu-region-container"></div>
17
+ <div class="quick-actions-content">
17
18
  <quick-actions-menu>
18
19
  <div class="${props.sidebarExpanded ? "create-button-opened" : "create-button-closed"} " slot="anchor" id="usage-anchor">
19
- <dss-icon class="${props.sidebarExpanded ? "icon" : ""}" icon="add_circle_outline" size="md"></dss-icon>${props.sidebarExpanded ? translate('actions.create') : nothing}
20
+ <div class="${props.quickActionBusy ? "create-button-icon-badge" : ""}">
21
+ ${props.quickActionBusy ? html`<dss-notification-badge value="" state="error" borderwhite="" type="default" dot=""></dss-notification-badge>`: nothing}
22
+ <dss-icon class="${props.sidebarExpanded ? "icon" : ""}" icon="add_circle_outline" size="md"></dss-icon>
23
+ </div>
24
+ ${props.sidebarExpanded ? translate('actions.create') : nothing}
20
25
  ${!props.sidebarExpanded ? html`<dss-tooltip position="right">
21
26
  ${translate('actions.create')}
22
27
  </dss-tooltip>` : nothing}
@@ -24,7 +29,6 @@ export const template = (props: PrimariaShell) => html`
24
29
  <dss-action-menu id="quick-actions-region-container" slot="content" id="content" anchor="usage-anchor"></dss-action-menu>
25
30
  </quick-actions-menu>
26
31
  </div>
27
- <div id="menu-region-container"></div>
28
32
  <div class="bottom-content">
29
33
  <div class="bottom-content-first">
30
34
  <hr class="dss-divider--bold ${props.sidebarExpanded ? "divider-opened" : "divider-closed"}" />
@@ -31,10 +31,14 @@ export class QuickActionsMenu extends LitElement {
31
31
 
32
32
  _handleOpenMenu(e) {
33
33
  // Obtener el otro slot (target-slot)
34
- const targetSlot = this.shadowRoot?.querySelector('slot[name="content"]');
34
+ const contentSlot = this.shadowRoot?.querySelector('slot[name="content"]') as HTMLSlotElement;
35
+
36
+ const actionMenu = this.querySelector("dss-action-menu");
35
37
 
36
38
  // Obtener los elementos asignados al otro slot
37
- const assignedElements = targetSlot?.assignedElements();
39
+ const assignedElements = contentSlot?.assignedElements();
40
+ if (assignedElements[0])
41
+ this._addMenuCustomStyle(contentSlot as HTMLElement, actionMenu as HTMLElement);
38
42
 
39
43
  // Hacer target al elemento que deseas (por ejemplo, un <p>)
40
44
  const targetElement = assignedElements.find((el) => el.id === "content");
@@ -43,4 +47,10 @@ export class QuickActionsMenu extends LitElement {
43
47
  targetElement.show();
44
48
  }
45
49
  }
46
- }
50
+
51
+ _addMenuCustomStyle(contentSlot: HTMLElement, actionMenu: HTMLElement) {
52
+ contentSlot.style.borderRadius = "var(--dss-radius-sm)";
53
+ const list = actionMenu.shadowRoot?.querySelector("ul");
54
+ if (list) list.style.overflow = "hidden";
55
+ }
56
+ }
@@ -21,7 +21,7 @@ export const template = (props: ShellHeader) => {
21
21
  <div id="header-actions-region-container"></div>
22
22
  ${when(
23
23
  props.professional,
24
- () => html`<dss-header-menu-professional @onLogout=${props.logout} slot="professional-menu" name="${props.professional.firstName} ${props.professional?.familyName} ${props.professional?.lastName}" center="${props.professional.workCenter}" collegiate="${props.professional.registrationNumber}">
24
+ () => html`<dss-header-menu-professional @onExit=${props.logout} slot="professional-menu" name="${props.professional.firstName} ${props.professional?.familyName} ${props.professional?.lastName}" center="${props.professional.workCenter}" collegiate="${props.professional.registrationNumber}">
25
25
  <dss-avatar size="xl" name="${props.professional.firstName}" surname="${props.professional?.familyName}" slot="avatar"></dss-avatar>
26
26
  <dss-input-dropdown icon="maps_home_work" type="default" .elements=${workCenterElements} selectedvalue="[&quot;1&quot;]">
27
27
  <label slot="label" for="preferences1">${translate("header.workCenter")}</label>
@@ -1,5 +1,4 @@
1
1
  import "./primaria-content-switcher/primaria-content-switcher";
2
2
  import "./primaria-nav-item/primaria-nav-item";
3
3
  import "./primaria-text-editor/primaria-rich-text-editor";
4
- export * from "./primaria-interaction";
5
4
  export { PrimariaContentSwitcher } from "./primaria-content-switcher/primaria-content-switcher";
package/src/api/api.ts CHANGED
@@ -7,14 +7,10 @@ import {
7
7
  createRegionManager,
8
8
  } from "@uxland/harmonix";
9
9
  import { PrimariaRegionManager, createRegionManagerProxy } from "./region-manager/region-manager";
10
- import { createBroker } from "./broker/factory";
10
+ import { createBroker } from "./broker/factory";
11
11
  import { PrimariaBroker } from "./broker/primaria-broker";
12
12
  import { PrimariaGlobalStateManager, createGlobalStateManager } from "./global-state/global-state";
13
13
  import { HttpClient, createHttpClient } from "./http-client/http-client";
14
- import {
15
- PrimariaInteractionManager,
16
- createInteractionManager,
17
- } from "./interaction-manager/interaction";
18
14
  import { createLocaleManager } from "./localization/localization";
19
15
  import { TokenManager, createTokenManager } from "./token-manager/token-manager";
20
16
  import { EcapEventManager, createEcapEventManager } from "./ecap-event-manager/ecap-event-manager";
@@ -22,12 +18,16 @@ import {
22
18
  PluginBusyManager,
23
19
  PluginBusyManagerImpl,
24
20
  } from "./plugin-busy-manager/plugin-busy-manager";
25
-
21
+ import { PrimariaInteractionService } from "./interaction-service";
22
+ import { ParimariaInteractionServiceImpl } from "./interaction-service/interaction-service-impl";
23
+ import { PrimariaNotificationService } from "./notification-service/notification-service";
24
+ import { PrimariaNotificationServiceImpl } from "./notification-service/notification.service-impl";
26
25
 
27
26
  const broker = createBroker();
28
27
  export interface PrimariaApi extends HarmonixApi {
29
28
  httpClient: HttpClient;
30
- interactionManager: PrimariaInteractionManager;
29
+ interactionService: PrimariaInteractionService;
30
+ notificationService: PrimariaNotificationService;
31
31
  broker: PrimariaBroker;
32
32
  regionManager: PrimariaRegionManager;
33
33
  globalStateManager: PrimariaGlobalStateManager;
@@ -40,7 +40,9 @@ const regionManager: IRegionManager = createRegionManager("primaria");
40
40
  export const PrimariaRegionHost: any = createRegionHost(regionManager as any);
41
41
  const tokenManager = createTokenManager();
42
42
  const globalStateManager: PrimariaGlobalStateManager = createGlobalStateManager(broker);
43
- const pluginBusyManager = new PluginBusyManagerImpl();
43
+ const pluginBusyManager = new PluginBusyManagerImpl(broker);
44
+ const interactionService = new ParimariaInteractionServiceImpl();
45
+ const notificationService = new PrimariaNotificationServiceImpl();
44
46
 
45
47
  /**
46
48
  * Factory function that creates a Primaria API instance.
@@ -55,13 +57,14 @@ export const primariaApiFactory: ApiFactory<PrimariaApi> = (
55
57
  pluginInfo: pluginInfo,
56
58
  regionManager: createRegionManagerProxy(pluginInfo, regionManager, broker),
57
59
  httpClient: createHttpClient(tokenManager, broker),
58
- interactionManager: { ...createInteractionManager() },
59
60
  broker: broker,
60
61
  createLocaleManager: createLocaleManager(pluginInfo.pluginId) as any,
61
62
  globalStateManager,
62
63
  tokenManager,
63
64
  ecapEventManager: createEcapEventManager(),
64
65
  pluginBusyManager,
66
+ interactionService,
67
+ notificationService,
65
68
  };
66
69
  };
67
70
 
@@ -1,17 +1,19 @@
1
1
  import axios, { AxiosInstance } from "axios";
2
2
  import AxiosMockAdapter from "axios-mock-adapter";
3
- import { Mock, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { TokenManager, createTokenManager } from "../token-manager/token-manager";
5
5
  import { createAxiosInstance } from "./http-client";
6
6
  import { shellEvents } from "../../events";
7
7
  import { createBroker } from "../broker/factory";
8
8
 
9
- const access_token = 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3Mzg2NjcxODIsImlhdCI6MTczODY2NjI4MiwianRpIjoiY2I0M2M2ZmItY2MyMS00M2Y2LTk5M2UtNDM3ZTJjNDU3YWMzIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6ImU3MzIzYWIyLTU3MDktNGY1ZS05ZGU4LWU1MzM3ZTBhOTMxNiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiZTczMjNhYjItNTcwOS00ZjVlLTlkZTgtZTUzMzdlMGE5MzE2IiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJ1cF9jb2RlIjoiMDc3MzMiLCJ0cmFjZV91c2VyX2dpdmVuX25hbWUiOiJHaXZlbiBOYW1lIiwidHJhY2VfdXNlcl9mYW1pbHlfbmFtZSI6IkZhbWlseSBOYW1lIiwidXNlcl90eXBlIjoiQURNIiwiY2VudGVyX2NvZGUiOiJFMDg1ODY5NjMiLCJwcm9mZXNzaW9uYWxfY2F0ZWdvcnkiOiIzMDkzNDMwMDYiLCJzZXJ2aWNlX2NvZGUiOiI1UzA4OSIsImVwX2NvZGUiOiIwMjA4IiwiaWRlbnRpZmllciI6W3sidHlwZSI6IkROSSIsInZhbHVlIjoiNzMyODgyMTlBIn0seyJ0eXBlIjoiTlVNQ09MIiwidmFsdWUiOiIyIn1dLCJhbHRfaWRlbnRpZmllciI6W3sidHlwZSI6Ik1QSSIsInZhbHVlIjoiMDYyMWNmN2QtN2Q2My00OWVjLTgwMDYtOGMwNTY5MmVkMzc3In1dfSwiY2xpZW50QWRkcmVzcyI6IjEwLjUzLjI1NC4xNTAiLCJjbGllbnRfaWQiOiJldGMtY2NmLXByZSJ9.WYF6VvIVqzW71MdOcvRJnKNBEe8BoD43Ql_hyGyqBC1wbNDNcbqucX-2wPbSOpHaHv5iogYNTVp1eEkHvrnryQ4koHXf4U3inTxoZR94qAzIt8XGgBVxpYlArc-XrFX1FAwkdfsNmYE_L6G7q5wfzaP0y1YrFH6u9TPTBl1w2us8O8xKPB6B652NUphNHFWmPWv6t6Zq97sjHPIHMZsDeBdolK5RlG5J3u8K-quJeKLByZhA2kYAJMGyCzR6jLLrup5w-WdYNJRyGopeFSDVp-lECmaiYIXmhQOJzzJQqhARrwYN8ZxqTOyD5u24HOB7Q1ZCMnAp4vAL6OphWyRZuw';
10
- const refresh_token = 'eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwMjUzZGM1MC1hY2FmLTQ5ZDctYTYzNi0xN2NkMTlmOWEwOTAifQ.eyJleHAiOjE3Mzg2NjgwODIsImlhdCI6MTczODY2NjI4MiwianRpIjoiZWM5OGE2OWMtMDJkNS00Yjc0LWIwZWYtNzcwMGU4OTc3YWY1IiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsImF1ZCI6Imh0dHBzOi8vcHJlcHJvZHVjY2lvLnBkcy5oZXMuY2F0c2FsdXQuZ2VuY2F0LmNhdC9hdXRoL3JlYWxtcy9IRVMiLCJzdWIiOiI5NThlMmVkOS0yZDZlLTQ2YzgtYTkxOS01OTA0NGU2MDM1YzIiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiZXRjLWNjZi1wcmUiLCJzZXNzaW9uX3N0YXRlIjoiZTczMjNhYjItNTcwOS00ZjVlLTlkZTgtZTUzMzdlMGE5MzE2Iiwic2NvcGUiOiIiLCJzaWQiOiJlNzMyM2FiMi01NzA5LTRmNWUtOWRlOC1lNTMzN2UwYTkzMTYifQ.KOO0Ulbed4640-obEr3Xg8qavGeXhU25o6ZUfsq0SoQpzMjhcx8GTgOH-PiIR88ksrjtTnpbmzw2D29xuugv4A';
11
-
9
+ const access_token =
10
+ "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3NDI5MDkxNjMsImlhdCI6MTc0MjkwODI2MywianRpIjoiODRlMWEwYjYtOGNmYS00NzQ3LTk0MDItMjVkM2FlMzM3N2QxIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6ImVhNzUwMDg4LWI1NjctNDU4ZC1iOWY5LWQ0OTdjMzlkN2ZkMyIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiZWE3NTAwODgtYjU2Ny00NThkLWI5ZjktZDQ5N2MzOWQ3ZmQzIiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJtcGlfcGF0aWVudF9pZCI6IjVlYzc0YzdiLTVhYTUtNGE4NC1iM2I0LWYwMmVkZGZkZjZkYyIsInVwX2NvZGUiOiIwNzczMyIsInRyYWNlX3VzZXJfZ2l2ZW5fbmFtZSI6IkdpdmVuIE5hbWUiLCJ0cmFjZV91c2VyX2ZhbWlseV9uYW1lIjoiRmFtaWx5IE5hbWUiLCJ1c2VyX3R5cGUiOiJBRE0iLCJjZW50ZXJfY29kZSI6IkUwODU4Njk2MyIsInByb2Zlc3Npb25hbF9jYXRlZ29yeSI6IjMwOTM0MzAwNiIsInNlcnZpY2VfY29kZSI6IjVTMDg5IiwiZXBfY29kZSI6IjAyMDgiLCJpZGVudGlmaWVyIjpbeyJ0eXBlIjoiRE5JIiwidmFsdWUiOiI3MzI4ODIxOUEifSx7InR5cGUiOiJOVU1DT0wiLCJ2YWx1ZSI6IjIifV0sImFsdF9pZGVudGlmaWVyIjpbeyJ0eXBlIjoiTVBJIiwidmFsdWUiOiIwNjIxY2Y3ZC03ZDYzLTQ5ZWMtODAwNi04YzA1NjkyZWQzNzcifV19LCJjbGllbnRBZGRyZXNzIjoiMTAuNTMuMjU0LjE1MCIsImNsaWVudF9pZCI6ImV0Yy1jY2YtcHJlIn0.CgYL3zXbUHHiE7ZtrcxvlDHB3dbY_eI8TsL-0mBOy-JedSZZaGT_JS8Odm_eIo1drWZSnEaNblFZUPE56sCEPH6RYSqtyK0eYwy_aKoprKoRuDjEy2-VfmOJi5gY9wpYnAYpVtuTbncVirQIV9SIZ9st2d7LlfcM_88Yn8NJNTDcUk7ETw7Zy_728X2wjL_UeHT7yuzo3Y-Xm_389Og7UMyYGGrs104CHQNrzIMXiklkRZr4dqjqWU61Csrb8OBI6jCUAbBqgS8vPuUzPUghQXstr-aNn3zicy-Zw5e6RqCmSQXg56aTKx9QIOrQuX-az-urPnqvtWdloQrQJLQGSg";
11
+ const refresh_token =
12
+ "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwMjUzZGM1MC1hY2FmLTQ5ZDctYTYzNi0xN2NkMTlmOWEwOTAifQ.eyJleHAiOjE3NDI5MTE4NjMsImlhdCI6MTc0MjkwODI2MywianRpIjoiZjA4MzA1MDktMTA0My00ODI1LTk5YTItOTg1YmNjOGI4MmJmIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsImF1ZCI6Imh0dHBzOi8vcHJlcHJvZHVjY2lvLnBkcy5oZXMuY2F0c2FsdXQuZ2VuY2F0LmNhdC9hdXRoL3JlYWxtcy9IRVMiLCJzdWIiOiI5NThlMmVkOS0yZDZlLTQ2YzgtYTkxOS01OTA0NGU2MDM1YzIiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiZXRjLWNjZi1wcmUiLCJzZXNzaW9uX3N0YXRlIjoiZWE3NTAwODgtYjU2Ny00NThkLWI5ZjktZDQ5N2MzOWQ3ZmQzIiwic2NvcGUiOiIiLCJzaWQiOiJlYTc1MDA4OC1iNTY3LTQ1OGQtYjlmOS1kNDk3YzM5ZDdmZDMifQ.I8u83nfdxbjRrS7dC1k8_f9oBKmSD_Xu4iAxlFqcA3QdHi-L_yY63ENU1LTehh8-2d51f__nv58zPb799hNGhg";
13
+ const newAccessToken =
14
+ "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJidFZxNnRMWGpmcXdzbm5MR2FXMXdhdU9McDNiTmY4bWZ3Rm1SZ0lBS2VJIn0.eyJleHAiOjE3NDI5MTAzNzgsImlhdCI6MTc0MjkwOTQ3OCwianRpIjoiMTg4OTE3ZDYtNmM0OS00YWY0LTlhYjItZjEzYTM4MGY5OTgyIiwiaXNzIjoiaHR0cHM6Ly9wcmVwcm9kdWNjaW8ucGRzLmhlcy5jYXRzYWx1dC5nZW5jYXQuY2F0L2F1dGgvcmVhbG1zL0hFUyIsInN1YiI6Ijk1OGUyZWQ5LTJkNmUtNDZjOC1hOTE5LTU5MDQ0ZTYwMzVjMiIsInR5cCI6IkJlYXJlciIsImF6cCI6ImV0Yy1jY2YtcHJlIiwic2Vzc2lvbl9zdGF0ZSI6IjY5ODdhMzExLWM1NzQtNDc1Ni1hMzY0LWY1ZjQzZmViZjNhYiIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWhlcyIsIlJPTEVfSEVTX0VUQyIsIlJPTEVfSEVTX0xBTkRJTkciXX0sInNjb3BlIjoiIiwic2lkIjoiNjk4N2EzMTEtYzU3NC00NzU2LWEzNjQtZjVmNDNmZWJmM2FiIiwiY2xpZW50SG9zdCI6IjEwLjUzLjI1NC4xNTAiLCJhY2Nlc3NfcnVsZXMiOnt9LCJhY2Nlc3NfaW5mbyI6eyJtb2R1bGVfY29kZSI6IkEwMDEiLCJyb2xlX3R5cGUiOiJOT1JNIiwidHJhY2VfdXNlcl9pZCI6IlVzZXJfSUQiLCJjZW50ZXJfdHlwZSI6IkUiLCJtcGlfcGF0aWVudF9pZCI6IjVlYzc0YzdiLTVhYTUtNGE4NC1iM2I0LWYwMmVkZGZkZjZkYyIsInVwX2NvZGUiOiIwNzczMyIsInRyYWNlX3VzZXJfZ2l2ZW5fbmFtZSI6IkdpdmVuIE5hbWUiLCJ0cmFjZV91c2VyX2ZhbWlseV9uYW1lIjoiRmFtaWx5IE5hbWUiLCJ1c2VyX3R5cGUiOiJBRE0iLCJjZW50ZXJfY29kZSI6IkUwODU4Njk2MyIsInByb2Zlc3Npb25hbF9jYXRlZ29yeSI6IjMwOTM0MzAwNiIsInNlcnZpY2VfY29kZSI6IjVTMDg5IiwiZXBfY29kZSI6IjAyMDgiLCJpZGVudGlmaWVyIjpbeyJ0eXBlIjoiRE5JIiwidmFsdWUiOiI3MzI4ODIxOUEifSx7InR5cGUiOiJOVU1DT0wiLCJ2YWx1ZSI6IjIifV0sImFsdF9pZGVudGlmaWVyIjpbeyJ0eXBlIjoiTVBJIiwidmFsdWUiOiIwNjIxY2Y3ZC03ZDYzLTQ5ZWMtODAwNi04YzA1NjkyZWQzNzcifV19LCJjbGllbnRBZGRyZXNzIjoiMTAuNTMuMjU0LjE1MCIsImNsaWVudF9pZCI6ImV0Yy1jY2YtcHJlIn0.zPxoN5d8_uxdbxeH7ly9sQdlnDxNp8_eWDzNSzjzkUPsRVkLxgjQw_WcjZfns-LNgGr0FToS6uSDIC7uj2hPpKqD7HPWbI6UPa7g-43Jf9rh-KSOXEHJWhlASi2w0n3xQHFSxHDYBwhyXlvWQ_Tr1Vh37MroWmEL84fdwqhcfZHmD9Ek52EuRRrB-XkymCmN1ZvO2GPU04LbBvQxLVzdAaKRzr1cUqHTnsEdgRduyKShPcyth01L_X7m128SIwjJsa1lYxFE_DbSDKz8dPTsxoS1DgwRxeArrkMlIdTHyxmDRezZUGhamyLuWpXLDXJCFfrTUB8-j_JnVlILHeR6O";
12
15
 
13
16
  describe("HTTP Client", () => {
14
-
15
17
  let broker = createBroker();
16
18
  let tokenManager: TokenManager;
17
19
  let axiosInstance: AxiosInstance;
@@ -19,91 +21,201 @@ describe("HTTP Client", () => {
19
21
 
20
22
  beforeEach(() => {
21
23
  vi.restoreAllMocks(); // Reset all spies before each test
22
- vi.spyOn(window, 'location', 'get').mockReturnValue({
23
- search: `?access_token=${access_token}&refresh_token=${refresh_token}`
24
+ vi.spyOn(window, "location", "get").mockReturnValue({
25
+ search: `?access_token=${access_token}&refresh_token=${refresh_token}`,
24
26
  } as unknown as Location);
25
-
26
- tokenManager = createTokenManager();
27
- axiosInstance = createAxiosInstance(tokenManager, broker);
28
- axiosMockInstance = new AxiosMockAdapter(axiosInstance);
29
- axiosMockInstance.reset();
30
27
  });
31
28
 
32
- it("should make a request with header", async () => {
33
- axiosMockInstance.onGet("/api/clinical-course").reply(200, "success");
34
- // Perform the request
35
- const response = await axiosInstance.get("/api/clinical-course");
36
- // Assertions
37
- expect(response.status).toBe(200);
38
- expect(response.data).toBe("success");
39
- expect(axiosMockInstance.history.get?.length).toBe(1);
40
- // @ts-ignore
41
- expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
42
- `Bearer ${access_token}`,
43
- );
44
- });
29
+ describe("Without validateMpid", () => {
30
+ beforeEach(() => {
31
+ tokenManager = createTokenManager();
32
+ axiosInstance = createAxiosInstance(tokenManager, broker);
33
+ axiosMockInstance = new AxiosMockAdapter(axiosInstance);
34
+ axiosMockInstance.reset();
35
+ });
45
36
 
46
- it("should retry request with new token if failed with 401 after token refresh, and requests after that have new token", async () => {
47
-
48
- axiosMockInstance
49
- .onGet("/api/clinical-course")
50
- .replyOnce(401, "Unauthorized")
51
- .onGet("/api/clinical-course")
52
- .replyOnce(200, "success")
53
- .onGet("/api/clinical-course/2")
54
- .replyOnce(200, "success");
55
-
56
- vi.spyOn(axios, "post").mockResolvedValueOnce({
57
- data: {
58
- access_token: "new-auth-token",
59
- refresh_token: "new-refresh-token"
60
- }
37
+ it("should make a request with header", async () => {
38
+ axiosMockInstance.onGet("/api/clinical-course").reply(200, "success");
39
+ // Perform the request
40
+ const response = await axiosInstance.get("/api/clinical-course");
41
+ // Assertions
42
+ expect(response.status).toBe(200);
43
+ expect(response.data).toBe("success");
44
+ expect(axiosMockInstance.history.get?.length).toBe(1);
45
+ // @ts-ignore
46
+ expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
47
+ `Bearer ${access_token}`,
48
+ );
61
49
  });
62
50
 
63
- // Perform the request
64
- const response = await axiosInstance.get("/api/clinical-course");
65
- const secondResponse = await axiosInstance.get("/api/clinical-course/2");
66
-
67
- console.log("response", response.status);
68
- // Assertions
69
- expect(response.status).toBe(200);
70
- expect(response.data).toBe("success");
71
- expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
72
- expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
73
- "Bearer new-auth-token",
74
- );
75
- expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
76
- "Bearer new-auth-token",
77
- );
51
+ it("should retry request with new token if failed with 401 after token refresh, and requests after that have new token", async () => {
52
+ axiosMockInstance
53
+ .onGet("/api/clinical-course")
54
+ .replyOnce(401, "Unauthorized")
55
+ .onGet("/api/clinical-course")
56
+ .replyOnce(200, "success")
57
+ .onGet("/api/clinical-course/2")
58
+ .replyOnce(200, "success");
59
+
60
+ vi.spyOn(axios, "post").mockResolvedValueOnce({
61
+ data: {
62
+ access_token: newAccessToken,
63
+ refresh_token: "new-refresh-token",
64
+ },
65
+ });
66
+
67
+ // Perform the request
68
+ const response = await axiosInstance.get("/api/clinical-course");
69
+ const secondResponse = await axiosInstance.get("/api/clinical-course/2");
70
+ // Assertions
71
+ expect(response.status).toBe(200);
72
+ expect(response.data).toBe("success");
73
+ expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
74
+ expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
75
+ "Bearer " + newAccessToken,
76
+ );
77
+ expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
78
+ "Bearer " + newAccessToken,
79
+ );
80
+ });
81
+
82
+ it("should fail request if token refresh fails and publish an event", async () => {
83
+ vi.spyOn(axios, "post").mockRejectedValueOnce({
84
+ response: { status: 400, statusText: "Bad Request" },
85
+ });
86
+ const brokerSpy = vi.spyOn(broker, "publish");
87
+ axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");
88
+
89
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
90
+ "Request failed with status code 401",
91
+ );
92
+
93
+ expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
94
+ expect(brokerSpy).toHaveBeenCalledWith(shellEvents.refreshTokenFailed, expect.any(Object));
95
+ expect(brokerSpy).toHaveBeenCalledWith(shellEvents.refreshTokenFailed, {
96
+ request: expect.objectContaining({ url: "/api/clinical-course" }),
97
+ });
98
+ });
78
99
 
100
+ it("should fail request with error != 401 and should not retry", async () => {
101
+ const refreshTokenSpy = vi.spyOn(tokenManager, "refreshToken");
102
+ const brokerSpy = vi.spyOn(broker, "publish");
103
+ axiosMockInstance.onGet("/api/clinical-course").replyOnce(500, "Internal Server Error");
104
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toMatchObject({
105
+ response: { status: 500 },
106
+ });
107
+ expect(axiosMockInstance.history.get?.length).toBe(1);
108
+ expect(refreshTokenSpy).not.toHaveBeenCalled();
109
+ expect(brokerSpy).not.toHaveBeenCalled();
110
+ });
79
111
  });
80
112
 
81
- it("should fail request if token refresh fails and publish an event", async () => {
82
- vi.spyOn(axios, "post").mockRejectedValueOnce({
83
- response: { status: 400, statusText: "Bad Request" },
113
+ describe.skip("With validateMpid", () => {
114
+ const validTokenHeaders = { "x-catsalut-mpid": "5ec74c7b-5aa5-4a84-b3b4-f02eddfdf6dc" };
115
+ beforeEach(() => {
116
+ tokenManager = createTokenManager();
117
+ axiosInstance = createAxiosInstance(tokenManager, broker, true);
118
+ axiosMockInstance = new AxiosMockAdapter(axiosInstance);
119
+ axiosMockInstance.reset();
84
120
  });
85
- const brokerSpy = vi.spyOn(broker, "publish")
86
- axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");
87
121
 
88
- await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow("Request failed with status code 401");
122
+ it("should throw error if mpid header is missing without sending broker event", async () => {
123
+ axiosMockInstance.onGet("/api/clinical-course").reply(200, "success");
124
+ const brokerSpy = vi.spyOn(broker, "publish");
89
125
 
90
- expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
91
- expect(brokerSpy).toHaveBeenCalledWith(shellEvents.refreshTokenFailed, expect.any(Object));
92
- expect(brokerSpy).toHaveBeenCalledWith(shellEvents.refreshTokenFailed, {
93
- request: expect.objectContaining({ url: "/api/clinical-course" }),
126
+ // Perform the request
127
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
128
+ "Mpid header is missing",
129
+ );
130
+ expect(brokerSpy).not.toHaveBeenCalled();
131
+ });
132
+
133
+ it("should throw error if mpid header value is invalid and send broker event ", async () => {
134
+ axiosMockInstance.onGet("/api/clinical-course").reply(200, "success", {
135
+ "x-catsalut-mpid": "invalid-mpid",
136
+ });
137
+ const brokerSpy = vi.spyOn(broker, "publish");
138
+ // Perform the request
139
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
140
+ "Mpid header value is invalid",
141
+ );
142
+ expect(brokerSpy).toHaveBeenCalledWith(shellEvents.mpidHeaderInvalid, expect.any(Object));
143
+ });
144
+
145
+ it("should make a request with header and respond succesful if x-catsalut-mpid has valid value ", async () => {
146
+ axiosMockInstance.onGet("/api/clinical-course").reply(200, "success", validTokenHeaders);
147
+ // Perform the request
148
+ const response = await axiosInstance.get("/api/clinical-course");
149
+ // Assertions
150
+ expect(response.status).toBe(200);
151
+ expect(response.data).toBe("success");
152
+ expect(axiosMockInstance.history.get?.length).toBe(1);
153
+ // @ts-ignore
154
+ expect(axiosMockInstance.history.get?.[0]?.headers.Authorization).toBe(
155
+ `Bearer ${access_token}`,
156
+ );
157
+ });
158
+
159
+ it("should retry request with new token if failed with 401 after token refresh, and requests after that have new token", async () => {
160
+ axiosMockInstance
161
+ .onGet("/api/clinical-course")
162
+ .replyOnce(401, "Unauthorized")
163
+ .onGet("/api/clinical-course")
164
+ .replyOnce(200, "success", validTokenHeaders)
165
+ .onGet("/api/clinical-course/2")
166
+ .replyOnce(200, "success", validTokenHeaders);
167
+
168
+ vi.spyOn(axios, "post").mockResolvedValueOnce({
169
+ data: {
170
+ access_token: newAccessToken,
171
+ refresh_token: "new-refresh-token",
172
+ },
173
+ });
174
+
175
+ // Perform the request
176
+ const response = await axiosInstance.get("/api/clinical-course");
177
+ const secondResponse = await axiosInstance.get("/api/clinical-course/2");
178
+
179
+ // Assertions
180
+ expect(response.status).toBe(200);
181
+ expect(response.data).toBe("success");
182
+ expect(axiosMockInstance.history.get?.length).toBe(3); // 3 Calls 2 for the first request (request and retry), 1 for the second request
183
+ expect(axiosMockInstance.history.get?.[1]?.headers?.Authorization).toBe(
184
+ "Bearer " + newAccessToken,
185
+ );
186
+ expect(axiosMockInstance.history.get?.[2]?.headers?.Authorization).toBe(
187
+ "Bearer " + newAccessToken,
188
+ );
189
+ });
190
+
191
+ it("should fail request if token refresh fails and publish an event", async () => {
192
+ vi.spyOn(axios, "post").mockRejectedValueOnce({
193
+ response: { status: 400, statusText: "Bad Request" },
194
+ });
195
+ const brokerSpy = vi.spyOn(broker, "publish");
196
+ axiosMockInstance.onGet("/api/clinical-course").replyOnce(401, "Unauthorized");
197
+
198
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toThrow(
199
+ "Request failed with status code 401",
200
+ );
201
+
202
+ expect(axiosMockInstance.history.get?.length).toBe(1); // No retry happened
203
+ expect(brokerSpy).toHaveBeenCalledWith(shellEvents.refreshTokenFailed, expect.any(Object));
204
+ expect(brokerSpy).toHaveBeenCalledWith(shellEvents.refreshTokenFailed, {
205
+ request: expect.objectContaining({ url: "/api/clinical-course" }),
206
+ });
94
207
  });
95
- });
96
208
 
97
- it("should fail request with error != 401 and should not retry", async () => {
98
- const refreshTokenSpy = vi.spyOn(tokenManager, "refreshToken");
99
- const brokerSpy = vi.spyOn(broker, "publish");
100
- axiosMockInstance.onGet("/api/clinical-course").replyOnce(500, "Internal Server Error");
101
- await expect(axiosInstance.get("/api/clinical-course")).rejects.toMatchObject({
102
- response: { status: 500 },
209
+ it("should fail request with error != 401 and should not retry", async () => {
210
+ const refreshTokenSpy = vi.spyOn(tokenManager, "refreshToken");
211
+ const brokerSpy = vi.spyOn(broker, "publish");
212
+ axiosMockInstance.onGet("/api/clinical-course").replyOnce(500, "Internal Server Error");
213
+ await expect(axiosInstance.get("/api/clinical-course")).rejects.toMatchObject({
214
+ response: { status: 500 },
215
+ });
216
+ expect(axiosMockInstance.history.get?.length).toBe(1);
217
+ expect(refreshTokenSpy).not.toHaveBeenCalled();
218
+ expect(brokerSpy).not.toHaveBeenCalled();
103
219
  });
104
- expect(axiosMockInstance.history.get?.length).toBe(1);
105
- expect(refreshTokenSpy).not.toHaveBeenCalled();
106
- expect(brokerSpy).not.toHaveBeenCalled();
107
220
  });
108
-
109
221
  });