mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
- 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
361 lines
12 KiB
Swift
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")
|
|
}
|
|
}
|