diff --git a/src/app/store/rtl.effects.spec.ts b/src/app/store/rtl.effects.spec.ts index 2e0f498a..3466bf0b 100644 --- a/src/app/store/rtl.effects.spec.ts +++ b/src/app/store/rtl.effects.spec.ts @@ -1,6 +1,7 @@ import { TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { OverlayContainer } from '@angular/cdk/overlay'; @@ -25,7 +26,7 @@ import { API_END_POINTS, APICallStatusEnum, RTLActions, UI_MESSAGES } from '../s import { RTLEffects } from './rtl.effects'; import { RTLState } from './rtl.state'; -import { updateRootAPICallStatus, openSpinner, closeSpinner, openAlert, resetRootStore } from './rtl.actions'; +import { updateRootAPICallStatus, openSpinner, closeSpinner, openAlert, resetRootStore, fetchRTLConfig, openSnackBar } from './rtl.actions'; import { resetLNDStore, fetchInfoLND, fetchPageSettings as fetchPageSettingsLND } from '../lnd/store/lnd.actions'; import { resetCLNStore } from '../cln/store/cln.actions'; import { resetECLStore } from '../eclair/store/ecl.actions'; @@ -35,6 +36,7 @@ describe('RTL Root Effects', () => { let effects: RTLEffects; let mockStore: Store; let snackBar: MatSnackBar; + let router: Router; let container: any; let httpClient: HttpClient; let httpTestingController: HttpTestingController; @@ -62,6 +64,7 @@ describe('RTL Root Effects', () => { effects = TestBed.inject(RTLEffects); mockStore = TestBed.inject(Store); snackBar = TestBed.inject(MatSnackBar); + router = TestBed.inject(Router); httpClient = TestBed.inject(HttpClient); httpTestingController = TestBed.inject(HttpTestingController); container = document.createElement('div'); @@ -125,6 +128,41 @@ describe('RTL Root Effects', () => { }); }); + it('should refresh application settings after default password login', () => { + const storeDispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough(); + const routerNavigateSpy = spyOn(router, 'navigate').and.stub(); + + effects.setLoggedInDetails(true, { token: 'test-token' }); + + expect(storeDispatchSpy.calls.all()[0].args[0]).toEqual(fetchRTLConfig()); + expect(storeDispatchSpy.calls.all()[1].args[0]).toEqual(openSnackBar({ payload: 'Reset your password.' })); + expect(routerNavigateSpy).toHaveBeenCalledWith(['/settings/auth']); + }); + + it('should store application settings when selected node index is missing from config', (done) => { + const storeDispatchSpy = spyOn(mockStore, 'dispatch').and.callThrough(); + actions = new ReplaySubject(1); + const appConfig = { + ...mockRTLStoreState.root.appConfig, + SSO: { rtlSSO: 0, logoutRedirectLink: '/rtl/login' }, + secret2FA: '', + allowPasswordUpdate: true, + selectedNodeIndex: 99 + }; + actions.next({ type: RTLActions.FETCH_APPLICATION_SETTINGS }); + const sub = effects.appConfigFetch.subscribe((appConfigResponse) => { + expect(appConfigResponse).toEqual({ type: RTLActions.SET_APPLICATION_SETTINGS, payload: appConfig }); + const setSelectedNodeAction = storeDispatchSpy.calls.all().find((call) => (call.args[0] as any).type === RTLActions.SET_SELECTED_NODE)?.args[0] as any; + expect(setSelectedNodeAction).toBeTruthy(); + expect(setSelectedNodeAction.payload.currentLnNode.index).toEqual(appConfig.nodes[0].index); + done(); + setTimeout(() => sub.unsubscribe()); + }); + const req = httpTestingController.expectOne(API_END_POINTS.CONF_API); + req.flush(appConfig); + expect(req.request.method).toEqual('GET'); + }); + it('should open snack bar', (done) => { const snackBarOpenSpy = spyOn(snackBar, 'open').and.callThrough(); actions = new ReplaySubject(1); diff --git a/src/app/store/rtl.effects.ts b/src/app/store/rtl.effects.ts index 66ea5af5..d3b79bed 100644 --- a/src/app/store/rtl.effects.ts +++ b/src/app/store/rtl.effects.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of, Subject } from 'rxjs'; -import { map, mergeMap, catchError, take, withLatestFrom, takeUntil } from 'rxjs/operators'; +import { map, mergeMap, switchMap, catchError, finalize, take, withLatestFrom, takeUntil } from 'rxjs/operators'; import { MatDialog } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; @@ -198,7 +198,7 @@ export class RTLEffects implements OnDestroy { appConfigFetch = createEffect( () => this.actions.pipe( ofType(RTLActions.FETCH_APPLICATION_SETTINGS), - mergeMap(() => { + switchMap(() => { this.screenSize = this.commonService.getScreenSize(); if (this.screenSize === ScreenSizeEnum.XS || this.screenSize === ScreenSizeEnum.SM) { this.alertWidth = '95%'; @@ -212,34 +212,43 @@ export class RTLEffects implements OnDestroy { } this.store.dispatch(openSpinner({ payload: UI_MESSAGES.GET_RTL_CONFIG })); this.store.dispatch(updateRootAPICallStatus({ payload: { action: 'FetchRTLConfig', status: APICallStatusEnum.INITIATED } })); - return this.httpClient.get(API_END_POINTS.CONF_API); - }), - map((rtlConfig: RTLConfiguration) => { - this.logger.info(rtlConfig); - this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.GET_RTL_CONFIG })); - this.store.dispatch(updateRootAPICallStatus({ payload: { action: 'FetchRTLConfig', status: APICallStatusEnum.COMPLETED } })); - let searchNode: Node | null = null; - rtlConfig.nodes.forEach((node) => { - node.settings.currencyUnits = [...CURRENCY_UNITS, (node.settings?.currencyUnit ? node.settings?.currencyUnit : '')]; - if (+(node.index || -1) === rtlConfig.selectedNodeIndex) { - searchNode = node; - } - }); - if (searchNode) { - this.store.dispatch(setSelectedNode({ payload: { uiMessage: UI_MESSAGES.NO_SPINNER, prevLnNodeIndex: -1, currentLnNode: searchNode, isInitialSetup: true } })); - return { - type: RTLActions.SET_APPLICATION_SETTINGS, - payload: rtlConfig - }; - } else { - return { - type: RTLActions.VOID - }; - } - }), - catchError((err) => { - this.handleErrorWithAlert('FetchRTLConfig', UI_MESSAGES.GET_RTL_CONFIG, 'Fetch RTL Config Failed!', API_END_POINTS.CONF_API, err); - return of({ type: RTLActions.VOID }); + let errorHandled = false; + return this.httpClient.get(API_END_POINTS.CONF_API).pipe( + map((rtlConfig: RTLConfiguration) => { + this.logger.info(rtlConfig); + this.store.dispatch(updateRootAPICallStatus({ payload: { action: 'FetchRTLConfig', status: APICallStatusEnum.COMPLETED } })); + let searchNode: Node | null = null; + const selectedNodeIndex = +rtlConfig.selectedNodeIndex; + rtlConfig.nodes?.forEach((node) => { + node.settings.currencyUnits = [...CURRENCY_UNITS, (node.settings?.currencyUnit ? node.settings?.currencyUnit : '')]; + if ((node.index ?? -1) === selectedNodeIndex) { + searchNode = node; + } + }); + searchNode = searchNode || rtlConfig.nodes?.[0] || null; + if (searchNode) { + this.store.dispatch(setSelectedNode({ payload: { uiMessage: UI_MESSAGES.NO_SPINNER, prevLnNodeIndex: -1, currentLnNode: searchNode, isInitialSetup: true } })); + return { + type: RTLActions.SET_APPLICATION_SETTINGS, + payload: rtlConfig + }; + } else { + return { + type: RTLActions.VOID + }; + } + }), + catchError((err) => { + errorHandled = true; + this.handleErrorWithAlert('FetchRTLConfig', UI_MESSAGES.GET_RTL_CONFIG, 'Fetch RTL Config Failed!', API_END_POINTS.CONF_API, err); + return of({ type: RTLActions.VOID }); + }), + finalize(() => { + if (!errorHandled) { + this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.GET_RTL_CONFIG })); + } + }) + ); })) ); @@ -581,11 +590,10 @@ export class RTLEffects implements OnDestroy { this.logger.info('Successfully Authorized!'); this.SetToken(postRes.token); this.sessionService.setItem('defaultPassword', defaultPassword); + this.store.dispatch(fetchRTLConfig()); if (defaultPassword) { this.store.dispatch(openSnackBar({ payload: 'Reset your password.' })); this.router.navigate(['/settings/auth']); - } else { - this.store.dispatch(fetchRTLConfig()); } }