mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-27 05:45:19 +00:00
A fifth flake landed on CI while the first guard was in review — GossipSyncBoardTests timing out at 1.03s — and the guard did not catch it, because the deadline was `TestConstants.shortTimeout` rather than a literal. A literals-only scan cannot see a short value behind a symbol, which is the more common way it is written: 81 wait sites used shortTimeout (1s) or defaultTimeout (5s), every one of them below the floor. Rather than guess which of those were safe to raise, all 81 were converted and the suite timed. Runtime went 15s -> 72s, which located the genuine negative waits precisely: ten tests that assert something does *not* happen and therefore always run their deadline out. Measurement instead of a heuristic, since a mis-guess in either direction is invisible — too short reintroduces the flake, too long silently costs a minute a run. Those 28 sites now use `TestConstants.negativeWaitWindow`, a named constant whose doc explains the inverted reasoning: for a negative wait, starvation can only make the assertion *more* likely to hold, so short is correct, and the name states the polarity instead of leaving a bare literal that reads like the mistake. Suite is back to 15.3s. The guard now also rejects `timeout: TestConstants.shortTimeout` and `defaultTimeout`, while accepting `negativeWaitWindow` by name. Re-verified with a canary carrying every banned shape — literal default, both named constants, and an elapsed-time upper bound. All four were flagged with file and line; the suite went green again on removal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
150 lines
5.2 KiB
Swift
150 lines
5.2 KiB
Swift
//
|
|
// ChatViewModelRefactoringTests.swift
|
|
// bitchatTests
|
|
//
|
|
// Pinning tests to characterize ChatViewModel behavior before refactoring.
|
|
// These tests act as a safety net to ensure we don't break existing functionality.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
import BitFoundation
|
|
@testable import bitchat
|
|
|
|
struct ChatViewModelRefactoringTests {
|
|
|
|
// Helper to setup the environment
|
|
@MainActor
|
|
private func makePinnedViewModel() -> (viewModel: ChatViewModel, transport: MockTransport, identity: MockIdentityManager) {
|
|
let keychain = MockKeychain()
|
|
let keychainHelper = MockKeychainHelper()
|
|
let idBridge = NostrIdentityBridge(keychain: keychainHelper)
|
|
let identityManager = MockIdentityManager(keychain)
|
|
let transport = MockTransport()
|
|
|
|
let viewModel = ChatViewModel(
|
|
keychain: keychain,
|
|
idBridge: idBridge,
|
|
identityManager: identityManager,
|
|
transport: transport
|
|
)
|
|
|
|
return (viewModel, transport, identityManager)
|
|
}
|
|
|
|
// MARK: - Command Processor Integration "Pinning"
|
|
|
|
@Test @MainActor
|
|
func command_msg_routesToTransport() async throws {
|
|
let (viewModel, transport, _) = makePinnedViewModel()
|
|
|
|
// Setup: Use simulateConnect so ChatViewModel and UnifiedPeerService are notified
|
|
let peerID = PeerID(str: "0000000000000001")
|
|
transport.simulateConnect(peerID, nickname: "alice")
|
|
|
|
let didResolve = await TestHelpers.waitUntil({ viewModel.getPeerIDForNickname("alice") != nil },
|
|
timeout: TestConstants.settleTimeout)
|
|
#expect(didResolve)
|
|
|
|
// Action: User types /msg command
|
|
viewModel.sendMessage("/msg @alice Hello Private World")
|
|
|
|
let didSend = await TestHelpers.waitUntil({ transport.sentPrivateMessages.count == 1 },
|
|
timeout: TestConstants.settleTimeout)
|
|
#expect(didSend)
|
|
|
|
// Assert:
|
|
// 1. Should NOT go to public transport
|
|
#expect(transport.sentMessages.isEmpty, "Command should not be sent as public message")
|
|
|
|
// 2. Should go to private transport logic
|
|
#expect(transport.sentPrivateMessages.count == 1)
|
|
#expect(transport.sentPrivateMessages.first?.content == "Hello Private World")
|
|
#expect(transport.sentPrivateMessages.first?.peerID == peerID)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func command_block_updatesIdentity() async throws {
|
|
let (viewModel, transport, identity) = makePinnedViewModel()
|
|
|
|
// Setup: Use simulateConnect
|
|
let peerID = PeerID(str: "0000000000000002")
|
|
// Mock the fingerprint so the block command finds it
|
|
transport.peerFingerprints[peerID] = "fingerprint_123"
|
|
transport.simulateConnect(peerID, nickname: "troll")
|
|
|
|
let didResolve = await TestHelpers.waitUntil({ viewModel.getPeerIDForNickname("troll") != nil },
|
|
timeout: TestConstants.settleTimeout)
|
|
#expect(didResolve)
|
|
|
|
// Action
|
|
viewModel.sendMessage("/block @troll")
|
|
|
|
// Assert
|
|
// Verify identity manager was called to block "fingerprint_123"
|
|
let didBlock = await TestHelpers.waitUntil({ identity.isBlocked(fingerprint: "fingerprint_123") },
|
|
timeout: TestConstants.settleTimeout)
|
|
#expect(didBlock)
|
|
}
|
|
|
|
// MARK: - Message Routing Logic
|
|
|
|
@Test @MainActor
|
|
func routing_incomingPrivateMessage_addsToPrivateChats() async {
|
|
let (viewModel, _, _) = makePinnedViewModel()
|
|
let senderID = PeerID(str: "sender_1")
|
|
|
|
// Setup
|
|
let message = BitchatMessage(
|
|
id: "msg_1",
|
|
sender: "bob",
|
|
content: "Secret",
|
|
timestamp: Date(),
|
|
isRelay: false,
|
|
originalSender: nil,
|
|
isPrivate: true,
|
|
recipientNickname: "me",
|
|
senderPeerID: senderID,
|
|
mentions: nil
|
|
)
|
|
|
|
// Action: Simulate incoming private message
|
|
viewModel.didReceiveMessage(message)
|
|
|
|
// Wait for async processing with proper timeout
|
|
let found = await TestHelpers.waitUntil(
|
|
{ viewModel.privateChats[senderID]?.first?.content == "Secret" },
|
|
timeout: TestConstants.settleTimeout
|
|
)
|
|
|
|
// Assert
|
|
#expect(found)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func routing_incomingPublicMessage_addsToPublicTimeline() async {
|
|
let (viewModel, _, _) = makePinnedViewModel()
|
|
let senderID = PeerID(str: "sender_2")
|
|
|
|
// Action
|
|
viewModel.didReceivePublicMessage(
|
|
from: senderID,
|
|
nickname: "charlie",
|
|
content: "Public Hi",
|
|
timestamp: Date(),
|
|
messageID: "msg_2"
|
|
)
|
|
|
|
// Wait for async processing with proper timeout
|
|
let found = await TestHelpers.waitUntil(
|
|
{
|
|
viewModel.publicMessages(for: .mesh).contains(where: { $0.content == "Public Hi" })
|
|
},
|
|
timeout: TestConstants.settleTimeout
|
|
)
|
|
|
|
// Assert
|
|
#expect(found)
|
|
}
|
|
}
|