mirror of
https://github.com/samsonjs/FileOtter.git
synced 2026-03-25 08:25:49 +00:00
281 lines
9.4 KiB
Swift
281 lines
9.4 KiB
Swift
//
|
|
// FileOperationTests.swift
|
|
// FileOtterTests
|
|
//
|
|
// Created by Sami Samhuri on 2025-08-19.
|
|
//
|
|
|
|
@testable import FileOtter
|
|
import XCTest
|
|
|
|
final class FileOperationTests: XCTestCase {
|
|
var tempDir: URL!
|
|
var sourceFile: URL!
|
|
var destFile: URL!
|
|
|
|
override func setUpWithError() throws {
|
|
tempDir = URL.temporaryDirectory
|
|
.appendingPathComponent("FileOperationTests-\(UUID().uuidString)")
|
|
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
|
|
|
|
sourceFile = tempDir.appendingPathComponent("source.txt")
|
|
try "Source content".write(to: sourceFile, atomically: true, encoding: .utf8)
|
|
|
|
destFile = tempDir.appendingPathComponent("dest.txt")
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
if FileManager.default.fileExists(atPath: tempDir.path) {
|
|
try FileManager.default.removeItem(at: tempDir)
|
|
}
|
|
}
|
|
|
|
// MARK: - Link Tests
|
|
|
|
func testLink() throws {
|
|
// TODO: Implement
|
|
// File.link(source, destination) creates hard link
|
|
}
|
|
|
|
func testLinkThrowsIfDestExists() throws {
|
|
// TODO: Implement
|
|
// File.link should not overwrite existing file
|
|
}
|
|
|
|
func testSymlink() throws {
|
|
// Create a file to link to
|
|
let target = tempDir.appendingPathComponent("target.txt")
|
|
try "Target content".write(to: target, atomically: true, encoding: .utf8)
|
|
|
|
// Create symlink
|
|
let link = tempDir.appendingPathComponent("symlink.txt")
|
|
try File.symlink(source: target, destination: link)
|
|
|
|
// Verify symlink exists and points to target
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: link.path))
|
|
|
|
// Reading through symlink should get target's content
|
|
XCTAssertEqual(try String(contentsOf: link), "Target content")
|
|
}
|
|
|
|
func testSymlinkThrowsIfDestExists() throws {
|
|
let target = tempDir.appendingPathComponent("target.txt")
|
|
try "Target".write(to: target, atomically: true, encoding: .utf8)
|
|
|
|
let existingFile = tempDir.appendingPathComponent("existing.txt")
|
|
try "Existing".write(to: existingFile, atomically: true, encoding: .utf8)
|
|
|
|
// Should throw because destination exists
|
|
XCTAssertThrowsError(try File.symlink(source: target, destination: existingFile))
|
|
}
|
|
|
|
func testReadlink() throws {
|
|
// Create target and symlink
|
|
let target = tempDir.appendingPathComponent("readlink-target.txt")
|
|
try "Content".write(to: target, atomically: true, encoding: .utf8)
|
|
|
|
let link = tempDir.appendingPathComponent("readlink-symlink.txt")
|
|
try File.symlink(source: target, destination: link)
|
|
|
|
// readlink should return the target path
|
|
let readTarget = try File.readlink(link)
|
|
XCTAssertEqual(readTarget.lastPathComponent, "readlink-target.txt")
|
|
}
|
|
|
|
func testReadlinkThrowsForNonSymlink() throws {
|
|
// Regular file, not a symlink
|
|
XCTAssertThrowsError(try File.readlink(sourceFile))
|
|
}
|
|
|
|
// MARK: - Delete Tests
|
|
|
|
func testUnlink() throws {
|
|
// Create a file to delete
|
|
let fileToDelete = tempDir.appendingPathComponent("delete-me.txt")
|
|
try "Delete this file".write(to: fileToDelete, atomically: true, encoding: .utf8)
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: fileToDelete.path))
|
|
|
|
// Delete it
|
|
try File.unlink(fileToDelete)
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: fileToDelete.path))
|
|
}
|
|
|
|
func testUnlinkThrowsForNonExistent() throws {
|
|
let nonExistent = tempDir.appendingPathComponent("does-not-exist.txt")
|
|
XCTAssertThrowsError(try File.unlink(nonExistent))
|
|
}
|
|
|
|
func testDelete() throws {
|
|
// Create a file to delete
|
|
let fileToDelete = tempDir.appendingPathComponent("delete-me-too.txt")
|
|
try "Delete this too".write(to: fileToDelete, atomically: true, encoding: .utf8)
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: fileToDelete.path))
|
|
|
|
// Delete is an alias for unlink
|
|
try File.delete(fileToDelete)
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: fileToDelete.path))
|
|
}
|
|
|
|
// MARK: - Rename Tests
|
|
|
|
func testRename() throws {
|
|
// Create source file
|
|
let source = tempDir.appendingPathComponent("original.txt")
|
|
let dest = tempDir.appendingPathComponent("renamed.txt")
|
|
let content = "Original content"
|
|
try content.write(to: source, atomically: true, encoding: .utf8)
|
|
|
|
// Rename it
|
|
try File.rename(source: source, destination: dest)
|
|
|
|
// Source should not exist, dest should exist with same content
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: source.path))
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: dest.path))
|
|
XCTAssertEqual(try String(contentsOf: dest), content)
|
|
}
|
|
|
|
func testRenameOverwritesExisting() throws {
|
|
// Create source and destination files
|
|
let source = tempDir.appendingPathComponent("source.txt")
|
|
let dest = tempDir.appendingPathComponent("existing.txt")
|
|
try "Source content".write(to: source, atomically: true, encoding: .utf8)
|
|
try "Old content".write(to: dest, atomically: true, encoding: .utf8)
|
|
|
|
// Rename should overwrite destination
|
|
try File.rename(source: source, destination: dest)
|
|
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: source.path))
|
|
XCTAssertEqual(try String(contentsOf: dest), "Source content")
|
|
}
|
|
|
|
func testRenameThrowsForNonExistentSource() throws {
|
|
let nonExistent = tempDir.appendingPathComponent("does-not-exist.txt")
|
|
|
|
// Test 1: When destination doesn't exist
|
|
let newDest = tempDir.appendingPathComponent("new-dest.txt")
|
|
XCTAssertThrowsError(try File.rename(source: nonExistent, destination: newDest))
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: newDest.path))
|
|
|
|
// Test 2: When destination exists (should be preserved on failure)
|
|
let existingDest = tempDir.appendingPathComponent("existing.txt")
|
|
let importantContent = "Important data that must not be lost"
|
|
try importantContent.write(to: existingDest, atomically: true, encoding: .utf8)
|
|
|
|
XCTAssertThrowsError(try File.rename(source: nonExistent, destination: existingDest))
|
|
|
|
// Destination should still exist with original content
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: existingDest.path))
|
|
XCTAssertEqual(try String(contentsOf: existingDest), importantContent)
|
|
}
|
|
|
|
// MARK: - Truncate Tests
|
|
|
|
func testTruncate() throws {
|
|
// TODO: Implement
|
|
// File.truncate(url, size) truncates file to size
|
|
}
|
|
|
|
func testTruncateExpands() throws {
|
|
// TODO: Implement
|
|
// truncate can expand file with zero padding
|
|
}
|
|
|
|
func testTruncateThrowsForNonExistent() throws {
|
|
// TODO: Implement
|
|
}
|
|
|
|
func testInstanceTruncate() throws {
|
|
// TODO: Implement
|
|
// file.truncate(size) truncates open file
|
|
}
|
|
|
|
// MARK: - Touch Tests
|
|
|
|
func testTouch() throws {
|
|
// Create a file with old times
|
|
let file = tempDir.appendingPathComponent("touch-test.txt")
|
|
try "content".write(to: file, atomically: true, encoding: .utf8)
|
|
|
|
// Get initial mtime
|
|
let initialMtime = try File.mtime(file)
|
|
|
|
// Wait a moment and touch
|
|
Thread.sleep(forTimeInterval: 0.1)
|
|
try File.touch(file)
|
|
|
|
// mtime should be updated
|
|
let newMtime = try File.mtime(file)
|
|
XCTAssertGreaterThan(newMtime, initialMtime)
|
|
}
|
|
|
|
func testTouchCreatesFile() throws {
|
|
// Touch non-existent file should create it
|
|
let newFile = tempDir.appendingPathComponent("created-by-touch.txt")
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: newFile.path))
|
|
|
|
try File.touch(newFile)
|
|
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: newFile.path))
|
|
XCTAssertEqual(try File.size(newFile), 0) // Should be empty
|
|
}
|
|
|
|
// MARK: - utime Tests
|
|
|
|
func testUtime() throws {
|
|
// TODO: Implement
|
|
// File.utime(url, atime, mtime) sets specific times
|
|
}
|
|
|
|
func testUtimeFollowsSymlinks() throws {
|
|
// TODO: Implement
|
|
// utime affects target of symlink
|
|
}
|
|
|
|
func testLutime() throws {
|
|
// TODO: Implement
|
|
// File.lutime(url, atime, mtime) sets symlink times
|
|
}
|
|
|
|
// MARK: - mkfifo Tests
|
|
|
|
func testMkfifo() throws {
|
|
// TODO: Implement
|
|
// File.mkfifo(url) creates named pipe
|
|
}
|
|
|
|
func testMkfifoWithPermissions() throws {
|
|
// TODO: Implement
|
|
// File.mkfifo(url, permissions) creates with specific perms
|
|
}
|
|
|
|
func testMkfifoThrowsIfExists() throws {
|
|
// TODO: Implement
|
|
}
|
|
|
|
// MARK: - identical Tests
|
|
|
|
func testIdentical() throws {
|
|
// TODO: Implement
|
|
// File.identical(url1, url2) returns true for same file
|
|
}
|
|
|
|
func testIdenticalForHardLink() throws {
|
|
// TODO: Implement
|
|
// identical returns true for hard links to same file
|
|
}
|
|
|
|
func testIdenticalForSymlink() throws {
|
|
// TODO: Implement
|
|
// identical returns true for symlink and target
|
|
}
|
|
|
|
func testIdenticalForDifferent() throws {
|
|
// TODO: Implement
|
|
// identical returns false for different files
|
|
}
|
|
|
|
func testIdenticalForSameContent() throws {
|
|
// TODO: Implement
|
|
// identical returns false for files with same content but different inodes
|
|
}
|
|
}
|