|
| 1 | +/** |
| 2 | + * Tests for SubscriptionEventPollerService: |
| 3 | + * - feature flag disables polling |
| 4 | + * - stale / disconnected RPC is handled gracefully (no throw) |
| 5 | + * - typed methods are called (no any-cast) |
| 6 | + */ |
| 7 | +import { SubscriptionEventPollerService } from './subscription-event-poller.service'; |
| 8 | +import { RequestContextService } from '../../common/services/request-context.service'; |
| 9 | + |
| 10 | +function makePoller(overrides: { |
| 11 | + pollerEnabled?: boolean; |
| 12 | + getLatestLedgerSequence?: () => Promise<number>; |
| 13 | + getNetworkEvents?: () => Promise<any>; |
| 14 | + checkpoint?: number; |
| 15 | +}) { |
| 16 | + const requestContext = new RequestContextService(); |
| 17 | + |
| 18 | + const featureFlags = { |
| 19 | + isSorobanPollerEnabled: jest.fn().mockReturnValue(overrides.pollerEnabled ?? true), |
| 20 | + }; |
| 21 | + |
| 22 | + const indexRepo = { |
| 23 | + getLatestCheckpoint: jest.fn().mockResolvedValue(overrides.checkpoint ?? 0), |
| 24 | + findByEventId: jest.fn().mockResolvedValue(null), |
| 25 | + upsertEvent: jest.fn().mockResolvedValue({ id: '1', eventType: 'subscribed', fan: 'A', creator: 'B', planId: 0, expiryUnix: 9999999999 }), |
| 26 | + }; |
| 27 | + |
| 28 | + const sorobanRpc = { |
| 29 | + getLatestLedgerSequence: jest.fn().mockImplementation( |
| 30 | + overrides.getLatestLedgerSequence ?? (() => Promise.resolve(0)), |
| 31 | + ), |
| 32 | + getNetworkEvents: jest.fn().mockImplementation( |
| 33 | + overrides.getNetworkEvents ?? (() => Promise.resolve({ events: [], startLedger: 0, latestLedger: 0 })), |
| 34 | + ), |
| 35 | + }; |
| 36 | + |
| 37 | + const eventBus = { publish: jest.fn() }; |
| 38 | + |
| 39 | + const svc = new (SubscriptionEventPollerService as any)( |
| 40 | + { get: () => 'CONTRACT_ID' }, |
| 41 | + indexRepo, |
| 42 | + eventBus, |
| 43 | + sorobanRpc, |
| 44 | + requestContext, |
| 45 | + featureFlags, |
| 46 | + ) as SubscriptionEventPollerService; |
| 47 | + |
| 48 | + (svc as any).contractId = 'CONTRACT_ID'; |
| 49 | + |
| 50 | + return { svc, sorobanRpc, indexRepo, featureFlags, eventBus }; |
| 51 | +} |
| 52 | + |
| 53 | +describe('SubscriptionEventPollerService – feature flag', () => { |
| 54 | + it('skips poll when isSorobanPollerEnabled returns false', async () => { |
| 55 | + const { svc, sorobanRpc } = makePoller({ pollerEnabled: false }); |
| 56 | + await svc.poll(); |
| 57 | + expect(sorobanRpc.getLatestLedgerSequence).not.toHaveBeenCalled(); |
| 58 | + }); |
| 59 | + |
| 60 | + it('proceeds when isSorobanPollerEnabled returns true', async () => { |
| 61 | + const { svc, sorobanRpc } = makePoller({ pollerEnabled: true, checkpoint: 5 }); |
| 62 | + // latest == checkpoint → early return, but RPC was still called |
| 63 | + sorobanRpc.getLatestLedgerSequence.mockResolvedValue(5); |
| 64 | + await svc.poll(); |
| 65 | + expect(sorobanRpc.getLatestLedgerSequence).toHaveBeenCalledTimes(1); |
| 66 | + }); |
| 67 | +}); |
| 68 | + |
| 69 | +describe('SubscriptionEventPollerService – stale / disconnected RPC', () => { |
| 70 | + it('does not throw when getLatestLedgerSequence rejects', async () => { |
| 71 | + const { svc, sorobanRpc } = makePoller({ |
| 72 | + getLatestLedgerSequence: () => Promise.reject(new Error('connection refused')), |
| 73 | + }); |
| 74 | + await expect(svc.poll()).resolves.toBeUndefined(); |
| 75 | + expect(sorobanRpc.getNetworkEvents).not.toHaveBeenCalled(); |
| 76 | + }); |
| 77 | + |
| 78 | + it('does not throw when getNetworkEvents rejects mid-page', async () => { |
| 79 | + const { svc, sorobanRpc } = makePoller({ |
| 80 | + getLatestLedgerSequence: () => Promise.resolve(100), |
| 81 | + getNetworkEvents: () => Promise.reject(new Error('rpc timeout')), |
| 82 | + checkpoint: 50, |
| 83 | + }); |
| 84 | + await expect(svc.poll()).resolves.toBeUndefined(); |
| 85 | + }); |
| 86 | + |
| 87 | + it('uses typed getLatestLedgerSequence (not any-cast)', async () => { |
| 88 | + const { svc, sorobanRpc } = makePoller({ checkpoint: 10 }); |
| 89 | + sorobanRpc.getLatestLedgerSequence.mockResolvedValue(10); // no new ledgers |
| 90 | + await svc.poll(); |
| 91 | + // Verify the real method was called, not a dynamic property |
| 92 | + expect(sorobanRpc.getLatestLedgerSequence).toHaveBeenCalledTimes(1); |
| 93 | + }); |
| 94 | +}); |
0 commit comments