Files
suaveplan/docs/old-network-planning/test-failures-analysis.md
2026-05-27 23:29:53 +01:00

280 lines
8.0 KiB
Markdown

# Analysis of Test Failures in Network Library
## Failing Tests Overview
When running `bun test libs/network/src`, these tests fail:
```sql
✗ MemoryTransport > Simulated network conditions > should simulate connection delay
✗ MemoryTransport > Simulated network conditions > should simulate latency
✗ VotingManager > session management > should get vote history
✗ NetworkNodePhase4 > initialization > should register core network reliability calculator
✗ NetworkNodePhase4 > host selection > should select the best host
✗ MessageBatch > Age tracking > should track batch age
✗ TransportTester > testMessageDelivery > should return true when message is delivered
✗ TransportTester > testReliability > should report perfect reliability when all messages delivered
✗ TransportTester > testReliability > should report partial reliability when some messages lost
```
However, when running each test file individually, all tests pass. This indicates an issue with test isolation.
## Root Cause Analysis for Each Failing Test
### 1. MemoryTransport - Connection Delay & Latency Tests
**File:** `libs/network/src/transport/memory-transport.spec.ts`
**Issues:**
- Both tests use real `setTimeout` for time-based assertions
- The `MemoryTransport` class uses a static `_instances` Map that isn't properly reset between tests
- Tests make timing assertions that can be affected by other running tests
**Relevant code:**
```typescript
it('should simulate connection delay', async () => {
const simulationOptions: MemoryTransportSimulationOptions = {
connectionDelay: 50
};
const transport = new MemoryTransport('delayed-transport', {
simulationOptions,
eventEmitter: new NetworkEventEmitter()
});
const startTime = Date.now();
await transport.connect();
const endTime = Date.now();
expect(endTime - startTime).toBeGreaterThanOrEqual(50);
});
```
**Solution:**
1. Update `MemoryTransport` to accept a `TimerController` instance
2. Add a static `resetRegistry` method to clear all instances
3. Refactor tests to use `TimerController` for time simulation
### 2. VotingManager - Vote History Test
**File:** `libs/network/src/voting/voting-manager.spec.ts`
**Issues:**
- The test creates and completes multiple sessions
- Some sessions may be added to history out of order due to timing issues
- Depends on proper cleanup between tests
**Relevant code:**
```typescript
it('should get vote history', () => {
// Create a vote session
const sessionId = votingManager.createVoteSession(VoteType.HOST_SELECTION, 'session');
// Complete the session to move it to history
const session = votingManager.getSession(sessionId);
if (session) {
// Add some votes
(session as any)._testAddVote({
voteId: 'vote1',
voterId: 'node1',
value: { selectedHost: 'node1' },
timestamp: Date.now()
});
// Force completion
(session as any)._testSetStatus(VoteSessionStatus.COMPLETED);
}
// Get vote history
const history = votingManager.getVoteHistory();
// Verify history
expect(history.length).toBe(1);
expect(history[0].id).toBe(sessionId);
});
```
**Solution:**
1. Ensure `VotingManager` properly cleans up between tests
2. Use a consistent timestamp source that can be controlled
3. Improve assertion to check if the expected session is in the history, not just the count
### 3. NetworkNodePhase4 - Initialization and Host Selection
**File:** `libs/network/src/node/network-node-phase4.spec.ts`
**Issues:**
- The test creates multiple extensions in the same test file
- Each extension registers calculators with shared resources
- The host selection test depends on mocked responses that could be affected by other tests
**Relevant code:**
```typescript
it('should register core network reliability calculator', () => {
// Spy on metrics manager
const registerCalculatorSpy = jest.spyOn(nodePhase4.metricsManager, 'registerCalculator');
// Create a new extension to verify registration
const newExtension = new NetworkNodePhase4(mockNode, eventEmitter);
// Verify calculator was registered
expect(registerCalculatorSpy).toHaveBeenCalled();
// Clean up
newExtension.dispose();
});
```
**Solution:**
1. Ensure proper cleanup between tests with more thorough `dispose()` methods
2. Isolate dependencies so that new instances don't interfere with each other
3. Replace event emitter instances in each test
### 4. MessageBatch - Age Tracking
**File:** `libs/network/src/router/message-batch.spec.ts`
**Issues:**
- Uses real `setTimeout` which can be affected by other tests
- Depends on actual timing for assertions
**Relevant code:**
```typescript
it('should track batch age', async () => {
// Create a batch and wait a bit
const initialAge = batch.age;
// Wait at least 10ms
await new Promise(resolve => setTimeout(resolve, 10));
// Age should have increased
expect(batch.age).toBeGreaterThan(initialAge);
expect(batch.age).toBeGreaterThanOrEqual(10);
});
```
**Solution:**
1. Update `MessageBatch` to optionally accept a `TimerController`
2. Refactor test to use controlled time advancement
### 5. TransportTester - Message Delivery and Reliability Tests
**File:** `libs/network/src/transport/pdk/transport-testing.spec.ts`
**Issues:**
- Tests rely on asynchronous callbacks and real timing
- MockTransport implementation uses real `setTimeout`
- Race conditions can cause tests to fail when run concurrently
**Relevant code:**
```typescript
it('should return true when message is delivered', async () => {
// Setup receiver to simulate receiving the message
const testMessage = { test: true };
// We need to simulate the receipt of the message when the sender sends it
sender.on(TransportEvents.MESSAGE_SENT, (msg) => {
receiver.simulateMessageReceived(msg, sender.id);
});
const result = await TransportTester.testMessageDelivery(sender, receiver, testMessage);
expect(result).toBe(true);
});
```
**Solution:**
1. Update `MockTransport` to use `TimerController`
2. Modify `TransportTester` to optionally accept a `TimerController`
3. Use controlled time advancement rather than actual timeouts
## Common Patterns and Solution Strategy
### Core Issues
1. **Shared Static State**: Several classes use static properties or singletons
2. **Real Timing Dependency**: Tests rely on actual setTimeout/setInterval
3. **Timestamp Variations**: Inconsistent approaches to time tracking
### Solution Strategy
1. **Refactor TimerController**:
- Remove the singleton pattern
- Create factory for isolated instances
- Ensure proper cleanup methods
2. **Update Key Classes**:
```typescript
interface TimeOptions {
timerController?: TimerController;
useRealTime?: boolean; // Default to false in tests
}
```
3. **Consistent Cleanup Pattern**:
```typescript
beforeEach(() => {
// Create isolated timer instances for each test
const timer = TimerController.createInstance();
// Reset any static state
MemoryTransport.resetRegistry();
// Create test instances with isolated timers
testInstance = new ClassUnderTest({
timerController: timer
});
});
afterEach(() => {
// Clean up instances
testInstance.dispose();
// Reset any static state again
MemoryTransport.resetRegistry();
});
```
4. **Time-based Assertions**:
```typescript
// Before
const startTime = Date.now();
await someAsyncOperation();
const elapsed = Date.now() - startTime;
expect(elapsed).toBeGreaterThanOrEqual(expectedTime);
// After
const timer = TimerController.createInstance();
const startTime = timer.getCurrentTime();
// Start the operation without await
const operation = someAsyncOperation();
// Advance time
timer.advanceTime(expectedTime + 10);
// Now wait for the operation to complete
await operation;
// Check the operation had the expected effect
// But don't assert based on actual elapsed time
```
By implementing these changes systematically across the test suite, we can ensure proper test isolation and eliminate the timing-related test failures.