import XCTest @testable import Rosetta /// Comprehensive tests for file attachment download, caching, preview parsing, /// icon mapping, and cross-platform parity. final class FileAttachmentTests: XCTestCase { // ========================================================================= // MARK: - AttachmentPreviewCodec: File Preview Parsing // ========================================================================= func testParseFilePreview_TagSizeFilename() { let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::12345::document.pdf" let parsed = AttachmentPreviewCodec.parseFilePreview(preview) XCTAssertEqual(parsed.downloadTag, "4e6712a0-31c0-4b0d-b17c-83170709ec02") XCTAssertEqual(parsed.fileSize, 12345) XCTAssertEqual(parsed.fileName, "document.pdf") } func testParseFilePreview_NoTag() { let preview = "12345::document.pdf" let parsed = AttachmentPreviewCodec.parseFilePreview(preview) XCTAssertEqual(parsed.downloadTag, "") XCTAssertEqual(parsed.fileSize, 12345) XCTAssertEqual(parsed.fileName, "document.pdf") } func testParseFilePreview_PlaceholderBeforeUpload() { let preview = "::500::notes.txt" let parsed = AttachmentPreviewCodec.parseFilePreview(preview) XCTAssertEqual(parsed.downloadTag, "") XCTAssertEqual(parsed.fileSize, 500) XCTAssertEqual(parsed.fileName, "notes.txt") } func testParseFilePreview_EmptyPreview() { let parsed = AttachmentPreviewCodec.parseFilePreview("") XCTAssertEqual(parsed.downloadTag, "") XCTAssertEqual(parsed.fileName, "file") // fallback XCTAssertEqual(parsed.fileSize, 0) } func testParseFilePreview_CustomFallbacks() { let parsed = AttachmentPreviewCodec.parseFilePreview( "", fallbackFileName: "unknown.bin", fallbackFileSize: 999 ) XCTAssertEqual(parsed.fileName, "unknown.bin") XCTAssertEqual(parsed.fileSize, 999) } func testParseFilePreview_FilenameWithColons() { // Edge case: filename containing "::" (unlikely but possible) let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::1024::file::with::colons.txt" let parsed = AttachmentPreviewCodec.parseFilePreview(preview) XCTAssertEqual(parsed.downloadTag, "4e6712a0-31c0-4b0d-b17c-83170709ec02") XCTAssertEqual(parsed.fileSize, 1024) XCTAssertEqual(parsed.fileName, "file::with::colons.txt") } // ========================================================================= // MARK: - AttachmentPreviewCodec: Download Tag Extraction // ========================================================================= func testDownloadTag_ValidUUID() { let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::blurhash" XCTAssertEqual( AttachmentPreviewCodec.downloadTag(from: preview), "4e6712a0-31c0-4b0d-b17c-83170709ec02" ) } func testDownloadTag_NoTag() { let preview = "::blurhash" XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: preview), "") } func testDownloadTag_EmptyPreview() { XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: ""), "") } func testDownloadTag_JustBlurhash() { let preview = "LVRv{GtRSXWB" XCTAssertEqual(AttachmentPreviewCodec.downloadTag(from: preview), "") } // ========================================================================= // MARK: - AttachmentPreviewCodec: BlurHash Extraction // ========================================================================= func testBlurHash_TagAndHash() { let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::LVRv{GtR" XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtR") } func testBlurHash_WithDimensionSuffix() { let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::LVRv{GtR|640x480" XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtR") } func testBlurHash_PlainHash() { let preview = "LVRv{GtRSXWB" XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtRSXWB") } func testBlurHash_PlaceholderFormat() { let preview = "::LVRv{GtR" XCTAssertEqual(AttachmentPreviewCodec.blurHash(from: preview), "LVRv{GtR") } // ========================================================================= // MARK: - AttachmentPreviewCodec: Image Dimensions // ========================================================================= func testImageDimensions_Present() { let preview = "tag::LVRv{GtR|640x480" let dims = AttachmentPreviewCodec.imageDimensions(from: preview) XCTAssertEqual(dims?.width, 640) XCTAssertEqual(dims?.height, 480) } func testImageDimensions_Missing() { let preview = "tag::LVRv{GtR" XCTAssertNil(AttachmentPreviewCodec.imageDimensions(from: preview)) } func testImageDimensions_TooSmall() { let preview = "tag::LVRv{GtR|5x5" XCTAssertNil(AttachmentPreviewCodec.imageDimensions(from: preview)) } // ========================================================================= // MARK: - AttachmentPreviewCodec: Call Duration // ========================================================================= func testCallDuration_PlainNumber() { XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds("45"), 45) } func testCallDuration_Zero() { XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds("0"), 0) } func testCallDuration_Empty() { XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds(""), 0) } func testCallDuration_WithTag() { let preview = "4e6712a0-31c0-4b0d-b17c-83170709ec02::60" XCTAssertEqual(AttachmentPreviewCodec.parseCallDurationSeconds(preview), 60) } // ========================================================================= // MARK: - AttachmentPreviewCodec: Compose // ========================================================================= func testCompose_TagAndPayload() { let result = AttachmentPreviewCodec.compose( downloadTag: "4e6712a0-31c0-4b0d-b17c-83170709ec02", payload: "LVRv{GtR" ) XCTAssertEqual(result, "4e6712a0-31c0-4b0d-b17c-83170709ec02::LVRv{GtR") } func testCompose_EmptyTag() { XCTAssertEqual(AttachmentPreviewCodec.compose(downloadTag: "", payload: "LVRv"), "LVRv") } // ========================================================================= // MARK: - MessageAttachment: effectiveDownloadTag // ========================================================================= func testEffectiveTag_TransportTagPresent() { let att = MessageAttachment( id: "test", preview: "old-tag::hash", transportTag: "new-tag" ) XCTAssertEqual(att.effectiveDownloadTag, "new-tag") } func testEffectiveTag_FallbackToPreview() { let att = MessageAttachment( id: "test", preview: "4e6712a0-31c0-4b0d-b17c-83170709ec02::hash" ) XCTAssertEqual(att.effectiveDownloadTag, "4e6712a0-31c0-4b0d-b17c-83170709ec02") } func testEffectiveTag_BothEmpty() { let att = MessageAttachment(id: "test", preview: "::hash") XCTAssertEqual(att.effectiveDownloadTag, "") } // ========================================================================= // MARK: - File Icon Mapping (Telegram parity) // ========================================================================= /// Tests the file icon mapping used by MessageFileView (SwiftUI) and /// NativeMessageCell.fileIcon(for:) (UIKit). Both must return same values. private func fileIcon(for fileName: String) -> String { let ext = (fileName as NSString).pathExtension.lowercased() switch ext { case "pdf": return "doc.fill" case "zip", "rar", "7z": return "doc.zipper" case "jpg", "jpeg", "png", "gif": return "photo.fill" case "mp4", "mov", "avi": return "film.fill" case "mp3", "wav", "aac": return "waveform" default: return "doc.fill" } } func testFileIcon_PDF() { XCTAssertEqual(fileIcon(for: "report.pdf"), "doc.fill") } func testFileIcon_ZIP() { XCTAssertEqual(fileIcon(for: "archive.zip"), "doc.zipper") } func testFileIcon_RAR() { XCTAssertEqual(fileIcon(for: "data.rar"), "doc.zipper") } func testFileIcon_7Z() { XCTAssertEqual(fileIcon(for: "backup.7z"), "doc.zipper") } func testFileIcon_JPG() { XCTAssertEqual(fileIcon(for: "photo.jpg"), "photo.fill") } func testFileIcon_JPEG() { XCTAssertEqual(fileIcon(for: "photo.jpeg"), "photo.fill") } func testFileIcon_PNG() { XCTAssertEqual(fileIcon(for: "screenshot.png"), "photo.fill") } func testFileIcon_GIF() { XCTAssertEqual(fileIcon(for: "animation.gif"), "photo.fill") } func testFileIcon_MP4() { XCTAssertEqual(fileIcon(for: "video.mp4"), "film.fill") } func testFileIcon_MOV() { XCTAssertEqual(fileIcon(for: "clip.mov"), "film.fill") } func testFileIcon_AVI() { XCTAssertEqual(fileIcon(for: "movie.avi"), "film.fill") } func testFileIcon_MP3() { XCTAssertEqual(fileIcon(for: "song.mp3"), "waveform") } func testFileIcon_WAV() { XCTAssertEqual(fileIcon(for: "audio.wav"), "waveform") } func testFileIcon_AAC() { XCTAssertEqual(fileIcon(for: "voice.aac"), "waveform") } func testFileIcon_Unknown() { XCTAssertEqual(fileIcon(for: "data.bin"), "doc.fill") } func testFileIcon_NoExtension() { XCTAssertEqual(fileIcon(for: "Makefile"), "doc.fill") } func testFileIcon_UpperCase() { XCTAssertEqual(fileIcon(for: "IMAGE.PNG"), "photo.fill") } // ========================================================================= // MARK: - AttachmentCache: File Save/Load Round-Trip // ========================================================================= func testFileSaveAndLoad_Plaintext() { // Without private key, files are saved as plaintext let cache = AttachmentCache.shared let originalKey = cache.privateKey cache.privateKey = nil defer { cache.privateKey = originalKey } let testData = "Hello, world!".data(using: .utf8)! let attId = "test_plain_\(UUID().uuidString.prefix(8))" let fileName = "test.txt" let url = cache.saveFile(testData, forAttachmentId: attId, fileName: fileName) XCTAssertTrue(FileManager.default.fileExists(atPath: url.path)) // fileURL should find it let foundURL = cache.fileURL(forAttachmentId: attId, fileName: fileName) XCTAssertNotNil(foundURL) // loadFileData should return original data let loaded = cache.loadFileData(forAttachmentId: attId, fileName: fileName) XCTAssertEqual(loaded, testData) // Cleanup try? FileManager.default.removeItem(at: url) } func testFileSaveAndLoad_Encrypted() throws { let cache = AttachmentCache.shared let originalKey = cache.privateKey // Use a test key cache.privateKey = "test_private_key_for_encryption" defer { cache.privateKey = originalKey } let testData = "Encrypted file content 🔒".data(using: .utf8)! let attId = "test_enc_\(UUID().uuidString.prefix(8))" let fileName = "secret.pdf" let url = cache.saveFile(testData, forAttachmentId: attId, fileName: fileName) XCTAssertTrue(FileManager.default.fileExists(atPath: url.path)) XCTAssertTrue(url.lastPathComponent.hasSuffix(".enc")) // Raw file should NOT be the original data (it's encrypted) let rawData = try Data(contentsOf: url) XCTAssertNotEqual(rawData, testData) // loadFileData should decrypt and return original let loaded = cache.loadFileData(forAttachmentId: attId, fileName: fileName) XCTAssertEqual(loaded, testData) // Cleanup try? FileManager.default.removeItem(at: url) } func testFileURL_NotCached() { let url = AttachmentCache.shared.fileURL( forAttachmentId: "nonexistent_\(UUID())", fileName: "nope.txt" ) XCTAssertNil(url) } func testLoadFileData_NotCached() { let data = AttachmentCache.shared.loadFileData( forAttachmentId: "nonexistent_\(UUID())", fileName: "nope.txt" ) XCTAssertNil(data) } func testFileSave_SlashInFilename() { let cache = AttachmentCache.shared let originalKey = cache.privateKey cache.privateKey = nil defer { cache.privateKey = originalKey } let testData = "data".data(using: .utf8)! let attId = "test_slash_\(UUID().uuidString.prefix(8))" // Slash should be replaced with underscore let fileName = "path/to/file.txt" let url = cache.saveFile(testData, forAttachmentId: attId, fileName: fileName) XCTAssertFalse(url.lastPathComponent.contains("/")) let loaded = cache.loadFileData(forAttachmentId: attId, fileName: fileName) XCTAssertEqual(loaded, testData) try? FileManager.default.removeItem(at: url) } // ========================================================================= // MARK: - AttachmentType JSON Encode/Decode (cross-platform parity) // ========================================================================= func testAttachmentType_EncodeValues() throws { // Verify wire format matches Desktop/Android (0,1,2,3,4) let encoder = JSONEncoder() let types: [AttachmentType] = [.image, .messages, .file, .avatar, .call] for (idx, type) in types.enumerated() { let data = try encoder.encode(type) let str = String(data: data, encoding: .utf8)! XCTAssertEqual(str, "\(idx)", "AttachmentType.\(type) should encode as \(idx)") } } func testMessageAttachment_BackwardCompatDecode() throws { // Old DB JSON without transportTag/transportServer should decode fine let json = #"{"id":"abc","preview":"tag::hash","blob":"data","type":0}"# let att = try JSONDecoder().decode(MessageAttachment.self, from: Data(json.utf8)) XCTAssertEqual(att.id, "abc") XCTAssertEqual(att.type, .image) XCTAssertEqual(att.transportTag, "") // default XCTAssertEqual(att.transportServer, "") // default } func testMessageAttachment_NewFieldsDecode() throws { let json = #"{"id":"abc","preview":"hash","blob":"","type":3,"transportTag":"cdn-tag","transportServer":"https://cdn.example.com"}"# let att = try JSONDecoder().decode(MessageAttachment.self, from: Data(json.utf8)) XCTAssertEqual(att.type, .avatar) XCTAssertEqual(att.transportTag, "cdn-tag") XCTAssertEqual(att.transportServer, "https://cdn.example.com") } // ========================================================================= // MARK: - File Size Formatting // ========================================================================= private func formattedFileSize(bytes: Int) -> String { if bytes < 1024 { return "\(bytes) B" } if bytes < 1024 * 1024 { return String(format: "%.1f KB", Double(bytes) / 1024) } if bytes < 1024 * 1024 * 1024 { return String(format: "%.1f MB", Double(bytes) / (1024 * 1024)) } return String(format: "%.1f GB", Double(bytes) / (1024 * 1024 * 1024)) } func testFileSize_Bytes() { XCTAssertEqual(formattedFileSize(bytes: 500), "500 B") } func testFileSize_KB() { XCTAssertEqual(formattedFileSize(bytes: 2048), "2.0 KB") } func testFileSize_MB() { XCTAssertEqual(formattedFileSize(bytes: 5_242_880), "5.0 MB") } func testFileSize_GB() { XCTAssertEqual(formattedFileSize(bytes: 1_073_741_824), "1.0 GB") } func testFileSize_Zero() { XCTAssertEqual(formattedFileSize(bytes: 0), "0 B") } }