vibetunnel/ios/VibeTunnelTests/FileSystemTests.swift
Peter Steinberger eab1e6c962 feat: Update bundle identifiers and add logging configuration profile
- Updated macOS test bundle IDs to use consistent naming pattern:
  - sh.vibetunnel.vibetunnelTests → sh.vibetunnel.vibetunnel.tests
  - sh.vibetunnel.vibetunnelTests.debug → sh.vibetunnel.vibetunnel.tests.debug
- Updated iOS test bundle ID:
  - sh.vibetunnel.VibeTunnelTests-Mobile → sh.vibetunnel.ios.tests
- Fixed iOS logging to use sh.vibetunnel.ios subsystem consistently
- Created logging configuration profile in apple/logging/ to enable full debug logging
- Configuration profile covers all VibeTunnel subsystems and bundle IDs
- Updated documentation to reflect new bundle identifiers and logging setup
2025-07-30 18:04:32 +02:00

361 lines
12 KiB
Swift

import Foundation
import Testing
@Suite("File System Operation Tests", .tags(.fileSystem))
struct FileSystemTests {
// MARK: - Path Operations
@Test("Path normalization and resolution")
func pathNormalization() {
// Test path normalization
func normalizePath(_ path: String) -> String {
(path as NSString).standardizingPath
}
#expect(normalizePath("/Users/test/./Documents") == "/Users/test/Documents")
#expect(normalizePath("/Users/test/../test/Documents") == "/Users/test/Documents")
#expect(normalizePath("~/Documents") != "~/Documents") // Should expand tilde
#expect(normalizePath("/Users//test///Documents") == "/Users/test/Documents")
}
@Test("File extension handling")
func fileExtensions() {
struct FileInfo {
let path: String
var filename: String {
(path as NSString).lastPathComponent
}
var `extension`: String {
(path as NSString).pathExtension
}
var nameWithoutExtension: String {
(filename as NSString).deletingPathExtension
}
func appendingExtension(_ ext: String) -> String {
(path as NSString).appendingPathExtension(ext) ?? path
}
}
let file = FileInfo(path: "/Users/test/document.txt")
#expect(file.filename == "document.txt")
#expect(file.extension == "txt")
#expect(file.nameWithoutExtension == "document")
let noExtFile = FileInfo(path: "/Users/test/README")
#expect(noExtFile.extension == "")
#expect(noExtFile.nameWithoutExtension == "README")
}
// MARK: - File Permissions
@Test("File permission checks")
func filePermissions() {
struct FilePermissions {
let isReadable: Bool
let isWritable: Bool
let isExecutable: Bool
let isDeletable: Bool
var octalRepresentation: String {
var value = 0
if isReadable { value += 4 }
if isWritable { value += 2 }
if isExecutable { value += 1 }
return String(value)
}
}
let readOnly = FilePermissions(
isReadable: true,
isWritable: false,
isExecutable: false,
isDeletable: false
)
#expect(readOnly.octalRepresentation == "4")
let readWrite = FilePermissions(
isReadable: true,
isWritable: true,
isExecutable: false,
isDeletable: true
)
#expect(readWrite.octalRepresentation == "6")
let executable = FilePermissions(
isReadable: true,
isWritable: false,
isExecutable: true,
isDeletable: false
)
#expect(executable.octalRepresentation == "5")
}
// MARK: - Directory Operations
@Test("Directory traversal and listing")
func directoryTraversal() {
struct DirectoryEntry {
let name: String
let isDirectory: Bool
let size: Int64?
let modificationDate: Date?
var type: String {
isDirectory ? "directory" : "file"
}
}
// Test directory entry creation
let fileEntry = DirectoryEntry(
name: "test.txt",
isDirectory: false,
size: 1_024,
modificationDate: Date()
)
#expect(fileEntry.type == "file")
#expect(fileEntry.size == 1_024)
let dirEntry = DirectoryEntry(
name: "Documents",
isDirectory: true,
size: nil,
modificationDate: Date()
)
#expect(dirEntry.type == "directory")
#expect(dirEntry.size == nil)
}
@Test("Recursive directory size calculation")
func directorySizeCalculation() {
// Simulate directory size calculation
func calculateDirectorySize(files: [(name: String, size: Int64)]) -> Int64 {
files.reduce(0) { $0 + $1.size }
}
let files = [
("file1.txt", Int64(1_024)),
("file2.doc", Int64(2_048)),
("image.jpg", Int64(4_096))
]
let totalSize = calculateDirectorySize(files: files)
#expect(totalSize == 7_168)
// Test size formatting
func formatFileSize(_ bytes: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.countStyle = .file
return formatter.string(fromByteCount: bytes)
}
#expect(!formatFileSize(1_024).isEmpty)
#expect(!formatFileSize(1_048_576).isEmpty) // 1 MB
}
// MARK: - File Operations
@Test("Safe file operations")
func safeFileOperations() {
enum FileOperation {
case read
case write
case delete
case move(to: String)
case copy(to: String)
var requiresWritePermission: Bool {
switch self {
case .read:
false
case .write, .delete, .move, .copy:
true
}
}
}
#expect(FileOperation.read.requiresWritePermission == false)
#expect(FileOperation.write.requiresWritePermission == true)
#expect(FileOperation.delete.requiresWritePermission == true)
#expect(FileOperation.move(to: "/tmp/file").requiresWritePermission == true)
}
@Test("Atomic file writing")
func atomicFileWriting() {
struct AtomicFileWriter {
let destinationPath: String
var temporaryPath: String {
destinationPath + ".tmp"
}
func writeSteps() -> [String] {
[
"Write to temporary file: \(temporaryPath)",
"Verify temporary file integrity",
"Atomically rename to: \(destinationPath)",
"Clean up any failed attempts"
]
}
}
let writer = AtomicFileWriter(destinationPath: "/Users/test/important.dat")
let steps = writer.writeSteps()
#expect(steps.count == 4)
#expect(writer.temporaryPath == "/Users/test/important.dat.tmp")
}
// MARK: - File Watching
@Test("File change detection")
func fileChangeDetection() {
struct FileSnapshot {
let path: String
let size: Int64
let modificationDate: Date
let contentHash: String
func hasChanged(comparedTo other: FileSnapshot) -> Bool {
size != other.size ||
modificationDate != other.modificationDate ||
contentHash != other.contentHash
}
}
let snapshot1 = FileSnapshot(
path: "/test/file.txt",
size: 1_024,
modificationDate: Date(),
contentHash: "abc123"
)
let snapshot2 = FileSnapshot(
path: "/test/file.txt",
size: 1_024,
modificationDate: Date().addingTimeInterval(10),
contentHash: "abc123"
)
let snapshot3 = FileSnapshot(
path: "/test/file.txt",
size: 2_048,
modificationDate: Date().addingTimeInterval(20),
contentHash: "def456"
)
#expect(!snapshot1.hasChanged(comparedTo: snapshot1))
#expect(snapshot1.hasChanged(comparedTo: snapshot2)) // Different date
#expect(snapshot1.hasChanged(comparedTo: snapshot3)) // Different size and hash
}
// MARK: - Sandbox and Security
@Test("Sandbox path validation")
func sandboxPaths() {
struct SandboxValidator {
let appGroupIdentifier = "group.sh.vibetunnel"
var documentsDirectory: String {
"~/Documents"
}
var temporaryDirectory: String {
NSTemporaryDirectory()
}
var appGroupDirectory: String {
"~/Library/Group Containers/\(appGroupIdentifier)"
}
func isWithinSandbox(_ path: String) -> Bool {
let normalizedPath = (path as NSString).standardizingPath
let expandedDocs = (documentsDirectory as NSString).expandingTildeInPath
let expandedAppGroup = (appGroupDirectory as NSString).expandingTildeInPath
return normalizedPath.hasPrefix(expandedDocs) ||
normalizedPath.hasPrefix(temporaryDirectory) ||
normalizedPath.hasPrefix(expandedAppGroup)
}
}
let validator = SandboxValidator()
#expect(validator.isWithinSandbox("~/Documents/file.txt"))
#expect(validator.isWithinSandbox(NSTemporaryDirectory() + "temp.dat"))
#expect(!validator.isWithinSandbox("/System/Library/file.txt"))
}
// MARK: - File Type Detection
@Test("MIME type detection")
func mIMETypeDetection() {
func mimeType(for fileExtension: String) -> String {
let mimeTypes: [String: String] = [
"txt": "text/plain",
"html": "text/html",
"json": "application/json",
"pdf": "application/pdf",
"jpg": "image/jpeg",
"png": "image/png",
"mp4": "video/mp4",
"zip": "application/zip"
]
return mimeTypes[fileExtension.lowercased()] ?? "application/octet-stream"
}
#expect(mimeType(for: "txt") == "text/plain")
#expect(mimeType(for: "JSON") == "application/json")
#expect(mimeType(for: "unknown") == "application/octet-stream")
}
@Test("Text encoding detection")
func textEncodingDetection() {
// Test BOM (Byte Order Mark) detection
func detectEncoding(from bom: [UInt8]) -> String.Encoding? {
if bom.starts(with: [0xEF, 0xBB, 0xBF]) {
return .utf8
} else if bom.starts(with: [0xFF, 0xFE]) {
return .utf16LittleEndian
} else if bom.starts(with: [0xFE, 0xFF]) {
return .utf16BigEndian
} else if bom.starts(with: [0xFF, 0xFE, 0x00, 0x00]) {
return .utf32LittleEndian
} else if bom.starts(with: [0x00, 0x00, 0xFE, 0xFF]) {
return .utf32BigEndian
}
return nil
}
#expect(detectEncoding(from: [0xEF, 0xBB, 0xBF]) == .utf8)
#expect(detectEncoding(from: [0xFF, 0xFE]) == .utf16LittleEndian)
#expect(detectEncoding(from: [0x41, 0x42]) == nil) // No BOM
}
// MARK: - URL and Path Conversion
@Test("URL to path conversion")
func uRLPathConversion() {
func filePathFromURL(_ urlString: String) -> String? {
guard let url = URL(string: urlString),
url.isFileURL else { return nil }
return url.path
}
#expect(filePathFromURL("file:///Users/test/file.txt") == "/Users/test/file.txt")
#expect(filePathFromURL("file://localhost/Users/test/file.txt") == "/Users/test/file.txt")
#expect(filePathFromURL("https://example.com/file.txt") == nil)
// Test path to URL conversion
func fileURLFromPath(_ path: String) -> URL? {
URL(fileURLWithPath: path)
}
let url = fileURLFromPath("/Users/test/file.txt")
#expect(url?.isFileURL == true)
#expect(url?.path == "/Users/test/file.txt")
}
}