Files
ef848857b7 Remove dead code found by full Periphery audit; add scan config + advisory CI (#1410)
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so
platform-specific code is never touched), with test targets indexed and
the share extension built. 277 dead declarations removed or demoted:
dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed-
feature remnants (autocomplete command suggestions, back-swipe tuning,
MediaSendError, GeohashParticipantTracker), unused Tor dormancy
bindings, assign-only properties, unused parameters (renamed to _), and
redundant public accessibility. 13 orphaned localization keys deleted
across all 29 locales (old pre-#1392 location-notes UI, app_info
warnings).

Two real tests were flagged as unused because they never ran: Swift
Testing methods missing @Test (NostrProtocolTests.
testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests.
testAssemblesCompressedLargeFrame). Re-armed both; they pass.

Deliberately kept, now recorded in .periphery.baseline.json: iOS-only
code invisible to the CI macOS scan, C FFI signatures, keep-alive
NWPathMonitor reference, InboundEventKey.eventID (dedup semantics),
wifiBulk capability bit (reserved for Wi-Fi bulk work, used by
BitFoundation package tests), and the String secureClear cluster
(exercised by package tests).

New: .periphery.yml config and an advisory Dead Code CI job (mirrors
the SwiftLint precedent from #1361) that fails on findings not in the
committed baseline.

Verified: full macOS app suite, BitFoundation (119) and BitLogger (13)
package tests green; periphery scan --strict exits clean.

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:24:19 +02:00

133 lines
4.0 KiB
Swift

//
// TestHelpers.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import Foundation
import CryptoKit
import BitFoundation
@testable import bitchat
final class TestHelpers {
// MARK: - Message Creation
static func createTestMessage(
content: String = TestConstants.testMessage1,
sender: String = TestConstants.testNickname1,
senderPeerID: PeerID = PeerID(str: UUID().uuidString),
isPrivate: Bool = false,
recipientNickname: String? = nil,
mentions: [String]? = nil
) -> BitchatMessage {
return BitchatMessage(
id: UUID().uuidString,
sender: sender,
content: content,
timestamp: Date(),
isRelay: false,
originalSender: nil,
isPrivate: isPrivate,
recipientNickname: recipientNickname,
senderPeerID: senderPeerID,
mentions: mentions
)
}
static func createTestPacket(
type: UInt8 = 0x01,
senderID: PeerID = PeerID(str: UUID().uuidString),
recipientID: PeerID? = nil,
payload: Data = Data("test payload".utf8),
signature: Data? = nil,
ttl: UInt8 = 3
) -> BitchatPacket {
return BitchatPacket(
type: type,
senderID: Data(senderID.id.utf8),
recipientID: recipientID?.id.data(using: .utf8),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: payload,
signature: signature,
ttl: ttl
)
}
// MARK: - Data Generation
static func generateRandomData(length: Int) -> Data {
var data = Data(count: length)
_ = data.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, length, bytes.baseAddress!)
}
return data
}
// MARK: - Async Helpers
static func waitFor(_ condition: @escaping () -> Bool, timeout: TimeInterval = TestConstants.defaultTimeout) async throws {
let start = Date()
while !condition() {
if Date().timeIntervalSince(start) > timeout {
throw TestError.timeout
}
try await sleep(0.01)
}
}
@MainActor
static func waitUntil(
_ condition: @escaping () -> Bool,
timeout: TimeInterval = TestConstants.defaultTimeout,
pollInterval: TimeInterval = 0.01
) async -> Bool {
let start = Date()
while !condition() {
if Date().timeIntervalSince(start) > timeout {
return condition()
}
try? await sleep(pollInterval)
}
return true
}
}
enum TestError: Error {
case timeout
}
// MARK: - Private chat seeding (ConversationStore migration)
extension ChatViewModel {
/// Test-only replacement for the deleted `privateChats` setter: seeds a
/// peer's chat through the single-writer `ConversationStore` intents
/// (upsert keeps re-seeding with updated copies working the way the old
/// dictionary assignment did).
@MainActor
func seedPrivateChat(_ messages: [BitchatMessage], for peerID: PeerID) {
_ = conversations.conversation(for: .directPeer(peerID))
for message in messages {
conversations.upsertByID(message, in: .directPeer(peerID))
}
}
/// Test-only replacement for the deleted `messages` setter: seeds a
/// public channel's conversation through the single-writer
/// `ConversationStore` intents (upsert keeps re-seeding with updated
/// copies working the way the old array assignment did).
@MainActor
func seedPublicMessages(_ messages: [BitchatMessage], for channel: ChannelID = .mesh) {
for message in messages {
conversations.upsertByID(message, in: ConversationID(channelID: channel))
}
}
}
func sleep(_ seconds: TimeInterval) async throws {
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
}