Files
bitchat/bitchatTests/Sync/GossipSyncBoardTests.swift
T
jackandClaude Fable 5 e0e90af9fd Integration test fixes: reconcile SyncTypeFlags phantom-bit assertions and sync-round counts
- SyncTypeFlags phantom-bit tests used bits 8/9/10 as unmapped examples;
  board/prekey/group now map those, so use bits 11+ (still unmapped).
- rawValueInitNormalizesPhantomBits: highest known bit is now 10, so the
  flags serialize to 2 bytes, not 1.
- Board/fragment/announce sync tests silence the prekey round (added by the
  prekeys feature; default 60s schedule fires at the maintenance barrier).
- DiagnosticsMockContext: conform to the CommandContextProvider members added
  by private-groups (group*) and cashu-chips (sendPublicMessage).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 22:38:45 +02:00

134 lines
5.2 KiB
Swift

//
// GossipSyncBoardTests.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import BitFoundation
import Foundation
import Testing
@testable import bitchat
/// Board posts ride gossip sync through a provider that queries the board
/// store, so retention (expiry, tombstones, caps) has a single owner.
struct GossipSyncBoardTests {
private let myPeerID = PeerID(str: "0102030405060708")
private func makeBoardPacket(timestamp: UInt64) throws -> BitchatPacket {
BitchatPacket(
type: MessageType.boardPost.rawValue,
senderID: try #require(Data(hexString: "aabbccddeeff0011")),
recipientID: nil,
timestamp: timestamp,
payload: Data([0x42]),
signature: nil,
ttl: 7
)
}
private func quietConfig() -> GossipSyncManager.Config {
var config = GossipSyncManager.Config()
config.messageSyncIntervalSeconds = 0
config.fragmentSyncIntervalSeconds = 0
config.fileTransferSyncIntervalSeconds = 0
// Silence the prekey round (added by the prekeys feature; groupMessage
// rides the message schedule, already off) so board is isolated.
config.prekeyBundleSyncIntervalSeconds = 0
return config
}
@Test func boardRequestIsServedFromProvider() async throws {
let manager = GossipSyncManager(myPeerID: myPeerID, config: quietConfig(), requestSyncManager: RequestSyncManager())
let delegate = RecordingBoardDelegate()
manager.delegate = delegate
let boardPacket = try makeBoardPacket(timestamp: UInt64(Date().timeIntervalSince1970 * 1000))
manager.boardPacketsProvider = { return [boardPacket] }
let request = RequestSyncPacket(p: 4, m: 1, data: Data(), types: .board)
manager.handleRequestSync(from: PeerID(str: "FFFFFFFFFFFFFFFF"), request: request)
try await TestHelpers.waitFor({ delegate.packets.count == 1 }, timeout: TestConstants.shortTimeout)
let sent = try #require(delegate.packets.first)
#expect(sent.type == MessageType.boardPost.rawValue)
#expect(sent.isRSR)
}
@Test func nonBoardRequestDoesNotServeBoardPackets() async throws {
let manager = GossipSyncManager(myPeerID: myPeerID, config: quietConfig(), requestSyncManager: RequestSyncManager())
let delegate = RecordingBoardDelegate()
manager.delegate = delegate
let boardPacket = try makeBoardPacket(timestamp: UInt64(Date().timeIntervalSince1970 * 1000))
manager.boardPacketsProvider = { return [boardPacket] }
let request = RequestSyncPacket(p: 4, m: 1, data: Data(), types: .publicMessages)
manager.handleRequestSync(from: PeerID(str: "FFFFFFFFFFFFFFFF"), request: request)
// Follow with a board request; only its response should arrive, which
// also proves the first request produced nothing.
let boardRequest = RequestSyncPacket(p: 4, m: 1, data: Data(), types: .board)
manager.handleRequestSync(from: PeerID(str: "FFFFFFFFFFFFFFFF"), request: boardRequest)
try await TestHelpers.waitFor({ delegate.packets.count == 1 }, timeout: TestConstants.shortTimeout)
#expect(delegate.packets.count == 1)
#expect(delegate.packets.first?.type == MessageType.boardPost.rawValue)
}
@Test func maintenanceEmitsBoardRoundOnlyWithProvider() throws {
var config = quietConfig()
config.boardSyncIntervalSeconds = 1
// Without a provider the board schedule stays silent.
let unwired = GossipSyncManager(myPeerID: myPeerID, config: config, requestSyncManager: RequestSyncManager())
let unwiredDelegate = RecordingBoardDelegate()
unwired.delegate = unwiredDelegate
unwired._performMaintenanceSynchronously(now: Date())
#expect(unwiredDelegate.packets.isEmpty)
// With a provider, maintenance sends a board-typed request.
let manager = GossipSyncManager(myPeerID: myPeerID, config: config, requestSyncManager: RequestSyncManager())
let delegate = RecordingBoardDelegate()
manager.delegate = delegate
manager.boardPacketsProvider = { return [] }
manager._performMaintenanceSynchronously(now: Date())
#expect(delegate.packets.count == 1)
let payload = try #require(delegate.packets.first?.payload)
let request = try #require(RequestSyncPacket.decode(from: payload))
#expect(request.types == .board)
}
}
private final class RecordingBoardDelegate: GossipSyncManager.Delegate {
private let lock = NSLock()
private var _packets: [BitchatPacket] = []
var packets: [BitchatPacket] {
lock.lock()
defer { lock.unlock() }
return _packets
}
func sendPacket(_ packet: BitchatPacket) {
lock.lock()
_packets.append(packet)
lock.unlock()
}
func sendPacket(to peerID: PeerID, packet: BitchatPacket) {
lock.lock()
_packets.append(packet)
lock.unlock()
}
func signPacketForBroadcast(_ packet: BitchatPacket) -> BitchatPacket {
packet
}
func getConnectedPeers() -> [PeerID] {
[]
}
}