Skip to content

Commit 198ac7c

Browse files
committed
fix tests
1 parent 84e7345 commit 198ac7c

File tree

3 files changed

+258
-5
lines changed

3 files changed

+258
-5
lines changed

packages/ocap-kernel/src/remotes/RemoteHandle.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,10 +649,13 @@ describe('RemoteHandle', () => {
649649
expect(AbortSignal.timeout).toHaveBeenCalledWith(30_000);
650650
expect(mockSignal?.timeoutMs).toBe(30_000);
651651

652+
// Wait for sendRemoteMessage to be called
653+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
654+
652655
// Resolve the redemption to avoid hanging
653656
const sendCall = vi.mocked(mockRemoteComms.sendRemoteMessage).mock
654657
.calls[0];
655-
const sentMessage = JSON.parse(sendCall?.[1] as string);
658+
const sentMessage = JSON.parse(sendCall![1]);
656659
const replyKey = sentMessage.params[1] as string;
657660

658661
await remote.handleRemoteMessage(
@@ -712,10 +715,13 @@ describe('RemoteHandle', () => {
712715
// Start a redemption
713716
const urlPromise = remote.redeemOcapURL(mockOcapURL);
714717

718+
// Wait for sendRemoteMessage to be called
719+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
720+
715721
// Get the reply key that was used
716722
const sendCall = vi.mocked(mockRemoteComms.sendRemoteMessage).mock
717723
.calls[0];
718-
const sentMessage = JSON.parse(sendCall?.[1] as string);
724+
const sentMessage = JSON.parse(sendCall![1]);
719725
const replyKey = sentMessage.params[1] as string;
720726

721727
// Wait for the promise to be set up and event listener registered

packages/ocap-kernel/src/remotes/network.test.ts

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,4 +1699,251 @@ describe('network.initNetwork', () => {
16991699
});
17001700
});
17011701
});
1702+
1703+
describe('message send timeout', () => {
1704+
afterEach(() => {
1705+
vi.restoreAllMocks();
1706+
});
1707+
1708+
it('times out after 10 seconds when write hangs', async () => {
1709+
// Ensure isReconnecting returns false so we actually call writeWithTimeout
1710+
mockReconnectionManager.isReconnecting.mockReturnValue(false);
1711+
1712+
const mockChannel = createMockChannel('peer-1');
1713+
// Make write hang indefinitely - return a new hanging promise each time
1714+
mockChannel.msgStream.write.mockReset();
1715+
mockChannel.msgStream.write.mockImplementation(
1716+
async () =>
1717+
new Promise<never>(() => {
1718+
// Never resolves - simulates hanging write
1719+
}),
1720+
);
1721+
mockConnectionFactory.dialIdempotent.mockResolvedValue(mockChannel);
1722+
1723+
let mockSignal: ReturnType<typeof makeAbortSignalMock> | undefined;
1724+
vi.spyOn(AbortSignal, 'timeout').mockImplementation((ms: number) => {
1725+
mockSignal = makeAbortSignalMock(ms);
1726+
return mockSignal;
1727+
});
1728+
1729+
const { sendRemoteMessage } = await initNetwork('0x1234', {}, vi.fn());
1730+
1731+
const sendPromise = sendRemoteMessage('peer-1', 'test message');
1732+
1733+
// Wait for the promise to be set up and event listener registered
1734+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1735+
1736+
// Verify write was called (proves we're not returning early)
1737+
expect(mockChannel.msgStream.write).toHaveBeenCalled();
1738+
1739+
// Manually trigger the abort to simulate timeout
1740+
mockSignal?.abort();
1741+
1742+
// Wait for the abort handler to execute
1743+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1744+
1745+
// Note: sendRemoteMessage catches the timeout error and returns undefined
1746+
// The timeout error is handled internally and triggers connection loss handling
1747+
expect(await sendPromise).toBeUndefined();
1748+
1749+
// Verify that connection loss handling was triggered
1750+
expect(mockReconnectionManager.startReconnection).toHaveBeenCalled();
1751+
});
1752+
1753+
it('does not timeout if write completes before timeout', async () => {
1754+
const mockChannel = createMockChannel('peer-1');
1755+
mockChannel.msgStream.write.mockResolvedValue(undefined);
1756+
mockConnectionFactory.dialIdempotent.mockResolvedValue(mockChannel);
1757+
1758+
let mockSignal: ReturnType<typeof makeAbortSignalMock> | undefined;
1759+
vi.spyOn(AbortSignal, 'timeout').mockImplementation((ms: number) => {
1760+
mockSignal = makeAbortSignalMock(ms);
1761+
return mockSignal;
1762+
});
1763+
1764+
const { sendRemoteMessage } = await initNetwork('0x1234', {}, vi.fn());
1765+
1766+
const sendPromise = sendRemoteMessage('peer-1', 'test message');
1767+
1768+
// Write resolves immediately, so promise should resolve
1769+
expect(await sendPromise).toBeUndefined();
1770+
1771+
// Verify timeout signal was not aborted
1772+
expect(mockSignal?.aborted).toBe(false);
1773+
});
1774+
1775+
it('handles timeout errors and triggers connection loss handling', async () => {
1776+
// Ensure isReconnecting returns false so we actually call writeWithTimeout
1777+
mockReconnectionManager.isReconnecting.mockReturnValue(false);
1778+
1779+
const mockChannel = createMockChannel('peer-1');
1780+
// Make write hang indefinitely - return a new hanging promise each time
1781+
mockChannel.msgStream.write.mockReset();
1782+
mockChannel.msgStream.write.mockImplementation(
1783+
async () =>
1784+
new Promise<never>(() => {
1785+
// Never resolves - simulates hanging write
1786+
}),
1787+
);
1788+
mockConnectionFactory.dialIdempotent.mockResolvedValue(mockChannel);
1789+
1790+
let mockSignal: ReturnType<typeof makeAbortSignalMock> | undefined;
1791+
vi.spyOn(AbortSignal, 'timeout').mockImplementation((ms: number) => {
1792+
mockSignal = makeAbortSignalMock(ms);
1793+
return mockSignal;
1794+
});
1795+
1796+
const { sendRemoteMessage } = await initNetwork('0x1234', {}, vi.fn());
1797+
1798+
const sendPromise = sendRemoteMessage('peer-1', 'test message');
1799+
1800+
// Wait for the promise to be set up and event listener registered
1801+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1802+
1803+
// Manually trigger the abort to simulate timeout
1804+
mockSignal?.abort();
1805+
1806+
// Wait for the abort handler to execute
1807+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1808+
1809+
// Note: sendRemoteMessage catches the timeout error and returns undefined
1810+
// The timeout error is handled internally and triggers connection loss handling
1811+
expect(await sendPromise).toBeUndefined();
1812+
1813+
// Verify that connection loss handling was triggered
1814+
expect(mockReconnectionManager.startReconnection).toHaveBeenCalled();
1815+
});
1816+
1817+
it('propagates write errors that occur before timeout', async () => {
1818+
// Ensure isReconnecting returns false so we actually call writeWithTimeout
1819+
mockReconnectionManager.isReconnecting.mockReturnValue(false);
1820+
1821+
const mockChannel = createMockChannel('peer-1');
1822+
const writeError = new Error('Write failed');
1823+
mockChannel.msgStream.write.mockRejectedValue(writeError);
1824+
mockConnectionFactory.dialIdempotent.mockResolvedValue(mockChannel);
1825+
1826+
const { sendRemoteMessage } = await initNetwork('0x1234', {}, vi.fn());
1827+
1828+
const sendPromise = sendRemoteMessage('peer-1', 'test message');
1829+
1830+
// Write error occurs immediately
1831+
// Note: sendRemoteMessage catches write errors and returns undefined
1832+
// The error is handled internally and triggers connection loss handling
1833+
expect(await sendPromise).toBeUndefined();
1834+
1835+
// Verify that connection loss handling was triggered
1836+
expect(mockReconnectionManager.startReconnection).toHaveBeenCalled();
1837+
});
1838+
1839+
it('writeWithTimeout uses AbortSignal.timeout with 10 second default', async () => {
1840+
const mockChannel = createMockChannel('peer-1');
1841+
// Make write resolve immediately to avoid timeout
1842+
mockChannel.msgStream.write.mockResolvedValue(undefined);
1843+
mockConnectionFactory.dialIdempotent.mockResolvedValue(mockChannel);
1844+
1845+
let mockSignal: ReturnType<typeof makeAbortSignalMock> | undefined;
1846+
vi.spyOn(AbortSignal, 'timeout').mockImplementation((ms: number) => {
1847+
mockSignal = makeAbortSignalMock(ms);
1848+
return mockSignal;
1849+
});
1850+
1851+
const { sendRemoteMessage } = await initNetwork('0x1234', {}, vi.fn());
1852+
1853+
await sendRemoteMessage('peer-1', 'test message');
1854+
1855+
// Verify AbortSignal.timeout was called with 10 seconds (default)
1856+
expect(AbortSignal.timeout).toHaveBeenCalledWith(10_000);
1857+
expect(mockSignal?.timeoutMs).toBe(10_000);
1858+
});
1859+
1860+
it('error message includes correct timeout duration', async () => {
1861+
// Ensure isReconnecting returns false so we actually call writeWithTimeout
1862+
mockReconnectionManager.isReconnecting.mockReturnValue(false);
1863+
1864+
const mockChannel = createMockChannel('peer-1');
1865+
// Make write hang indefinitely - return a new hanging promise each time
1866+
mockChannel.msgStream.write.mockReset();
1867+
mockChannel.msgStream.write.mockImplementation(
1868+
async () =>
1869+
new Promise<never>(() => {
1870+
// Never resolves - simulates hanging write
1871+
}),
1872+
);
1873+
mockConnectionFactory.dialIdempotent.mockResolvedValue(mockChannel);
1874+
1875+
let mockSignal: ReturnType<typeof makeAbortSignalMock> | undefined;
1876+
vi.spyOn(AbortSignal, 'timeout').mockImplementation((ms: number) => {
1877+
mockSignal = makeAbortSignalMock(ms);
1878+
return mockSignal;
1879+
});
1880+
1881+
const { sendRemoteMessage } = await initNetwork('0x1234', {}, vi.fn());
1882+
1883+
const sendPromise = sendRemoteMessage('peer-1', 'test message');
1884+
1885+
// Wait for the promise to be set up and event listener registered
1886+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1887+
1888+
// Manually trigger the abort to simulate timeout
1889+
mockSignal?.abort();
1890+
1891+
// Wait for the abort handler to execute
1892+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1893+
1894+
// Note: sendRemoteMessage catches the timeout error and returns undefined
1895+
// The timeout error is handled internally
1896+
expect(await sendPromise).toBeUndefined();
1897+
1898+
// Verify that writeWithTimeout was called (the timeout error message includes the duration)
1899+
expect(mockChannel.msgStream.write).toHaveBeenCalled();
1900+
});
1901+
1902+
it('handles multiple concurrent writes with timeout', async () => {
1903+
// Ensure isReconnecting returns false so we actually call writeWithTimeout
1904+
mockReconnectionManager.isReconnecting.mockReturnValue(false);
1905+
1906+
const mockChannel = createMockChannel('peer-1');
1907+
// Make write hang indefinitely - return a new hanging promise each time
1908+
mockChannel.msgStream.write.mockReset();
1909+
mockChannel.msgStream.write.mockImplementation(
1910+
async () =>
1911+
new Promise<never>(() => {
1912+
// Never resolves - simulates hanging write
1913+
}),
1914+
);
1915+
mockConnectionFactory.dialIdempotent.mockResolvedValue(mockChannel);
1916+
1917+
const mockSignals: ReturnType<typeof makeAbortSignalMock>[] = [];
1918+
vi.spyOn(AbortSignal, 'timeout').mockImplementation((ms: number) => {
1919+
const signal = makeAbortSignalMock(ms);
1920+
mockSignals.push(signal);
1921+
return signal;
1922+
});
1923+
1924+
const { sendRemoteMessage } = await initNetwork('0x1234', {}, vi.fn());
1925+
1926+
const sendPromise1 = sendRemoteMessage('peer-1', 'message 1');
1927+
const sendPromise2 = sendRemoteMessage('peer-1', 'message 2');
1928+
1929+
// Wait for the promises to be set up and event listeners registered
1930+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1931+
1932+
// Manually trigger the abort on all signals to simulate timeout
1933+
for (const signal of mockSignals) {
1934+
signal.abort();
1935+
}
1936+
1937+
// Wait for the abort handlers to execute
1938+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()));
1939+
1940+
// Note: sendRemoteMessage catches the timeout error and returns undefined
1941+
// The timeout error is handled internally
1942+
expect(await sendPromise1).toBeUndefined();
1943+
expect(await sendPromise2).toBeUndefined();
1944+
1945+
// Verify that writeWithTimeout was called for both messages
1946+
expect(mockChannel.msgStream.write).toHaveBeenCalledTimes(2);
1947+
});
1948+
});
17021949
});

vitest.config.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export default defineConfig({
106106
'packages/kernel-platforms/**': {
107107
statements: 99.38,
108108
functions: 100,
109-
branches: 96.25,
109+
branches: 96.2,
110110
lines: 99.38,
111111
},
112112
'packages/kernel-rpc-methods/**': {
@@ -159,8 +159,8 @@ export default defineConfig({
159159
},
160160
'packages/ocap-kernel/**': {
161161
statements: 96.53,
162-
functions: 98.53,
163-
branches: 97.73,
162+
functions: 98.54,
163+
branches: 97.59,
164164
lines: 96.53,
165165
},
166166
'packages/omnium-gatherum/**': {

0 commit comments

Comments
 (0)