Files
mobile-ios/RosettaTests/FileAttachmentTests.swift

434 lines
16 KiB
Swift

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")
}
}