mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 19:45:22 +00:00
Fix CI media-wipe race, per-link ping rate limiting, and /ping output routing
Three fixes for PR #1377 review: 1. CI flake (sendImage_privateChatProcessesAndTransfersImage): the panicClearAllData / clearCurrentPublicTimeline detached utility-priority tasks delete the real ~/Library/Application Support/files tree, which the test process shares. The wipe fires at a nondeterministic time and raced the sendImage test's JPEG in files/images/outgoing (write then re-read), so prepareImagePacket threw and the test timed out. Both wipes are now skipped under tests (existing TestEnvironment.isRunningTests pattern); this also stops test runs from deleting the developer's real media. 2. Codex P1: ping packets are unsigned, so keying the pong rate limiter on packet.senderID let one connected peer rotate forged sender IDs to bypass the 5-per-10s budget. The limiter now keys on the ingress link (the directly connected peer that delivered the packet); the pong still goes to the claimed sender. Regression test proves rotating senders over one link exhaust one budget (fails 10 vs 5 pongs on the old code). 3. Codex P2: /ping output arrived up to 10s later and was routed from selectedPrivateChatPeer at callback time, misrouting the result after a chat switch. The origin conversation is now captured when the command is issued (CommandOutputDestination) and deferred output is routed there: a DM result lands in the origin chat's history even if deselected, and a mesh-timeline result pins to #mesh instead of the active channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -129,6 +129,44 @@ struct MeshDiagnosticsTests {
|
||||
#expect(context.commandOutputs == ["no reply from alice"])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Test func pingOutputRoutesToConversationWhereCommandWasIssued() async {
|
||||
let context = DiagnosticsMockContext()
|
||||
let alice = PeerID(str: "abcd1234abcd1234")
|
||||
let bob = PeerID(str: "b0b0b0b0b0b0b0b0")
|
||||
context.nicknameToPeerID["alice"] = alice
|
||||
let transport = MockTransport()
|
||||
transport.meshPingResult = MeshPingResult(rttMs: 42, hops: 2)
|
||||
let processor = makeProcessor(context: context, transport: transport)
|
||||
|
||||
// Issue the ping from bob's DM, then switch chats before the async
|
||||
// result lands. The output must follow the origin conversation, not
|
||||
// whatever is selected at callback time.
|
||||
context.selectedPrivateChatPeer = bob
|
||||
_ = processor.process("/ping @alice")
|
||||
context.selectedPrivateChatPeer = nil
|
||||
|
||||
await waitForCommandOutput(context)
|
||||
#expect(context.commandOutputDestinations == [.privateChat(bob)])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@Test func pingIssuedFromPublicTimelineRoutesToMeshTimeline() async {
|
||||
let context = DiagnosticsMockContext()
|
||||
let alice = PeerID(str: "abcd1234abcd1234")
|
||||
context.nicknameToPeerID["alice"] = alice
|
||||
let transport = MockTransport()
|
||||
transport.meshPingResult = MeshPingResult(rttMs: 7, hops: 1)
|
||||
let processor = makeProcessor(context: context, transport: transport)
|
||||
|
||||
_ = processor.process("/ping @alice")
|
||||
// Opening a DM afterwards must not swallow the public-timeline result.
|
||||
context.selectedPrivateChatPeer = alice
|
||||
|
||||
await waitForCommandOutput(context)
|
||||
#expect(context.commandOutputDestinations == [.meshTimeline])
|
||||
}
|
||||
|
||||
// MARK: - /trace
|
||||
|
||||
@MainActor
|
||||
@@ -225,6 +263,7 @@ private final class DiagnosticsMockContext: CommandContextProvider {
|
||||
|
||||
var nicknameToPeerID: [String: PeerID] = [:]
|
||||
private(set) var commandOutputs: [String] = []
|
||||
private(set) var commandOutputDestinations: [CommandOutputDestination] = []
|
||||
|
||||
func getPeerIDForNickname(_ nickname: String) -> PeerID? {
|
||||
nicknameToPeerID[nickname]
|
||||
@@ -241,7 +280,15 @@ private final class DiagnosticsMockContext: CommandContextProvider {
|
||||
func addPublicSystemMessage(_ content: String) {}
|
||||
func toggleFavorite(peerID: PeerID) {}
|
||||
|
||||
func addCommandOutput(_ content: String) {
|
||||
func currentCommandDestination() -> CommandOutputDestination {
|
||||
if let peerID = selectedPrivateChatPeer {
|
||||
return .privateChat(peerID)
|
||||
}
|
||||
return .meshTimeline
|
||||
}
|
||||
|
||||
func addCommandOutput(_ content: String, to destination: CommandOutputDestination) {
|
||||
commandOutputs.append(content)
|
||||
commandOutputDestinations.append(destination)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user