import Testing @testable import Rosetta /// Verifies PacketPushNotification wire format matches server /// (im.rosetta.packet.Packet16PushNotification). /// /// Server wire format: /// writeInt16(packetId=0x10) /// writeString(notificationToken) /// writeInt8(action) — 0=subscribe, 1=unsubscribe /// writeInt8(tokenType) — 0=FCM, 1=VoIPApns /// writeString(deviceId) struct PushNotificationPacketTests { // MARK: - Enum Value Parity @Test("PushNotificationAction.subscribe == 0 (server: SUBSCRIBE)") func subscribeActionValue() { #expect(PushNotificationAction.subscribe.rawValue == 0) } @Test("PushNotificationAction.unsubscribe == 1 (server: UNSUBSCRIBE)") func unsubscribeActionValue() { #expect(PushNotificationAction.unsubscribe.rawValue == 1) } @Test("PushTokenType.fcm == 0 (server: FCM)") func fcmTokenTypeValue() { #expect(PushTokenType.fcm.rawValue == 0) } @Test("PushTokenType.voipApns == 1 (server: VoIPApns)") func voipTokenTypeValue() { #expect(PushTokenType.voipApns.rawValue == 1) } // MARK: - Round Trip (encode → decode) @Test("FCM subscribe round-trip preserves all fields") func fcmSubscribeRoundTrip() throws { var packet = PacketPushNotification() packet.notificationsToken = "test-fcm-token-abc123" packet.action = .subscribe packet.tokenType = .fcm packet.deviceId = "device-id-xyz" let decoded = try decodePushNotification(packet) #expect(decoded.notificationsToken == "test-fcm-token-abc123") #expect(decoded.action == .subscribe) #expect(decoded.tokenType == .fcm) #expect(decoded.deviceId == "device-id-xyz") } @Test("VoIP unsubscribe round-trip preserves all fields") func voipUnsubscribeRoundTrip() throws { var packet = PacketPushNotification() packet.notificationsToken = "voip-hex-token-deadbeef" packet.action = .unsubscribe packet.tokenType = .voipApns packet.deviceId = "another-device-id" let decoded = try decodePushNotification(packet) #expect(decoded.notificationsToken == "voip-hex-token-deadbeef") #expect(decoded.action == .unsubscribe) #expect(decoded.tokenType == .voipApns) #expect(decoded.deviceId == "another-device-id") } @Test("Empty token and deviceId round-trip") func emptyFieldsRoundTrip() throws { var packet = PacketPushNotification() packet.notificationsToken = "" packet.action = .subscribe packet.tokenType = .fcm packet.deviceId = "" let decoded = try decodePushNotification(packet) #expect(decoded.notificationsToken == "") #expect(decoded.deviceId == "") } // MARK: - Wire Format Byte Verification @Test("Packet ID is 0x10 in encoded data") func packetIdInEncodedData() { #expect(PacketPushNotification.packetId == 0x10) let packet = PacketPushNotification() let data = PacketRegistry.encode(packet) // First 2 bytes = packetId in big-endian: 0x00 0x10 #expect(data.count >= 2) #expect(data[0] == 0x00) #expect(data[1] == 0x10) } @Test("Wire format field order matches server: token → action → tokenType → deviceId") func wireFormatFieldOrder() throws { // Use known short values so we can verify byte positions. var packet = PacketPushNotification() packet.notificationsToken = "T" // 1 UTF-16 code unit packet.action = .subscribe // 0 packet.tokenType = .voipApns // 1 packet.deviceId = "D" // 1 UTF-16 code unit let data = PacketRegistry.encode(packet) // Expected layout: // [0-1] packetId = 0x0010 (2 bytes) // [2-5] string length = 1 (UInt32 big-endian) for "T" // [6-7] 'T' = 0x0054 (UInt16 big-endian) // [8] action = 0 (subscribe) // [9] tokenType = 1 (voipApns) // [10-13] string length = 1 for "D" // [14-15] 'D' = 0x0044 (UInt16 big-endian) #expect(data.count == 16) // packetId #expect(data[0] == 0x00) #expect(data[1] == 0x10) // token string length = 1 #expect(data[2] == 0x00) #expect(data[3] == 0x00) #expect(data[4] == 0x00) #expect(data[5] == 0x01) // 'T' in UTF-16 BE #expect(data[6] == 0x00) #expect(data[7] == 0x54) // action = 0 (subscribe) #expect(data[8] == 0x00) // tokenType = 1 (voipApns) #expect(data[9] == 0x01) // deviceId string length = 1 #expect(data[10] == 0x00) #expect(data[11] == 0x00) #expect(data[12] == 0x00) #expect(data[13] == 0x01) // 'D' in UTF-16 BE #expect(data[14] == 0x00) #expect(data[15] == 0x44) } // MARK: - Helper private func decodePushNotification( _ packet: PacketPushNotification ) throws -> PacketPushNotification { let encoded = PacketRegistry.encode(packet) guard let decoded = PacketRegistry.decode(from: encoded), let decodedPacket = decoded.packet as? PacketPushNotification else { throw NSError( domain: "PushNotificationPacketTests", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to decode PacketPushNotification"] ) } #expect(decoded.packetId == 0x10) return decodedPacket } }