import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { of } from 'rxjs'; import { Store, StoreModule } from '@ngrx/store'; import { CommonService } from '../../../shared/services/common.service'; import { RootReducer } from '../../../store/rtl.reducers'; import { LNDReducer } from '../../../lnd/store/lnd.reducers'; import { CLNReducer } from '../../../cln/store/cln.reducers'; import { ECLReducer } from '../../../eclair/store/ecl.reducers'; import { LightningSendPaymentsComponent } from './send-payment.component'; import { mockCLEffects, mockDataService, mockLoggerService, mockECLEffects, mockLNDEffects, mockMatDialogRef, mockRTLEffects } from '../../../shared/test-helpers/mock-services'; import { LoggerService } from '../../../shared/services/logger.service'; import { SharedModule } from '../../../shared/shared.module'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DataService } from '../../../shared/services/data.service'; import { EffectsModule } from '@ngrx/effects'; import { FEE_LIMIT_TYPES, UI_MESSAGES } from '../../../shared/services/consts-enums-functions'; import { mockRTLStoreState } from '../../../shared/test-helpers/test-data'; import { RTLState } from '../../../store/rtl.state'; import { sendPayment } from '../../store/lnd.actions'; import { SelNodeChild } from '../../../shared/models/RTLconfig'; import { SendPayment } from '../../../shared/models/lndModels'; describe('LightningSendPaymentsComponent', () => { let component: LightningSendPaymentsComponent; let fixture: ComponentFixture; let commonService: CommonService; let store: Store; let storeSpyChannels; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [LightningSendPaymentsComponent], imports: [ BrowserAnimationsModule, SharedModule, StoreModule.forRoot({ root: RootReducer, lnd: LNDReducer, cln: CLNReducer, ecl: ECLReducer }), EffectsModule.forRoot([mockRTLEffects, mockLNDEffects, mockCLEffects, mockECLEffects]) ], providers: [ CommonService, { provide: LoggerService, useClass: mockLoggerService }, { provide: MatDialogRef, useClass: mockMatDialogRef }, { provide: DataService, useClass: mockDataService }, { provide: MAT_DIALOG_DATA, useValue: {} } ] }). compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LightningSendPaymentsComponent); component = fixture.componentInstance; commonService = fixture.debugElement.injector.get(CommonService); store = fixture.debugElement.injector.get(Store); fixture.detectChanges(); }); it('should create', () => { storeSpyChannels = spyOn(store, 'select').and.returnValue(of(mockRTLStoreState.lnd.channels)); component.activeChannels = storeSpyChannels; expect(component).toBeTruthy(); }); it('should get lnd store value on ngOnInit', () => { const storeSpy = spyOn(store, 'select').and.returnValue(of(mockRTLStoreState.lnd.nodeSettings)); component.ngOnInit(); if (component.selNode) { expect(component.selNode.lnImplementation).toBe('LND'); expect(storeSpy).toHaveBeenCalledTimes(2); } }); it('should send payment buttons work as expected', () => { const storeSpy = spyOn(store, 'dispatch').and.callThrough(); component.zeroAmtInvoice = true; component.paymentAmount = 600; component.paymentRequest = 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxa' + 'rnyq6qcqp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8ss5cqqqzgqqqq' + 'qqqlgqqqqqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq38j0s3hrgpv4eel3'; component.paymentDecoded = { destination: '031844beb16bf8dd8c7bc30588b8c37b36e62b71c6e812e9b6d976c0a57e151be2', payment_hash: 'a53968453af7ab6fc58d229a91bdf23d7c121963067f06cf02e1a7b581852c07', timestamp: '1623624612', expiry: '3600', description: 'Testing ngrx Effects 4', description_hash: '', fallback_addr: '', cltv_expiry: '10', route_hints: [{ hop_hints: [{ node_id: '028ec70462207b57e3d4d9332d9e0aee676c92d89b7c9fb0850fc2a24814d4d83c', chan_id: '2166413939696009216', fee_base_msat: 1000, fee_proportional_millionths: 1, cltv_expiry_delta: 40 }] }], payment_addr: 'NIXNBEqCTmqw89joe0m71Z9MrkkBcF1t1ri+9BZehKw=', num_msat: '400000', features: { 9: { name: 'tlv-onion', is_required: false, is_known: true }, 15: { name: 'payment-addr', is_required: false, is_known: true }, 17: { name: 'multi-path-payments', is_required: false, is_known: true } } }; const sendButton = fixture.debugElement.nativeElement.querySelector('#sendBtn'); sendButton.click(); const expectedSendPaymentPayload: SendPayment = { uiMessage: UI_MESSAGES.SEND_PAYMENT, outgoingChannel: null, feeLimitType: 'none', feeLimit: null, fromDialog: true, paymentReq: 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq' + '6qcqp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8ss5c' + 'qqqzgqqqqqqqlgqqqqqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq' + '38j0s3hrgpv4eel3' }; expect(storeSpy.calls.all()[0].args[0]).toEqual(sendPayment({ payload: expectedSendPaymentPayload })); expect(storeSpy).toHaveBeenCalledTimes(1); }); it('should reset the component', () => { component.resetData(); expect(component.paymentDecoded).toEqual({}); expect(component.paymentRequest).toEqual(''); expect(component.selectedChannelCtrl.value).toBe(null); expect(component.filteredMinAmtActvChannels).toEqual(component.activeChannels); expect(component.feeLimit).toBe(null); expect(component.selFeeLimitType).toEqual(FEE_LIMIT_TYPES[0]); expect(component.advancedTitle).toEqual('Advanced Options'); expect(component.zeroAmtInvoice).toEqual(false); expect(component.paymentReq.control.errors).toBe(null); expect(component.paymentError).toEqual(''); expect(component.paymentDecodedHint).toEqual(''); }); it('should update title on advanced panel toggle', () => { component.onAdvancedPanelToggle(true, false); expect(component.advancedTitle).toContain('Advanced Options | '); component.onAdvancedPanelToggle(false, false); expect(component.advancedTitle).toContain('Advanced Options'); }); it('should clear the payment decoded amount on amount change', () => { component.paymentDecoded.num_satoshis = '400'; component.onAmountChange({}); expect(component.paymentDecoded.num_satoshis).toBeFalsy(); }); it('should return from decode payment when pay request length is less than hundred ', () => { component.onPaymentRequestEntry('lntb4u1psvdzaypp55'); expect(component.paymentDecodedHint).toEqual(''); }); it('should decode payment when pay request is for the zero amount invoice', () => { component.zeroAmtInvoice = false; component.paymentDecoded = {}; component.paymentRequest = 'lntb1ps8neg8pp5u897fhxxzg068jzt59tgqe458jt7srjtd6k93x4t9ts3hqdkd2nsdpj23jhxarfdenjq3tdwp68jgzfdemx76trv5sxvmm' + 'jypxyu3pqxvxqyd9uqcqp2sp5feg8wftf3fasmp2fe86kehyqfat2xcrjvunare7rrn28yjdrw8yqrzjq2m42d94jc8fxjzq675cmhr7fpjg0vr6238xutxp9p78yeaucwjfjxgpc' + 'uqqqxsqqyqqqqlgqqqqqqgq9q9qy9qsqwf6a4w9uqthm3aslwt03ucqt03e8j2atxrmt022d5kaw65cmqc3pnghz5xmsh2tlz9syhaulrxtwmvh3gdx9j33gec6yrycwh2g05qgqdnftgk'; component.onPaymentRequestEntry(component.paymentRequest); fixture.detectChanges(); expect(component.zeroAmtInvoice).toBe(true); expect(component.paymentDecodedHint).toEqual('Memo: Testing Empty Invoice for LND 3'); expect(component.filteredMinAmtActvChannels).toEqual(component.activeChannels); }); it('should NOT send payment when pay request is for zero amount invoice AND amount is not specified', () => { spyOn(component, 'sendPayment').and.callThrough(); component.onPaymentRequestEntry('lntb1ps8neg8pp5u897fhxxzg068jzt59tgqe458jt7srjtd6k93x4t9ts3hqdkd2nsdpj23jhxarfdenjq3tdwp68jgzfdemx76tr' + 'v5sxvmmjypxyu3pqxvxqyd9uqcqp2sp5feg8wftf3fasmp2fe86kehyqfat2xcrjvunare7rrn28yjdrw8yqrzjq2m42d94jc8fxjzq675cmhr7fpjg0vr6238xutxp9p78yeau' + 'cwjfjxgpcuqqqxsqqyqqqqlgqqqqqqgq9q9qy9qsqwf6a4w9uqthm3aslwt03ucqt03e8j2atxrmt022d5kaw65cmqc3pnghz5xmsh2tlz9syhaulrxtwmvh3gdx9j33gec6yryc' + 'wh2g05qgqdnftgk'); expect(component.zeroAmtInvoice).toBe(true); expect(component.paymentDecodedHint).toEqual('Memo: Testing Empty Invoice for LND 3'); expect(component.filteredMinAmtActvChannels).toEqual(component.activeChannels); expect(component.paymentAmount).toBeNull(); component.onSendPayment(); expect(component.sendPayment).not.toHaveBeenCalled(); component.paymentAmount = 100; component.onSendPayment(); expect(component.sendPayment).toHaveBeenCalled(); }); it('should decode payment when pay request changed and fiat conversion is true', () => { const updatedSelNode: SelNodeChild = JSON.parse(JSON.stringify(component.selNode)); updatedSelNode.fiatConversion = true; updatedSelNode.currencyUnits = ['BTC', 'SAT', 'USD']; Object.defineProperty(component, 'selNode', { value: updatedSelNode }); component.onPaymentRequestEntry('lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3' + 'txvejkxarnyq6qcqp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvr' + 'c8ss5cqqqzgqqqqqqqlgqqqqqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630a' + 'q38j0s3hrgpv4eel3'); expect(component.paymentDecodedHint).toEqual('Sending: 400 Sats (USD 0.13) | Memo: Testing ngrx Effects 4'); }); it('should decode payment when pay request changed and fiat conversion is false', () => { component.onPaymentRequestEntry('lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3tx' + 'vejkxarnyq6qcqp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8s' + 's5cqqqzgqqqqqqqlgqqqqqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq38j' + '0s3hrgpv4eel3'); expect(component.paymentDecodedHint).toEqual('Sending: 400 Sats | Memo: Testing ngrx Effects 4'); }); it('should throw an error from decode payment when pay request is not found', () => { component.onPaymentRequestEntry('p555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq6qcqp2sp3434dsfdsf'); expect(component.paymentDecodedHint).toEqual('ERROR: Request Failed!'); }); it('should convert Sats to USD by calling convertCurrency method from CommonService', () => { commonService.convertCurrency(200000, 'Sats', 'OTHER', 'USD', true).subscribe((data) => { expect(data.OTHER).toBe(66.87764); }); }); it('should send the payment when the payment is decoded', () => { spyOn(component, 'sendPayment').and.callThrough(); component.zeroAmtInvoice = true; component.paymentAmount = 600; component.paymentRequest = 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq6qcq' + 'p2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8ss5cqqqzgqqqqqqqlgqqqqqqg' + 'q9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq38j0s3hrgpv4eel3'; component.paymentDecoded = { destination: '031844beb16bf8dd8c7bc30588b8c37b36e62b71c6e812e9b6d976c0a57e151be2', payment_hash: 'a53968453af7ab6fc58d229a91bdf23d7c12' + '1963067f06cf02e1a7b581852c07', timestamp: '1623624612', expiry: '3600', description: 'Testing ngrx Effects 4', description_hash: '', fallback_addr: '', cltv_expiry: '10', route_hints: [{ hop_hints: [{ node_id: '028ec70462207b57e3d4d9332d9e0aee676c92d89b7c9fb0850fc2a24814d4d83c', chan_id: '2166413939696009216', fee_base_msat: 1000, fee_proportional_millionths: 1, cltv_expiry_delta: 40 }] }], payment_addr: 'NIXNBEqCTmqw89joe0m71Z9MrkkBcF1t1ri+9BZehKw=', num_msat: '400000', features: { 9: { name: 'tlv-onion', is_required: false, is_known: true }, 15: { name: 'payment-addr', is_required: false, is_known: true }, 17: { name: 'multi-path-payments', is_required: false, is_known: true } } }; component.onSendPayment(); expect(component.paymentDecoded.num_satoshis).toEqual('400'); expect(component.zeroAmtInvoice).toBe(false); expect(component.sendPayment).toHaveBeenCalledTimes(1); }); it('should decode the payment when send payment clicked but payment is not decoded yet', () => { const onPaymentRequestEntrySpy = spyOn(component, 'onPaymentRequestEntry').and.callThrough(); component.zeroAmtInvoice = true; component.paymentAmount = 600; component.paymentRequest = 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq6qc' + 'qp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8ss5cqqqzgqqqqqqqlgqqq' + 'qqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq38j0s3hrgpv4eel3'; component.paymentDecoded = {}; component.onSendPayment(); expect(component.paymentDecoded.num_satoshis).toEqual('400'); expect(component.zeroAmtInvoice).toBe(false); expect(onPaymentRequestEntrySpy).toHaveBeenCalledTimes(1); }); it('should decode the zero amount payment when send payment clicked but payment is not decoded yet', () => { const onPaymentRequestEntrySpy = spyOn(component, 'onPaymentRequestEntry').and.callThrough(); component.zeroAmtInvoice = false; component.paymentRequest = 'lntb1ps8neg8pp5u897fhxxzg068jzt59tgqe458jt7srjtd6k93x4t9ts3hqdkd2nsdpj23jhxarfdenjq3tdwp68jgzfdemx76trv5sxv' + 'mmjypxyu3pqxvxqyd9uqcqp2sp5feg8wftf3fasmp2fe86kehyqfat2xcrjvunare7rrn28yjdrw8yqrzjq2m42d94jc8fxjzq675cmhr7fpjg0vr6238xutxp9p78yeaucwjfjx' + 'gpcuqqqxsqqyqqqqlgqqqqqqgq9q9qy9qsqwf6a4w9uqthm3aslwt03ucqt03e8j2atxrmt022d5kaw65cmqc3pnghz5xmsh2tlz9syhaulrxtwmvh3gdx9j33gec6yrycwh2g05qg' + 'qdnftgk'; component.paymentDecoded = {}; component.onSendPayment(); fixture.detectChanges(); expect(component.zeroAmtInvoice).toBe(true); expect(component.filteredMinAmtActvChannels).toEqual(component.activeChannels); expect(onPaymentRequestEntrySpy).toHaveBeenCalledTimes(1); }); it('should complete subscriptions on ngOnDestroy', () => { const spy = spyOn(component['unSubs'][1], 'complete'); component.ngOnDestroy(); expect(spy).toHaveBeenCalledTimes(1); }); afterEach(() => { TestBed.resetTestingModule(); }); });