📄 src/web3/subscription/contract.test.ts
D-OPEN SOVEREIGN
1import { describe, it, expect, vi, beforeEach } from 'vitest';
2
3const mocks = vi.hoisted(() => ({
4  getAddress: vi.fn(),
5  getAbi: vi.fn(),
6}));
7
8vi.mock('../registry', () => ({
9  addressLoader: {
10    getAddress: mocks.getAddress,
11    getAbi: mocks.getAbi,
12  },
13}));
14
15import { multicallGetSubscriptions, multicallGetSubscriptionCounts } from './contract';
16
17const FAKE_ABI = [{ type: 'function', name: 'getSubscriptions' }];
18const SM_ADDRESS = '0xSubMgr00000000000000000000000000000001';
19
20function fakeClient(overrides: any = {}) {
21  return {
22    chainId: 1116,
23    environment: 'production',
24    publicClient: { multicall: vi.fn() },
25    subscriptionManager: {
26      getSubscriptions: vi.fn(),
27      getSubscriptionCount: vi.fn(),
28    },
29    ...overrides,
30  };
31}
32
33beforeEach(() => {
34  mocks.getAddress.mockReturnValue(SM_ADDRESS);
35  mocks.getAbi.mockReturnValue(FAKE_ABI);
36});
37
38describe('multicallGetSubscriptions', () => {
39  it('returns an empty map without calling multicall for an empty userIds list', async () => {
40    const client = fakeClient();
41    const result = await multicallGetSubscriptions(client as any, []);
42    expect(result.size).toBe(0);
43    expect(client.publicClient.multicall).not.toHaveBeenCalled();
44  });
45
46  it('maps successful multicall results to number[] per userId', async () => {
47    const client = fakeClient();
48    (client.publicClient.multicall as any).mockResolvedValue([
49      { status: 'success', result: [2, 3] },
50      { status: 'success', result: [] },
51    ]);
52
53    const result = await multicallGetSubscriptions(client as any, [1, 2]);
54    expect(result.get(1)).toEqual([2, 3]);
55    expect(result.get(2)).toEqual([]);
56  });
57
58  it('defaults a failed individual multicall entry to an empty array rather than dropping the userId', async () => {
59    const client = fakeClient();
60    (client.publicClient.multicall as any).mockResolvedValue([
61      { status: 'failure', error: new Error('revert') },
62      { status: 'success', result: [5] },
63    ]);
64
65    const result = await multicallGetSubscriptions(client as any, [1, 2]);
66    expect(result.get(1)).toEqual([]);
67    expect(result.get(2)).toEqual([5]);
68  });
69
70  it('falls back to sequential per-user reads when the address/ABI binding is unavailable', async () => {
71    mocks.getAddress.mockReturnValue(undefined);
72    const client = fakeClient();
73    client.subscriptionManager.getSubscriptions.mockImplementation(async (id: number) => (id === 1 ? [9] : []));
74
75    const result = await multicallGetSubscriptions(client as any, [1, 2]);
76    expect(client.publicClient.multicall).not.toHaveBeenCalled();
77    expect(result.get(1)).toEqual([9]);
78    expect(result.get(2)).toEqual([]);
79  });
80
81  it('falls back to sequential reads when the multicall RPC call itself throws', async () => {
82    const client = fakeClient();
83    (client.publicClient.multicall as any).mockRejectedValue(new Error('multicall unsupported on this chain'));
84    client.subscriptionManager.getSubscriptions.mockImplementation(async (id: number) => (id === 1 ? [9] : []));
85
86    const result = await multicallGetSubscriptions(client as any, [1, 2]);
87    expect(result.get(1)).toEqual([9]);
88    expect(result.get(2)).toEqual([]);
89  });
90
91  it('sequential fallback defaults a per-user error to an empty array', async () => {
92    mocks.getAddress.mockReturnValue(undefined);
93    const client = fakeClient();
94    client.subscriptionManager.getSubscriptions.mockRejectedValue(new Error('rpc down'));
95
96    const result = await multicallGetSubscriptions(client as any, [1]);
97    expect(result.get(1)).toEqual([]);
98  });
99});
100
101describe('multicallGetSubscriptionCounts', () => {
102  it('returns an empty map without calling multicall for an empty userIds list', async () => {
103    const client = fakeClient();
104    const result = await multicallGetSubscriptionCounts(client as any, []);
105    expect(result.size).toBe(0);
106    expect(client.publicClient.multicall).not.toHaveBeenCalled();
107  });
108
109  it('maps successful multicall results to Number(count) per userId', async () => {
110    const client = fakeClient();
111    (client.publicClient.multicall as any).mockResolvedValue([
112      { status: 'success', result: 3n },
113      { status: 'success', result: 0n },
114    ]);
115
116    const result = await multicallGetSubscriptionCounts(client as any, [1, 2]);
117    expect(result.get(1)).toBe(3);
118    expect(result.get(2)).toBe(0);
119  });
120
121  it('defaults a failed individual multicall entry to 0', async () => {
122    const client = fakeClient();
123    (client.publicClient.multicall as any).mockResolvedValue([
124      { status: 'failure', error: new Error('revert') },
125    ]);
126
127    const result = await multicallGetSubscriptionCounts(client as any, [1]);
128    expect(result.get(1)).toBe(0);
129  });
130
131  it('falls back to sequential reads when the binding is unavailable, converting bigint results to Number', async () => {
132    mocks.getAbi.mockReturnValue(null);
133    const client = fakeClient();
134    client.subscriptionManager.getSubscriptionCount.mockResolvedValue(7n);
135
136    const result = await multicallGetSubscriptionCounts(client as any, [1]);
137    expect(client.publicClient.multicall).not.toHaveBeenCalled();
138    expect(result.get(1)).toBe(7);
139  });
140
141  it('sequential fallback defaults a per-user error to 0', async () => {
142    mocks.getAbi.mockReturnValue(null);
143    const client = fakeClient();
144    client.subscriptionManager.getSubscriptionCount.mockRejectedValue(new Error('rpc down'));
145
146    const result = await multicallGetSubscriptionCounts(client as any, [1]);
147    expect(result.get(1)).toBe(0);
148  });
149});
150