FileOtter/FileOtterTests/FilePermissionTests.swift

243 lines
7.3 KiB
Swift

//
// FilePermissionTests.swift
// FileOtterTests
//
// Created by Sami Samhuri on 2025-08-19.
//
@testable import FileOtter
import XCTest
final class FilePermissionTests: XCTestCase {
var tempDir: URL!
var testFile: URL!
var readOnlyFile: URL!
var executableFile: URL!
override func setUpWithError() throws {
tempDir = URL.temporaryDirectory
.appendingPathComponent("FilePermissionTests-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
testFile = tempDir.appendingPathComponent("test.txt")
try "Test content".write(to: testFile, atomically: true, encoding: .utf8)
readOnlyFile = tempDir.appendingPathComponent("readonly.txt")
try "Read only".write(to: readOnlyFile, atomically: true, encoding: .utf8)
executableFile = tempDir.appendingPathComponent("script.sh")
try "#!/bin/sh\necho hello".write(to: executableFile, atomically: true, encoding: .utf8)
}
override func tearDownWithError() throws {
if FileManager.default.fileExists(atPath: tempDir.path) {
try FileManager.default.removeItem(at: tempDir)
}
}
// MARK: - Basic Permission Tests
func testIsReadable() throws {
// Normal files should be readable
XCTAssertTrue(File.isReadable(testFile))
// System files are generally readable
XCTAssertTrue(File.isReadable(URL(fileURLWithPath: "/etc/hosts")))
// Non-existent files are not readable
let nonExistent = tempDir.appendingPathComponent("nonexistent")
XCTAssertFalse(File.isReadable(nonExistent))
}
func testIsWritable() throws {
// Files we created should be writable
XCTAssertTrue(File.isWritable(testFile))
// System files are generally not writable
XCTAssertFalse(File.isWritable(URL(fileURLWithPath: "/etc/hosts")))
// Non-existent files are not writable
let nonExistent = tempDir.appendingPathComponent("nonexistent")
XCTAssertFalse(File.isWritable(nonExistent))
}
func testIsExecutable() throws {
// Make the script executable
try FileManager.default.setAttributes(
[.posixPermissions: 0o755],
ofItemAtPath: executableFile.path,
)
XCTAssertTrue(File.isExecutable(executableFile))
// System executables
XCTAssertTrue(File.isExecutable(URL(fileURLWithPath: "/bin/ls")))
XCTAssertTrue(File.isExecutable(URL(fileURLWithPath: "/usr/bin/swift")))
}
func testIsExecutableForNonExecutable() throws {
// Regular text files are not executable
XCTAssertFalse(File.isExecutable(testFile))
XCTAssertFalse(File.isExecutable(readOnlyFile))
// Non-existent files are not executable
let nonExistent = tempDir.appendingPathComponent("nonexistent")
XCTAssertFalse(File.isExecutable(nonExistent))
}
// MARK: - Ownership Tests
func testIsOwned() throws {
// Files we create should be owned by us
XCTAssertTrue(File.isOwned(testFile))
XCTAssertTrue(File.isOwned(readOnlyFile))
XCTAssertTrue(File.isOwned(executableFile))
// System files may not be owned by us
// This depends on the user running the test
}
func testIsGroupOwned() throws {
// Files we create should be owned by our effective group
XCTAssertTrue(File.isGroupOwned(testFile))
XCTAssertTrue(File.isGroupOwned(readOnlyFile))
XCTAssertTrue(File.isGroupOwned(executableFile))
}
// MARK: - World Permission Tests
func testIsWorldReadable() throws {
// Make file world readable
try FileManager.default.setAttributes(
[.posixPermissions: 0o644],
ofItemAtPath: testFile.path,
)
let perms = File.isWorldReadable(testFile)
XCTAssertNotNil(perms)
if let perms {
XCTAssertEqual(perms & 0o004, 0o004) // Check world read bit
}
}
func testIsWorldReadableForPrivate() throws {
// Make file not world readable
try FileManager.default.setAttributes(
[.posixPermissions: 0o640],
ofItemAtPath: readOnlyFile.path,
)
XCTAssertNil(File.isWorldReadable(readOnlyFile))
}
func testIsWorldWritable() throws {
// Make file world writable (dangerous in practice!)
try FileManager.default.setAttributes(
[.posixPermissions: 0o666],
ofItemAtPath: testFile.path,
)
let perms = File.isWorldWritable(testFile)
XCTAssertNotNil(perms)
if let perms {
XCTAssertEqual(perms & 0o002, 0o002) // Check world write bit
}
}
func testIsWorldWritableForProtected() throws {
// Make file not world writable
try FileManager.default.setAttributes(
[.posixPermissions: 0o644],
ofItemAtPath: readOnlyFile.path,
)
XCTAssertNil(File.isWorldWritable(readOnlyFile))
}
// MARK: - Special Bit Tests
func testIsSetuid() throws {
// Setuid is rarely used on regular files
// Most files should not have setuid
XCTAssertFalse(File.isSetuid(testFile))
// /usr/bin/sudo typically has setuid (if it exists)
let sudo = URL(fileURLWithPath: "/usr/bin/sudo")
if FileManager.default.fileExists(atPath: sudo.path) {
// This might be true on some systems
_ = File.isSetuid(sudo)
}
}
func testIsSetgid() throws {
// Setgid is rarely used on regular files
XCTAssertFalse(File.isSetgid(testFile))
}
func testIsSticky() throws {
// Sticky bit is typically set on /tmp
let tmpDir = URL(fileURLWithPath: "/tmp")
if FileManager.default.fileExists(atPath: tmpDir.path) {
// /tmp usually has sticky bit
_ = File.isSticky(tmpDir)
}
// Regular files should not have sticky bit
XCTAssertFalse(File.isSticky(testFile))
}
// MARK: - chmod Tests
func testChmod() throws {
// TODO: Implement
// File.chmod(url, permissions) changes file permissions
}
func testChmodThrowsForNonExistent() throws {
// TODO: Implement
}
func testLchmod() throws {
// TODO: Implement
// File.lchmod(url, permissions) changes symlink permissions
}
func testInstanceChmod() throws {
// TODO: Implement
// file.chmod(permissions) changes open file permissions
}
// MARK: - chown Tests
func testChown() throws {
// TODO: Implement
// File.chown(url, owner, group) changes ownership
// Note: May require special privileges
}
func testChownWithNilValues() throws {
// TODO: Implement
// nil owner or group means don't change that value
}
func testLchown() throws {
// TODO: Implement
// File.lchown(url, owner, group) changes symlink ownership
}
func testInstanceChown() throws {
// TODO: Implement
// file.chown(owner, group) changes open file ownership
}
// MARK: - umask Tests
func testUmask() throws {
// TODO: Implement
// File.umask() returns current umask
}
func testUmaskSet() throws {
// TODO: Implement
// File.umask(mask) sets umask and returns previous value
}
}