FriendZoneTests.swift (5411B)
1 import Foundation 2 import Testing 3 4 @testable import Crossmate 5 6 @Suite("FriendZone") 7 struct FriendZoneTests { 8 9 @Test("pairKey is symmetric in its arguments") 10 func pairKeySymmetric() { 11 let a = "_alice0000000000000000000000000000" 12 let b = "_bob00000000000000000000000000000000" 13 #expect(FriendZone.pairKey(a, b) == FriendZone.pairKey(b, a)) 14 } 15 16 @Test("pairKey is deterministic across calls") 17 func pairKeyDeterministic() { 18 let a = "_alice" 19 let b = "_bob" 20 #expect(FriendZone.pairKey(a, b) == FriendZone.pairKey(a, b)) 21 } 22 23 @Test("pairKey distinguishes different pairs") 24 func pairKeyDistinct() { 25 #expect(FriendZone.pairKey("_a", "_b") != FriendZone.pairKey("_a", "_c")) 26 } 27 28 @Test("pairKey is 64 lowercase hex characters") 29 func pairKeyShape() { 30 let key = FriendZone.pairKey("_a", "_b") 31 #expect(key.count == 64) 32 #expect(key.allSatisfy { "0123456789abcdef".contains($0) }) 33 } 34 35 @Test("zoneName carries the prefix and is recognised as a friend zone") 36 func zoneNameRoundTrip() { 37 let key = FriendZone.pairKey("_a", "_b") 38 let name = FriendZone.zoneName(pairKey: key) 39 #expect(name == "friend-\(key)") 40 #expect(FriendZone.isFriendZone(name)) 41 #expect(!FriendZone.isFriendZone("game-\(UUID().uuidString)")) 42 #expect(!FriendZone.isFriendZone("account")) 43 } 44 45 @Test("owner election is deterministic, asymmetric, and excludes self") 46 func ownerElection() { 47 let small = "_aaa" 48 let large = "_zzz" 49 #expect(FriendZone.isOwner(localAuthorID: small, remoteAuthorID: large)) 50 #expect(!FriendZone.isOwner(localAuthorID: large, remoteAuthorID: small)) 51 // Exactly one side owns. 52 #expect( 53 FriendZone.isOwner(localAuthorID: small, remoteAuthorID: large) 54 != FriendZone.isOwner(localAuthorID: large, remoteAuthorID: small) 55 ) 56 // Same user can never be a friend. 57 #expect(!FriendZone.isOwner(localAuthorID: small, remoteAuthorID: small)) 58 } 59 60 @Test("BootstrapPayload round-trips through its string encoding") 61 func bootstrapPayloadRoundTrip() { 62 let payload = FriendZone.BootstrapPayload( 63 friendShareURL: "https://www.icloud.com/share/abc#xyz", 64 pairKey: FriendZone.pairKey("_a", "_b"), 65 ownerAuthorID: "_a" 66 ) 67 let encoded = payload.encodedString() 68 #expect(encoded != nil) 69 #expect(FriendZone.BootstrapPayload.decode(encoded) == payload) 70 } 71 72 @Test("Bootstrap acceptor must be the other member of the pair") 73 func bootstrapAcceptorMustBePairMember() { 74 let payload = FriendZone.BootstrapPayload( 75 friendShareURL: "https://www.icloud.com/share/abc#xyz", 76 pairKey: FriendZone.pairKey("_alice", "_bob"), 77 ownerAuthorID: "_alice" 78 ) 79 80 #expect(FriendZone.canAcceptBootstrap(payload, localAuthorID: "_bob")) 81 #expect(!FriendZone.canAcceptBootstrap(payload, localAuthorID: "_carol")) 82 #expect(!FriendZone.canAcceptBootstrap(payload, localAuthorID: "_alice")) 83 #expect(!FriendZone.canAcceptBootstrap(payload, localAuthorID: nil)) 84 #expect(!FriendZone.canAcceptBootstrap(payload, localAuthorID: "")) 85 } 86 87 @Test("BootstrapPayload.decode tolerates nil and malformed input") 88 func bootstrapPayloadDecodeTolerant() { 89 #expect(FriendZone.BootstrapPayload.decode(nil) == nil) 90 #expect(FriendZone.BootstrapPayload.decode("") == nil) 91 #expect(FriendZone.BootstrapPayload.decode("not json") == nil) 92 #expect(FriendZone.BootstrapPayload.decode(#"{"friendShareURL":"x"}"#) == nil) 93 } 94 95 @Test("InvitePayload round-trips through its string encoding") 96 func invitePayloadRoundTrip() { 97 let payload = FriendZone.InvitePayload( 98 gameShareURL: "https://www.icloud.com/share/xyz#abc" 99 ) 100 let encoded = payload.encodedString() 101 #expect(encoded != nil) 102 #expect(FriendZone.InvitePayload.decode(encoded) == payload) 103 } 104 105 @Test("InvitePayload round-trips a grid silhouette segment") 106 func invitePayloadRoundTripWithSilhouette() { 107 let payload = FriendZone.InvitePayload( 108 gameShareURL: "https://www.icloud.com/share/xyz#abc", 109 gridSilhouette: "sf//AA" 110 ) 111 let decoded = FriendZone.InvitePayload.decode(payload.encodedString()) 112 #expect(decoded == payload) 113 #expect(decoded?.gridSilhouette == "sf//AA") 114 } 115 116 @Test("InvitePayload from an older sender decodes with a nil silhouette") 117 func invitePayloadDecodesLegacyWithoutSilhouette() { 118 // Payloads written before the silhouette field omit the key entirely. 119 let legacy = #"{"gameShareURL":"https://www.icloud.com/share/xyz"}"# 120 let decoded = FriendZone.InvitePayload.decode(legacy) 121 #expect(decoded?.gameShareURL == "https://www.icloud.com/share/xyz") 122 #expect(decoded?.gridSilhouette == nil) 123 } 124 125 @Test("InvitePayload.decode tolerates nil and malformed input") 126 func invitePayloadDecodeTolerant() { 127 #expect(FriendZone.InvitePayload.decode(nil) == nil) 128 #expect(FriendZone.InvitePayload.decode("") == nil) 129 #expect(FriendZone.InvitePayload.decode("garbage") == nil) 130 #expect(FriendZone.InvitePayload.decode(#"{"x":1}"#) == nil) 131 } 132 }