12 · Tests
Tests/SophaxChatCoreTests/RelayRouterTests.swift
117 lines · Swift
1// RelayRouterTests.swift
2// SophaxChatCoreTests
3//
4// Unit tests for RelayRouter — deduplication, TTL enforcement, LRU eviction.
5
6import Testing
7import Foundation
8@testable import SophaxChatCore
9
10// MARK: - Helpers
11
12private func makeEnvelope(id: String = UUID().uuidString, ttl: UInt8 = RelayEnvelope.maxTTL) -> RelayEnvelope {
13 // Minimal WireMessage stub — content is irrelevant for routing tests
14 let stub = WireMessage(
15 type: .message, payload: Data(), senderID: "sender",
16 timestamp: Date(), signature: Data()
17 )
18 return RelayEnvelope(
19 id: id, targetPeerID: "target", originPeerID: "origin",
20 ttl: ttl, hopCount: 0, message: stub
21 )
22}
23
24// MARK: - Tests
25
26@Suite("RelayRouter")
27struct RelayRouterTests {
28
29 // MARK: Basic deduplication
30
31 @Test("First-seen envelope is processed")
32 func firstSeenIsProcessed() {
33 let router = RelayRouter()
34 let env = makeEnvelope()
35 #expect(router.shouldProcess(env) == true)
36 }
37
38 @Test("Duplicate envelope is dropped")
39 func duplicateIsDropped() {
40 let router = RelayRouter()
41 let env = makeEnvelope()
42 _ = router.shouldProcess(env)
43 #expect(router.shouldProcess(env) == false)
44 }
45
46 @Test("Different IDs are processed independently")
47 func differentIDsAreIndependent() {
48 let router = RelayRouter()
49 #expect(router.shouldProcess(makeEnvelope(id: "a")) == true)
50 #expect(router.shouldProcess(makeEnvelope(id: "b")) == true)
51 }
52
53 // MARK: TTL enforcement
54
55 @Test("Zero TTL is rejected")
56 func zeroTTLRejected() {
57 let router = RelayRouter()
58 #expect(router.shouldProcess(makeEnvelope(ttl: 0)) == false)
59 }
60
61 @Test("Inflated TTL above maxTTL is rejected (DoS guard)")
62 func inflatedTTLRejected() {
63 let router = RelayRouter()
64 // A malicious peer could craft ttl = 255 to cause unbounded flooding.
65 #expect(router.shouldProcess(makeEnvelope(ttl: 255)) == false)
66 #expect(router.shouldProcess(makeEnvelope(ttl: RelayEnvelope.maxTTL + 1)) == false)
67 }
68
69 @Test("maxTTL is accepted")
70 func maxTTLAccepted() {
71 let router = RelayRouter()
72 #expect(router.shouldProcess(makeEnvelope(ttl: RelayEnvelope.maxTTL)) == true)
73 }
74
75 @Test("TTL of 1 is accepted")
76 func ttl1Accepted() {
77 let router = RelayRouter()
78 #expect(router.shouldProcess(makeEnvelope(ttl: 1)) == true)
79 }
80
81 // MARK: LRU eviction
82
83 @Test("Seen-set stays bounded by maxSeen")
84 func seenSetIsBounded() {
85 let max = 10
86 let router = RelayRouter(maxSeen: max)
87 for _ in 0..<(max + 5) {
88 _ = router.shouldProcess(makeEnvelope())
89 }
90 #expect(router.seenCount == max)
91 }
92
93 @Test("Evicted IDs can be re-processed")
94 func evictedIDsCanBeReprocessed() {
95 let router = RelayRouter(maxSeen: 2)
96 let first = makeEnvelope(id: "first")
97
98 _ = router.shouldProcess(first) // seen: [first]
99 _ = router.shouldProcess(makeEnvelope()) // seen: [first, x]
100 _ = router.shouldProcess(makeEnvelope()) // evicts "first", seen: [x, y]
101
102 // "first" was evicted — should be processable again
103 #expect(router.shouldProcess(first) == true)
104 }
105
106 // MARK: Reset
107
108 @Test("Reset clears all state")
109 func resetClearsState() {
110 let router = RelayRouter()
111 let env = makeEnvelope()
112 _ = router.shouldProcess(env)
113 router.reset()
114 #expect(router.seenCount == 0)
115 #expect(router.shouldProcess(env) == true)
116 }
117}