diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d3e46b2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,18 @@ +# PPPC Utility + +## Swift Concurrency + +- `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` is set on both app and test targets — don't add explicit `@MainActor` to production code or test structs/functions, it's already the default + +## Swift Testing Migration + +- Place `@Test` and `@Suite` annotations on the line **above** the declaration, not inline +- Use `// when` and `// then` comment blocks; skip `// given` (assumed from context) +- When XCTest assertions have message strings, preserve them as `#expect` messages, not code comments (e.g. `#expect(x == false, "reason")`) +- Avoid `#require` on `Bool?` — it's ambiguous; use `#expect(x == true)` instead +- Capture a baseline of compiler warnings before each phase, then verify no new warnings after. Use this command and compare the output before/after: + ``` + xcodebuild clean build-for-testing -project "PPPC Utility.xcodeproj" -scheme "PPPC Utility" -destination "platform=macOS" 2>&1 | grep -i "warning:" | grep -v "xcodebuild: WARNING" + ``` +- Use parameterized tests with Traits where it reduces duplication; 1–2 args is ideal, max 3 +- Beyond 3 params: create separate tests with some values hard-coded diff --git a/PPPC Utility.xcodeproj/project.pbxproj b/PPPC Utility.xcodeproj/project.pbxproj index b4f1ed4..11b3e67 100644 --- a/PPPC Utility.xcodeproj/project.pbxproj +++ b/PPPC Utility.xcodeproj/project.pbxproj @@ -547,9 +547,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = XPLDEEDNHE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "PPPC UtilityTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -571,9 +572,10 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = XPLDEEDNHE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "PPPC UtilityTests/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -711,9 +713,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Resources/PPPC Utility.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = XPLDEEDNHE; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = NO; INFOPLIST_FILE = Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -734,9 +737,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "Resources/PPPC Utility.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = XPLDEEDNHE; + DEVELOPMENT_TEAM = ""; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/PPPC UtilityTests/ModelTests/ModelTests.swift b/PPPC UtilityTests/ModelTests/ModelTests.swift index e6a64e2..8e838cb 100644 --- a/PPPC UtilityTests/ModelTests/ModelTests.swift +++ b/PPPC UtilityTests/ModelTests/ModelTests.swift @@ -26,23 +26,19 @@ // import Foundation -import XCTest +import Testing @testable import PPPC_Utility -class ModelTests: XCTestCase { +@Suite +struct ModelTests { - var model: Model! - - override func setUp() { - super.setUp() - model = Model() - } + let model = Model() // MARK: - tests for getExecutableFrom* - func testGetExecutableBasedOnIdentifierAndCodeRequirement_BundleIdentifierType() { - // given + @Test + func getExecutableBasedOnIdentifierAndCodeRequirement_BundleIdentifierType() { let identifier = "com.example.App" let codeRequirement = "testCodeRequirement" @@ -50,13 +46,13 @@ class ModelTests: XCTestCase { let executable = model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) // then - XCTAssertEqual(executable.displayName, "App") - XCTAssertEqual(executable.codeRequirement, codeRequirement) - XCTAssertEqual(executable.iconPath, IconFilePath.application) + #expect(executable.displayName == "App") + #expect(executable.codeRequirement == codeRequirement) + #expect(executable.iconPath == IconFilePath.application) } - func testGetExecutableBasedOnIdentifierAndCodeRequirement_PathIdentifierType() { - // given + @Test + func getExecutableBasedOnIdentifierAndCodeRequirement_PathIdentifierType() { let identifier = "/myGreatPath/Awesome/Binary" let codeRequirement = "testCodeRequirement" @@ -64,13 +60,13 @@ class ModelTests: XCTestCase { let executable = model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) // then - XCTAssertEqual(executable.displayName, "Binary") - XCTAssertEqual(executable.codeRequirement, codeRequirement) - XCTAssertEqual(executable.iconPath, IconFilePath.binary) + #expect(executable.displayName == "Binary") + #expect(executable.codeRequirement == codeRequirement) + #expect(executable.iconPath == IconFilePath.binary) } - func testGetExecutableFromComputerBasedOnIdentifier() { - // given + @Test + func getExecutableFromComputerBasedOnIdentifier() { let identifier = "com.apple.Safari" let codeRequirement = "randomReq" @@ -78,13 +74,13 @@ class ModelTests: XCTestCase { let executable = model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) // then - XCTAssertEqual(executable.displayName, "Safari") - XCTAssertNotEqual(executable.iconPath, IconFilePath.application) - XCTAssertNotEqual(codeRequirement, executable.codeRequirement) + #expect(executable.displayName == "Safari") + #expect(executable.iconPath != IconFilePath.application) + #expect(executable.codeRequirement != codeRequirement) } - func testGetExecutableFromSelectedExecutables() { - // given + @Test + func getExecutableFromSelectedExecutables() { let expectedIdentifier = "com.something.1" let executable = model.getExecutableFrom(identifier: expectedIdentifier, codeRequirement: "testReq") let executableSecond = model.getExecutableFrom(identifier: "com.something.2", codeRequirement: "testReq2") @@ -94,14 +90,14 @@ class ModelTests: XCTestCase { let existingExecutable = model.getExecutableFromSelectedExecutables(bundleIdentifier: "com.something.1") // then - XCTAssertNotNil(existingExecutable) - XCTAssertEqual(existingExecutable?.identifier, expectedIdentifier) - XCTAssertEqual(existingExecutable?.displayName, "1") - XCTAssertEqual(existingExecutable?.iconPath, IconFilePath.application) + #expect(existingExecutable != nil) + #expect(existingExecutable?.identifier == expectedIdentifier) + #expect(existingExecutable?.displayName == "1") + #expect(existingExecutable?.iconPath == IconFilePath.application) } - func testGetExecutableFromSelectedExecutables_Path() { - // given + @Test + func getExecutableFromSelectedExecutables_Path() { let expectedIdentifier = "/path/something/Special" let executableOneMore = model.getExecutableFrom(identifier: "/path/something/Special1", codeRequirement: "testReq") let executable = model.getExecutableFrom(identifier: expectedIdentifier, codeRequirement: "testReq") @@ -112,24 +108,25 @@ class ModelTests: XCTestCase { let existingExecutable = model.getExecutableFromSelectedExecutables(bundleIdentifier: "/path/something/Special") // then - XCTAssertNotNil(existingExecutable) - XCTAssertEqual(existingExecutable?.identifier, expectedIdentifier) - XCTAssertEqual(existingExecutable?.displayName, "Special") - XCTAssertEqual(existingExecutable?.iconPath, IconFilePath.binary) + #expect(existingExecutable != nil) + #expect(existingExecutable?.identifier == expectedIdentifier) + #expect(existingExecutable?.displayName == "Special") + #expect(existingExecutable?.iconPath == IconFilePath.binary) } - func testGetExecutableFromSelectedExecutables_Empty() { + @Test + func getExecutableFromSelectedExecutables_Empty() { // when let existingExecutable = model.getExecutableFromSelectedExecutables(bundleIdentifier: "com.something.1") // then - XCTAssertNil(existingExecutable) + #expect(existingExecutable == nil) } // MARK: - tests for exportProfile - func testExportProfileWithAppleEventsAndAuthorization() { - // given + @Test + func exportProfileWithAppleEventsAndAuthorization() { let exe1 = Executable(identifier: "one", codeRequirement: "oneReq") let exe2 = Executable(identifier: "two", codeRequirement: "twoReq") @@ -142,181 +139,178 @@ class ModelTests: XCTestCase { let profile = model.exportProfile(organization: "Org", identifier: "ID", displayName: "Name", payloadDescription: "Desc") // then check top level settings - XCTAssertEqual("Org", profile.organization) - XCTAssertEqual("ID", profile.identifier) - XCTAssertEqual("Name", profile.displayName) - XCTAssertEqual("Desc", profile.payloadDescription) - XCTAssertEqual("System", profile.scope) - XCTAssertEqual("Configuration", profile.type) - XCTAssertNotNil(profile.uuid) - XCTAssertEqual(1, profile.version) + #expect(profile.organization == "Org") + #expect(profile.identifier == "ID") + #expect(profile.displayName == "Name") + #expect(profile.payloadDescription == "Desc") + #expect(profile.scope == "System") + #expect(profile.type == "Configuration") + #expect(!profile.uuid.isEmpty) + #expect(profile.version == 1) // then check policy settings // then verify the payload content top level - XCTAssertEqual(1, profile.content.count) + #expect(profile.content.count == 1) profile.content.forEach { content in - XCTAssertNotNil(content.uuid) - XCTAssertEqual(1, content.version) + #expect(!content.uuid.isEmpty) + #expect(content.version == 1) // then verify the services - XCTAssertEqual(2, content.services.count) + #expect(content.services.count == 2) let appleEvents = content.services["AppleEvents"] - XCTAssertNotNil(appleEvents) + #expect(appleEvents != nil) let appleEventsPolicy = appleEvents?.first - XCTAssertEqual("one", appleEventsPolicy?.identifier) - XCTAssertEqual("oneReq", appleEventsPolicy?.codeRequirement) - XCTAssertEqual("bundleID", appleEventsPolicy?.identifierType) - XCTAssertEqual("two", appleEventsPolicy?.receiverIdentifier) - XCTAssertEqual("twoReq", appleEventsPolicy?.receiverCodeRequirement) - XCTAssertEqual("bundleID", appleEventsPolicy?.receiverIdentifierType) - XCTAssertTrue(appleEventsPolicy?.authorization == .allow) + #expect(appleEventsPolicy?.identifier == "one") + #expect(appleEventsPolicy?.codeRequirement == "oneReq") + #expect(appleEventsPolicy?.identifierType == "bundleID") + #expect(appleEventsPolicy?.receiverIdentifier == "two") + #expect(appleEventsPolicy?.receiverCodeRequirement == "twoReq") + #expect(appleEventsPolicy?.receiverIdentifierType == "bundleID") + #expect(appleEventsPolicy?.authorization == .allow) let allFiles = content.services["SystemPolicyAllFiles"] - XCTAssertNotNil(allFiles) + #expect(allFiles != nil) let allFilesPolicy = allFiles?.first - XCTAssertEqual("two", allFilesPolicy?.identifier) - XCTAssertEqual("twoReq", allFilesPolicy?.codeRequirement) - XCTAssertEqual("bundleID", allFilesPolicy?.identifierType) - XCTAssertNil(allFilesPolicy?.receiverIdentifier) - XCTAssertNil(allFilesPolicy?.receiverCodeRequirement) - XCTAssertNil(allFilesPolicy?.receiverIdentifierType) - XCTAssertTrue(allFilesPolicy?.authorization == .allowStandardUserToSetSystemService) + #expect(allFilesPolicy?.identifier == "two") + #expect(allFilesPolicy?.codeRequirement == "twoReq") + #expect(allFilesPolicy?.identifierType == "bundleID") + #expect(allFilesPolicy?.receiverIdentifier == nil) + #expect(allFilesPolicy?.receiverCodeRequirement == nil) + #expect(allFilesPolicy?.receiverIdentifierType == nil) + #expect(allFilesPolicy?.authorization == .allowStandardUserToSetSystemService) } } // MARK: - tests for importProfile - func testImportProfileUsingAuthorizationKeyAllow() { - // given + @Test + func importProfileUsingAuthorizationKeyAllow() { let profile = TCCProfileBuilder().buildProfile(authorization: .allow) // when model.importProfile(tccProfile: profile) // then - XCTAssertEqual(1, model.selectedExecutables.count) - XCTAssertEqual("Allow", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) + #expect(model.selectedExecutables.count == 1) + #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Allow") } - func testImportProfileUsingAuthorizationKeyDeny() { - // given + @Test + func importProfileUsingAuthorizationKeyDeny() { let profile = TCCProfileBuilder().buildProfile(authorization: .deny) // when model.importProfile(tccProfile: profile) // then - XCTAssertEqual(1, model.selectedExecutables.count) - XCTAssertEqual("Deny", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) + #expect(model.selectedExecutables.count == 1) + #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny") } - func testImportProfileUsingAuthorizationKeyAllowStandardUsers() { - // given + @Test + func importProfileUsingAuthorizationKeyAllowStandardUsers() { let profile = TCCProfileBuilder().buildProfile(authorization: .allowStandardUserToSetSystemService) // when model.importProfile(tccProfile: profile) // then - XCTAssertEqual(1, model.selectedExecutables.count) - XCTAssertEqual("Let Standard Users Approve", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) + #expect(model.selectedExecutables.count == 1) + #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Let Standard Users Approve") } - func testImportProfileUsingLegacyAllowKeyTrue() { - // given + @Test + func importProfileUsingLegacyAllowKeyTrue() { let profile = TCCProfileBuilder().buildProfile(allowed: true) // when model.importProfile(tccProfile: profile) // then - XCTAssertEqual(1, model.selectedExecutables.count) - XCTAssertEqual("Allow", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) + #expect(model.selectedExecutables.count == 1) + #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Allow") } - func testImportProfileUsingLegacyAllowKeyFalse() { - // given + @Test + func importProfileUsingLegacyAllowKeyFalse() { let profile = TCCProfileBuilder().buildProfile(allowed: false) // when model.importProfile(tccProfile: profile) // then - XCTAssertEqual(1, model.selectedExecutables.count) - XCTAssertEqual("Deny", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) + #expect(model.selectedExecutables.count == 1) + #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny") } - func testImportProfileUsingAuthorizationKeyThatIsInvalid() { - // given + @Test + func importProfileUsingAuthorizationKeyThatIsInvalid() { let profile = TCCProfileBuilder().buildProfile(authorization: "invalidkey") // when model.importProfile(tccProfile: profile) // then - XCTAssertEqual(1, model.selectedExecutables.count) - XCTAssertEqual("Deny", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) + #expect(model.selectedExecutables.count == 1) + #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny") } - func testImportProfileUsingAuthorizationKeyTranslatesToAppleEvents() { - // given + @Test + func importProfileUsingAuthorizationKeyTranslatesToAppleEvents() { let profile = TCCProfileBuilder().buildProfile(authorization: "deny") // when model.importProfile(tccProfile: profile) // then - XCTAssertEqual(1, model.selectedExecutables.count) - XCTAssertEqual("Deny", model.selectedExecutables.first?.policy.SystemPolicyAllFiles) + #expect(model.selectedExecutables.count == 1) + #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny") } - // MARK: - tests for policyFromString + // MARK: - tests for profileToString - func testPolicyWhenUsingAllow() { - // given + @Test + func policyWhenUsingAllow() { let app = Executable(identifier: "id", codeRequirement: "req") // when let policy = model.policyFromString(executable: app, value: "Allow") // then - XCTAssertEqual(policy?.authorization, TCCPolicyAuthorizationValue.allow) - XCTAssertNil(policy?.allowed) + #expect(policy?.authorization == .allow) } - func testPolicyWhenUsingDeny() { - // given + @Test + func policyWhenUsingDeny() { let app = Executable(identifier: "id", codeRequirement: "req") // when let policy = model.policyFromString(executable: app, value: "Deny") // then - XCTAssertEqual(policy?.authorization, TCCPolicyAuthorizationValue.deny) - XCTAssertNil(policy?.allowed) + #expect(policy?.authorization == .deny) } - func testPolicyWhenUsingAllowForStandardUsers() { - // given + @Test + func policyWhenUsingAllowForStandardUsers() { let app = Executable(identifier: "id", codeRequirement: "req") // when let policy = model.policyFromString(executable: app, value: "Let Standard Users Approve") // then - XCTAssertEqual(policy?.authorization, TCCPolicyAuthorizationValue.allowStandardUserToSetSystemService) - XCTAssertNil(policy?.allowed) + #expect(policy?.authorization == .allowStandardUserToSetSystemService) } - func testPolicyWhenUsingUnknownValue() { - // given + @Test + func policyWhenUsingUnknownValue() { let app = Executable(identifier: "id", codeRequirement: "req") // when let policy = model.policyFromString(executable: app, value: "For MDM Admins Only") // then - XCTAssertNil(policy, "should have not created the policy with an unknown value") + #expect(policy == nil, "should have not created the policy with an unknown value") } } diff --git a/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift b/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift index 70df11a..ca8bf7e 100644 --- a/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift +++ b/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift @@ -26,68 +26,64 @@ // import Foundation -import XCTest +import Testing @testable import PPPC_Utility -class PPPCServicesManagerTests: XCTestCase { +@Suite +struct PPPCServicesManagerTests { - func testLoadAllServices() { - // given/when + @Test + func loadAllServices() { + // when let actual = PPPCServicesManager() // then - XCTAssertEqual(actual.allServices.count, 21) + #expect(actual.allServices.count == 21) } - func testUserHelp_withEntitlements() throws { - // given + @Test + func userHelp_withEntitlements() throws { let services = PPPCServicesManager() - let service = try XCTUnwrap(services.allServices["Camera"]) + let service = try #require(services.allServices["Camera"]) // when let actual = service.userHelp // then - XCTAssertEqual( - actual, - "Use to deny specified apps access to the camera.\n\nMDM Key: Camera\nRelated entitlements: [\"com.apple.developer.avfoundation.multitasking-camera-access\", \"com.apple.security.device.camera\"]" + #expect( + actual + == "Use to deny specified apps access to the camera.\n\nMDM Key: Camera\nRelated entitlements: [\"com.apple.developer.avfoundation.multitasking-camera-access\", \"com.apple.security.device.camera\"]" ) } - func testUserHelp_withoutEntitlements() throws { - // given + @Test + func userHelp_withoutEntitlements() throws { let services = PPPCServicesManager() - let service = try XCTUnwrap(services.allServices["ScreenCapture"]) + let service = try #require(services.allServices["ScreenCapture"]) // when let actual = service.userHelp // then - XCTAssertEqual(actual, "Deny specified apps access to capture (read) the contents of the system display.\n\nMDM Key: ScreenCapture") + #expect(actual == "Deny specified apps access to capture (read) the contents of the system display.\n\nMDM Key: ScreenCapture") } - func testCameraIsDenyOnly() throws { - // given + @Test + func cameraIsDenyOnly() throws { let services = PPPCServicesManager() - let service = try XCTUnwrap(services.allServices["Camera"]) - - // when - let actual = try XCTUnwrap(service.denyOnly) + let service = try #require(services.allServices["Camera"]) // then - XCTAssertTrue(actual) + #expect(service.denyOnly == true) } - func testScreenCaptureAllowsStandardUsers() throws { - // given + @Test + func screenCaptureAllowsStandardUsers() throws { let services = PPPCServicesManager() - let service = try XCTUnwrap(services.allServices["ScreenCapture"]) - - // when - let actual = try XCTUnwrap(service.allowStandardUsers) + let service = try #require(services.allServices["ScreenCapture"]) // then - XCTAssertTrue(actual) + #expect(service.allowStandardUsers == true) } } diff --git a/PPPC UtilityTests/ModelTests/SemanticVersionTests.swift b/PPPC UtilityTests/ModelTests/SemanticVersionTests.swift index c37c5e4..4c5c8c1 100644 --- a/PPPC UtilityTests/ModelTests/SemanticVersionTests.swift +++ b/PPPC UtilityTests/ModelTests/SemanticVersionTests.swift @@ -26,71 +26,41 @@ // import Foundation -import XCTest +import Testing @testable import PPPC_Utility -class SemanticVersionTests: XCTestCase { - func testLessThan() { - // given - let version = SemanticVersion(major: 10, minor: 7, patch: 1) +@Suite +struct SemanticVersionTests { + let version = SemanticVersion(major: 10, minor: 7, patch: 1) - // when - let shouldBeLessThan = version < SemanticVersion(major: 10, minor: 7, patch: 4) - let shouldBeLessThan2 = version < SemanticVersion(major: 10, minor: 8, patch: 1) - let shouldBeLessThan3 = version < SemanticVersion(major: 11, minor: 7, patch: 1) - let shouldNotBeLessThan = version < SemanticVersion(major: 10, minor: 7, patch: 1) - let shouldNotBeLessThan2 = version < SemanticVersion(major: 10, minor: 7, patch: 0) - let shouldNotBeLessThan3 = version < SemanticVersion(major: 10, minor: 6, patch: 1) - let shouldNotBeLessThan4 = version < SemanticVersion(major: 9, minor: 7, patch: 1) - - // then - XCTAssertTrue(shouldBeLessThan) - XCTAssertTrue(shouldBeLessThan2) - XCTAssertTrue(shouldBeLessThan3) - XCTAssertFalse(shouldNotBeLessThan) - XCTAssertFalse(shouldNotBeLessThan2) - XCTAssertFalse(shouldNotBeLessThan3) - XCTAssertFalse(shouldNotBeLessThan4) + @Test + func lessThan() { + #expect(version < SemanticVersion(major: 10, minor: 7, patch: 4)) + #expect(version < SemanticVersion(major: 10, minor: 8, patch: 1)) + #expect(version < SemanticVersion(major: 11, minor: 7, patch: 1)) + #expect(!(version < SemanticVersion(major: 10, minor: 7, patch: 1))) + #expect(!(version < SemanticVersion(major: 10, minor: 7, patch: 0))) + #expect(!(version < SemanticVersion(major: 10, minor: 6, patch: 1))) + #expect(!(version < SemanticVersion(major: 9, minor: 7, patch: 1))) } - func testEquality() { - // given - let version = SemanticVersion(major: 10, minor: 7, patch: 1) - - // when - let shouldBeEqual = version == SemanticVersion(major: 10, minor: 7, patch: 1) - let shouldBeNotEqual = version == SemanticVersion(major: 10, minor: 7, patch: 4) - let shouldBeNotEqual2 = version == SemanticVersion(major: 10, minor: 8, patch: 1) - let shouldBeNotEqual3 = version == SemanticVersion(major: 11, minor: 7, patch: 1) - - // then - XCTAssertTrue(shouldBeEqual) - XCTAssertFalse(shouldBeNotEqual) - XCTAssertFalse(shouldBeNotEqual2) - XCTAssertFalse(shouldBeNotEqual3) + @Test + func equality() { + #expect(version == SemanticVersion(major: 10, minor: 7, patch: 1)) + #expect(version != SemanticVersion(major: 10, minor: 7, patch: 4)) + #expect(version != SemanticVersion(major: 10, minor: 8, patch: 1)) + #expect(version != SemanticVersion(major: 11, minor: 7, patch: 1)) } - func testGreaterThan() { - // given - let version = SemanticVersion(major: 10, minor: 7, patch: 1) - - // when - let shouldNotBeGreaterThan = version > SemanticVersion(major: 10, minor: 7, patch: 4) - let shouldNotBeGreaterThan2 = version > SemanticVersion(major: 10, minor: 8, patch: 1) - let shouldNotBeGreaterThan3 = version > SemanticVersion(major: 11, minor: 7, patch: 1) - let shouldNotBeGreaterThan4 = version > SemanticVersion(major: 10, minor: 7, patch: 1) - let shouldBeGreaterThan = version > SemanticVersion(major: 10, minor: 7, patch: 0) - let shouldBeGreaterThan2 = version > SemanticVersion(major: 10, minor: 6, patch: 1) - let shouldBeGreaterThan3 = version > SemanticVersion(major: 9, minor: 7, patch: 1) - - // then - XCTAssertFalse(shouldNotBeGreaterThan) - XCTAssertFalse(shouldNotBeGreaterThan2) - XCTAssertFalse(shouldNotBeGreaterThan3) - XCTAssertFalse(shouldNotBeGreaterThan4) - XCTAssertTrue(shouldBeGreaterThan) - XCTAssertTrue(shouldBeGreaterThan2) - XCTAssertTrue(shouldBeGreaterThan3) + @Test + func greaterThan() { + #expect(!(version > SemanticVersion(major: 10, minor: 7, patch: 4))) + #expect(!(version > SemanticVersion(major: 10, minor: 8, patch: 1))) + #expect(!(version > SemanticVersion(major: 11, minor: 7, patch: 1))) + #expect(!(version > SemanticVersion(major: 10, minor: 7, patch: 1))) + #expect(version > SemanticVersion(major: 10, minor: 7, patch: 0)) + #expect(version > SemanticVersion(major: 10, minor: 6, patch: 1)) + #expect(version > SemanticVersion(major: 9, minor: 7, patch: 1)) } } diff --git a/PPPC UtilityTests/NetworkingTests/JamfProAPIClientTests.swift b/PPPC UtilityTests/NetworkingTests/JamfProAPIClientTests.swift index 39e2798..f44979f 100644 --- a/PPPC UtilityTests/NetworkingTests/JamfProAPIClientTests.swift +++ b/PPPC UtilityTests/NetworkingTests/JamfProAPIClientTests.swift @@ -6,13 +6,14 @@ // Copyright (c) 2023 Jamf Software import Foundation -import XCTest +import Testing @testable import PPPC_Utility -class JamfProAPIClientTests: XCTestCase { - func testOAuthTokenRequest() throws { - // given +@Suite +struct JamfProAPIClientTests { + @Test + func oAuthTokenRequest() throws { let authManager = NetworkAuthManager(username: "", password: "") let apiClient = JamfProAPIClient(serverUrlString: "https://something", tokenManager: authManager) @@ -20,8 +21,8 @@ class JamfProAPIClientTests: XCTestCase { let request = try apiClient.oauthTokenRequest(clientId: "mine&yours", clientSecret: "foo bar") // then - let body = try XCTUnwrap(request.httpBody) + let body = try #require(request.httpBody) let bodyString = String(data: body, encoding: .utf8) - XCTAssertEqual(bodyString, "grant_type=client_credentials&client_id=mine%26yours&client_secret=foo%20bar") + #expect(bodyString == "grant_type=client_credentials&client_id=mine%26yours&client_secret=foo%20bar") } } diff --git a/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift b/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift index f78eb03..4eba732 100644 --- a/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift +++ b/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift @@ -26,11 +26,11 @@ // import Foundation -import XCTest +import Testing @testable import PPPC_Utility -/// Fake networking class for testing. No actual network was used in the making of this test. +/// Fake networking class for testing. No actual network was used in the making of this test. class MockNetworking: Networking { var errorToThrow: Error? @@ -45,15 +45,16 @@ class MockNetworking: Networking { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - let expiration = try XCTUnwrap(formatter.date(from: "2950-06-22T22:05:58.81Z")) + let expiration = try #require(formatter.date(from: "2950-06-22T22:05:58.81Z")) return Token(value: "xyz", expiresAt: expiration) } } -class NetworkAuthManagerTests: XCTestCase { - func testValidToken() async throws { - // given +@Suite +struct NetworkAuthManagerTests { + @Test + func validToken() async throws { let authManager = NetworkAuthManager(username: "test", password: "none") let networking = MockNetworking(tokenManager: authManager) @@ -61,102 +62,82 @@ class NetworkAuthManagerTests: XCTestCase { let token = try await authManager.validToken(networking: networking) // then - XCTAssertEqual(token.value, "xyz") - XCTAssertTrue(token.isValid) + #expect(token.value == "xyz") + #expect(token.isValid) } - func testValidTokenNetworkFailure() async throws { - // given + @Test + func validTokenNetworkFailure() async { let authManager = NetworkAuthManager(username: "test", password: "none") let networking = MockNetworking(tokenManager: authManager) networking.errorToThrow = NetworkingError.serverResponse(500, "Bad server") - // when - do { - _ = try await authManager.validToken(networking: networking) - XCTFail("Expected to throw from `validToken` call") - } catch { - // then - XCTAssertEqual(error as? NetworkingError, NetworkingError.serverResponse(500, "Bad server")) + // when/then + await #expect(throws: NetworkingError.serverResponse(500, "Bad server")) { + try await authManager.validToken(networking: networking) } } - func testValidTokenBearerAuthNotSupported() async throws { - // given + @Test + func validTokenBearerAuthNotSupported() async throws { let authManager = NetworkAuthManager(username: "test", password: "none") let networking = MockNetworking(tokenManager: authManager) networking.errorToThrow = NetworkingError.serverResponse(404, "No such page") // default is that bearer auth is supported. let firstCheckBearerAuthSupported = await authManager.bearerAuthSupported() - XCTAssertTrue(firstCheckBearerAuthSupported) + #expect(firstCheckBearerAuthSupported) - // when - do { - _ = try await authManager.validToken(networking: networking) - XCTFail("Expected to throw from `validToken` call") - } catch { - // then - should throw a `bearerAuthNotSupported` error - XCTAssertEqual(error as? AuthError, AuthError.bearerAuthNotSupported) + // when/then + await #expect(throws: AuthError.bearerAuthNotSupported) { + try await authManager.validToken(networking: networking) } // The authManager should now know that bearer auth is not supported let secondCheckBearerAuthSupported = await authManager.bearerAuthSupported() - XCTAssertFalse(secondCheckBearerAuthSupported) + #expect(!secondCheckBearerAuthSupported) } - func testValidTokenInvalidUsernamePassword() async throws { - // given + @Test + func validTokenInvalidUsernamePassword() async { let authManager = NetworkAuthManager(username: "test", password: "none") let networking = MockNetworking(tokenManager: authManager) networking.errorToThrow = NetworkingError.serverResponse(401, "Not authorized") - // when - do { - _ = try await authManager.validToken(networking: networking) - XCTFail("Expected to throw from `validToken` call") - } catch { - // then - should throw a `invalidUsernamePassword` error - XCTAssertEqual(error as? AuthError, AuthError.invalidUsernamePassword) + // when/then + await #expect(throws: AuthError.invalidUsernamePassword) { + try await authManager.validToken(networking: networking) } } - func testBasicAuthString() throws { - // given + @Test + func basicAuthString() throws { let authManager = NetworkAuthManager(username: "test", password: "none") // when let actual = try authManager.basicAuthString() // then - XCTAssertEqual(actual, "dGVzdDpub25l") + #expect(actual == "dGVzdDpub25l") } - func testBasicAuthStringEmptyUsername() throws { - // given + @Test + func basicAuthStringEmptyUsername() { let authManager = NetworkAuthManager(username: "", password: "none") - // when - do { - _ = try authManager.basicAuthString() - XCTFail("Expected to throw from `basicAuthString` call") - } catch { - // then - should throw a `invalidUsernamePassword` error - XCTAssertEqual(error as? AuthError, AuthError.invalidUsernamePassword) + // when/then + #expect(throws: AuthError.invalidUsernamePassword) { + try authManager.basicAuthString() } } - func testBasicAuthStringEmptyPassword() throws { - // given + @Test + func basicAuthStringEmptyPassword() { let authManager = NetworkAuthManager(username: "mine", password: "") - // when - do { - _ = try authManager.basicAuthString() - XCTFail("Expected to throw from `basicAuthString` call") - } catch { - // then - should throw a `invalidUsernamePassword` error - XCTAssertEqual(error as? AuthError, AuthError.invalidUsernamePassword) + // when/then + #expect(throws: AuthError.invalidUsernamePassword) { + try authManager.basicAuthString() } } } diff --git a/PPPC UtilityTests/NetworkingTests/TokenTests.swift b/PPPC UtilityTests/NetworkingTests/TokenTests.swift index 6ca3c6d..a7845fa 100644 --- a/PPPC UtilityTests/NetworkingTests/TokenTests.swift +++ b/PPPC UtilityTests/NetworkingTests/TokenTests.swift @@ -26,83 +26,84 @@ // import Foundation -import XCTest +import Testing @testable import PPPC_Utility -class TokenTests: XCTestCase { - func testPastIsNotValid() throws { - // given +@Suite +struct TokenTests { + @Test + func pastIsNotValid() throws { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - let expiration = try XCTUnwrap(formatter.date(from: "2021-06-22T22:05:58.81Z")) + let expiration = try #require(formatter.date(from: "2021-06-22T22:05:58.81Z")) let token = Token(value: "abc", expiresAt: expiration) // when let valid = token.isValid // then - XCTAssertFalse(valid) + #expect(!valid) } - func testFutureIsValid() throws { - // given + @Test + func futureIsValid() throws { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - let expiration = try XCTUnwrap(formatter.date(from: "2750-06-22T22:05:58.81Z")) + let expiration = try #require(formatter.date(from: "2750-06-22T22:05:58.81Z")) let token = Token(value: "abc", expiresAt: expiration) // when let valid = token.isValid // then - XCTAssertTrue(valid) + #expect(valid) } // MARK: - Decoding - func testDecodeBasicAuthToken() throws { - // given + @Test + func decodeBasicAuthToken() throws { let jsonText = """ { "token": "abc", "expires": "2750-06-22T22:05:58.81Z" } """ - let jsonData = try XCTUnwrap(jsonText.data(using: .utf8)) + let jsonData = try #require(jsonText.data(using: .utf8)) let decoder = JSONDecoder() // when let actual = try decoder.decode(Token.self, from: jsonData) // then - XCTAssertEqual(actual.value, "abc") - XCTAssertNotNil(actual.expiresAt) - XCTAssertTrue(actual.isValid) + #expect(actual.value == "abc") + #expect(actual.expiresAt != nil) + #expect(actual.isValid) } - func testDecodeExpiredBasicAuthToken() throws { - // given + @Test + func decodeExpiredBasicAuthToken() throws { let jsonText = """ { "token": "abc", "expires": "1970-10-24T22:05:58.81Z" } """ - let jsonData = try XCTUnwrap(jsonText.data(using: .utf8)) + let jsonData = try #require(jsonText.data(using: .utf8)) let decoder = JSONDecoder() // when let actual = try decoder.decode(Token.self, from: jsonData) // then - XCTAssertEqual(actual.value, "abc") - XCTAssertNotNil(actual.expiresAt) - XCTAssertFalse(actual.isValid) + #expect(actual.value == "abc") + #expect(actual.expiresAt != nil) + #expect(!actual.isValid) } - func testDecodeClientCredentialsAuthToken() throws { - // given + @Test + func decodeClientCredentialsAuthToken() throws { let jsonText = """ { "access_token": "abc", @@ -111,15 +112,15 @@ class TokenTests: XCTestCase { "expires_in": 599 } """ - let jsonData = try XCTUnwrap(jsonText.data(using: .utf8)) + let jsonData = try #require(jsonText.data(using: .utf8)) let decoder = JSONDecoder() // when let actual = try decoder.decode(Token.self, from: jsonData) // then - XCTAssertEqual(actual.value, "abc") - XCTAssertNotNil(actual.expiresAt) - XCTAssertTrue(actual.isValid) + #expect(actual.value == "abc") + #expect(actual.expiresAt != nil) + #expect(actual.isValid) } } diff --git a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift index db7f9db..4f1af27 100644 --- a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift +++ b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift @@ -26,102 +26,99 @@ // import Foundation -import XCTest +import Testing @testable import PPPC_Utility -class TCCProfileImporterTests: XCTestCase { +private final class BundleLocator {} - func testMalformedTCCProfile() { - let tccProfileImporter = TCCProfileImporter() +@Suite +struct TCCProfileImporterTests { - let resourceURL = getResourceProfile(fileName: "TestTCCProfileSigned-Broken") + @Test + func malformedTCCProfile() throws { + let tccProfileImporter = TCCProfileImporter() + let resourceURL = try getResourceProfile(fileName: "TestTCCProfileSigned-Broken") tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in switch tccProfileResult { case .success: - XCTFail("Malformed profile should not succeed") + Issue.record("Malformed profile should not succeed") case .failure(let tccProfileError): if case TCCProfileImportError.invalidProfileFile = tccProfileError { } else { - XCTFail("Expected invalidProfileFile error, got \(tccProfileError)") + Issue.record("Expected invalidProfileFile error, got \(tccProfileError)") } } } } - func testEmptyContentTCCProfile() { + @Test + func emptyContentTCCProfile() throws { let tccProfileImporter = TCCProfileImporter() - - let resourceURL = getResourceProfile(fileName: "TestTCCUnsignedProfile-Empty") - + let resourceURL = try getResourceProfile(fileName: "TestTCCUnsignedProfile-Empty") let expectedTCCProfileError = TCCProfileImportError.invalidProfileFile(description: "PayloadContent") tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in switch tccProfileResult { case .success: - XCTFail("Empty Content, it shouldn't be success") + Issue.record("Empty Content, it shouldn't be success") case .failure(let tccProfileError): - XCTAssertEqual(tccProfileError.localizedDescription, expectedTCCProfileError.localizedDescription) + #expect(tccProfileError.localizedDescription == expectedTCCProfileError.localizedDescription) } } } - func testCorrectUnsignedProfileContentData() { + @Test + func correctUnsignedProfileContentData() throws { let tccProfileImporter = TCCProfileImporter() - - let resourceURL = getResourceProfile(fileName: "TestTCCUnsignedProfile") + let resourceURL = try getResourceProfile(fileName: "TestTCCUnsignedProfile") tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in switch tccProfileResult { case .success(let tccProfile): - XCTAssertNotNil(tccProfile.content) - XCTAssertNotNil(tccProfile.content[0].services) + #expect(!tccProfile.content.isEmpty) + #expect(!tccProfile.content[0].services.isEmpty) case .failure(let tccProfileError): - XCTFail("Unable to read tccProfile \(tccProfileError.localizedDescription)") + Issue.record("Unable to read tccProfile \(tccProfileError.localizedDescription)") } } } - func testCorrectUnsignedProfileContentDataAllLowercase() { + @Test + func correctUnsignedProfileContentDataAllLowercase() throws { let tccProfileImporter = TCCProfileImporter() - - let resourceURL = getResourceProfile(fileName: "TestTCCUnsignedProfile-allLower") + let resourceURL = try getResourceProfile(fileName: "TestTCCUnsignedProfile-allLower") tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in switch tccProfileResult { case .success(let tccProfile): - XCTAssertNotNil(tccProfile.content) - XCTAssertNotNil(tccProfile.content[0].services) + #expect(!tccProfile.content.isEmpty) + #expect(!tccProfile.content[0].services.isEmpty) case .failure(let tccProfileError): - XCTFail("Unable to read tccProfile \(tccProfileError.localizedDescription)") + Issue.record("Unable to read tccProfile \(tccProfileError.localizedDescription)") } } } - func testBrokenUnsignedProfile() { + @Test + func brokenUnsignedProfile() throws { let tccProfileImporter = TCCProfileImporter() - - let resourceURL = getResourceProfile(fileName: "TestTCCUnsignedProfile-Broken") - + let resourceURL = try getResourceProfile(fileName: "TestTCCUnsignedProfile-Broken") let expectedTCCProfileError = TCCProfileImportError.invalidProfileFile(description: "The given data was not a valid property list.") tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in switch tccProfileResult { case .success: - XCTFail("Broken Unsigned Profile, it shouldn't be success") + Issue.record("Broken Unsigned Profile, it shouldn't be success") case .failure(let tccProfileError): - XCTAssertEqual(tccProfileError.localizedDescription, expectedTCCProfileError.localizedDescription) + #expect(tccProfileError.localizedDescription == expectedTCCProfileError.localizedDescription) } } } - private func getResourceProfile(fileName: String) -> URL { - let testBundle = Bundle(for: type(of: self)) - guard let resourceURL = testBundle.url(forResource: fileName, withExtension: "mobileconfig") else { - XCTFail("Resource file should exists") - return URL(fileURLWithPath: "invalidPath") - } - return resourceURL + private func getResourceProfile(fileName: String) throws -> URL { + let testBundle = Bundle(for: BundleLocator.self) + return try #require(testBundle.url(forResource: fileName, withExtension: "mobileconfig")) } } diff --git a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift index f643e93..761e274 100644 --- a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift +++ b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift @@ -23,131 +23,138 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // -import XCTest +import Foundation +import Testing @testable import PPPC_Utility -class TCCProfileTests: XCTestCase { +private final class BundleLocator {} + +@Suite +struct TCCProfileTests { // MARK: - tests for serializing to and from xml - func testSerializationOfComplexProfileUsingAuthorization() throws { + @Test + func serializationOfComplexProfileUsingAuthorization() throws { // when we export to xml and reimport it should still have the same attributes let plistData = try TCCProfileBuilder().buildProfile(authorization: .allowStandardUserToSetSystemService).xmlData() let profile = try TCCProfile.parse(from: plistData) // then verify the config profile top level - XCTAssertEqual("Configuration", profile.type) - XCTAssertEqual(100, profile.version) - XCTAssertEqual("the uuid", profile.uuid) - XCTAssertEqual("System", profile.scope) - XCTAssertEqual("Test Org", profile.organization) - XCTAssertEqual("Test ID", profile.identifier) - XCTAssertEqual("Test Name", profile.displayName) - XCTAssertEqual("Test Desc", profile.payloadDescription) + #expect(profile.type == "Configuration") + #expect(profile.version == 100) + #expect(profile.uuid == "the uuid") + #expect(profile.scope == "System") + #expect(profile.organization == "Test Org") + #expect(profile.identifier == "Test ID") + #expect(profile.displayName == "Test Name") + #expect(profile.payloadDescription == "Test Desc") // then verify the payload content top level - XCTAssertEqual(1, profile.content.count) + #expect(profile.content.count == 1) profile.content.forEach { content in - XCTAssertEqual("Content Desc 1", content.payloadDescription) - XCTAssertEqual("Content Name 1", content.displayName) - XCTAssertEqual("Content ID 1", content.identifier) - XCTAssertEqual("Content Org 1", content.organization) - XCTAssertEqual("Content type 1", content.type) - XCTAssertEqual("Content UUID 1", content.uuid) - XCTAssertEqual(1, content.version) + #expect(content.payloadDescription == "Content Desc 1") + #expect(content.displayName == "Content Name 1") + #expect(content.identifier == "Content ID 1") + #expect(content.organization == "Content Org 1") + #expect(content.type == "Content type 1") + #expect(content.uuid == "Content UUID 1") + #expect(content.version == 1) // then verify the services key - XCTAssertEqual(2, content.services.count) + #expect(content.services.count == 2) let allFiles = content.services["SystemPolicyAllFiles"] - XCTAssertEqual(1, allFiles?.count) + #expect(allFiles?.count == 1) allFiles?.forEach { policy in - XCTAssertEqual("policy id", policy.identifier) - XCTAssertEqual("policy id type", policy.identifierType) - XCTAssertEqual("policy code req", policy.codeRequirement) - XCTAssertNil(policy.allowed) - XCTAssertEqual(TCCPolicyAuthorizationValue.allowStandardUserToSetSystemService, policy.authorization) - XCTAssertEqual("policy comment", policy.comment) - XCTAssertEqual("policy receiver id", policy.receiverIdentifier) - XCTAssertEqual("policy receiver id type", policy.receiverIdentifierType) - XCTAssertEqual("policy receiver code req", policy.receiverCodeRequirement) + #expect(policy.identifier == "policy id") + #expect(policy.identifierType == "policy id type") + #expect(policy.codeRequirement == "policy code req") + #expect(policy.allowed == nil) + #expect(policy.authorization == .allowStandardUserToSetSystemService) + #expect(policy.comment == "policy comment") + #expect(policy.receiverIdentifier == "policy receiver id") + #expect(policy.receiverIdentifierType == "policy receiver id type") + #expect(policy.receiverCodeRequirement == "policy receiver code req") } } } - func testSerializationOfProfileUsingLegacyAllowedKey() throws { + @Test + func serializationOfProfileUsingLegacyAllowedKey() throws { // when we export to xml and reimport it should still have the same attributes let plistData = try TCCProfileBuilder().buildProfile(allowed: true).xmlData() let profile = try TCCProfile.parse(from: plistData) // then verify the config profile top level - XCTAssertEqual("Configuration", profile.type) - XCTAssertEqual(100, profile.version) - XCTAssertEqual("the uuid", profile.uuid) - XCTAssertEqual("System", profile.scope) - XCTAssertEqual("Test Org", profile.organization) - XCTAssertEqual("Test ID", profile.identifier) - XCTAssertEqual("Test Name", profile.displayName) - XCTAssertEqual("Test Desc", profile.payloadDescription) + #expect(profile.type == "Configuration") + #expect(profile.version == 100) + #expect(profile.uuid == "the uuid") + #expect(profile.scope == "System") + #expect(profile.organization == "Test Org") + #expect(profile.identifier == "Test ID") + #expect(profile.displayName == "Test Name") + #expect(profile.payloadDescription == "Test Desc") // then verify the payload content top level - XCTAssertEqual(1, profile.content.count) + #expect(profile.content.count == 1) profile.content.forEach { content in - XCTAssertEqual("Content Desc 1", content.payloadDescription) - XCTAssertEqual("Content Name 1", content.displayName) - XCTAssertEqual("Content ID 1", content.identifier) - XCTAssertEqual("Content Org 1", content.organization) - XCTAssertEqual("Content type 1", content.type) - XCTAssertEqual("Content UUID 1", content.uuid) - XCTAssertEqual(1, content.version) + #expect(content.payloadDescription == "Content Desc 1") + #expect(content.displayName == "Content Name 1") + #expect(content.identifier == "Content ID 1") + #expect(content.organization == "Content Org 1") + #expect(content.type == "Content type 1") + #expect(content.uuid == "Content UUID 1") + #expect(content.version == 1) // then verify the services key - XCTAssertEqual(2, content.services.count) + #expect(content.services.count == 2) let allFiles = content.services["SystemPolicyAllFiles"] - XCTAssertEqual(1, allFiles?.count) + #expect(allFiles?.count == 1) allFiles?.forEach { policy in - XCTAssertEqual("policy id", policy.identifier) - XCTAssertEqual("policy id type", policy.identifierType) - XCTAssertEqual("policy code req", policy.codeRequirement) - XCTAssertEqual(true, policy.allowed) - XCTAssertNil(policy.authorization) - XCTAssertEqual("policy comment", policy.comment) - XCTAssertEqual("policy receiver id", policy.receiverIdentifier) - XCTAssertEqual("policy receiver id type", policy.receiverIdentifierType) - XCTAssertEqual("policy receiver code req", policy.receiverCodeRequirement) + #expect(policy.identifier == "policy id") + #expect(policy.identifierType == "policy id type") + #expect(policy.codeRequirement == "policy code req") + #expect(policy.allowed == true) + #expect(policy.authorization == nil) + #expect(policy.comment == "policy comment") + #expect(policy.receiverIdentifier == "policy receiver id") + #expect(policy.receiverIdentifierType == "policy receiver id type") + #expect(policy.receiverCodeRequirement == "policy receiver code req") } } } - func testSerializationOfProfileWhenBothAllowedAndAuthorizationUsed() throws { + @Test + func serializationOfProfileWhenBothAllowedAndAuthorizationUsed() throws { // when we export to xml and reimport it should still have the same attributes let plistData = try TCCProfileBuilder().buildProfile(allowed: false, authorization: .allow).xmlData() let profile = try TCCProfile.parse(from: plistData) // then verify the config profile top level - XCTAssertEqual("Configuration", profile.type) + #expect(profile.type == "Configuration") // then verify the payload content top level - XCTAssertEqual(1, profile.content.count) + #expect(profile.content.count == 1) profile.content.forEach { content in - XCTAssertEqual("Content UUID 1", content.uuid) - XCTAssertEqual(1, content.version) + #expect(content.uuid == "Content UUID 1") + #expect(content.version == 1) // then verify the services key - XCTAssertEqual(2, content.services.count) + #expect(content.services.count == 2) let allFiles = content.services["SystemPolicyAllFiles"] - XCTAssertEqual(1, allFiles?.count) + #expect(allFiles?.count == 1) allFiles?.forEach { policy in - XCTAssertEqual(false, policy.allowed) - XCTAssertEqual(policy.authorization, TCCPolicyAuthorizationValue.allow) + #expect(policy.allowed == false) + #expect(policy.authorization == .allow) } } } // unit tests for handling both Auth and allowed keys should fail? - func testSettingLegacyAllowValueNullifiesAuthorization() throws { - // given + @Test + func settingLegacyAllowValueNullifiesAuthorization() throws { var tccPolicy = TCCPolicy(identifier: "id", codeRequirement: "req", receiverIdentifier: "recId", receiverCodeRequirement: "recreq") tccPolicy.authorization = .allow @@ -155,12 +162,12 @@ class TCCProfileTests: XCTestCase { tccPolicy.allowed = true // then - XCTAssertNil(tccPolicy.authorization) - XCTAssertTrue(try XCTUnwrap(tccPolicy.allowed)) + #expect(tccPolicy.authorization == nil) + #expect(tccPolicy.allowed == true) } - func testSettingAuthorizationValueDoesNotNullifyAllowed() { - // given + @Test + func settingAuthorizationValueDoesNotNullifyAllowed() { var tccPolicy = TCCPolicy(identifier: "id", codeRequirement: "req", receiverIdentifier: "recId", receiverCodeRequirement: "recreq") tccPolicy.allowed = false @@ -168,29 +175,26 @@ class TCCProfileTests: XCTestCase { tccPolicy.authorization = .allowStandardUserToSetSystemService // then - XCTAssertEqual(tccPolicy.allowed, false, "we don't have to nil this out because we use authorization by default if present") - XCTAssertEqual(tccPolicy.authorization, TCCPolicyAuthorizationValue.allowStandardUserToSetSystemService) + #expect(tccPolicy.allowed == false, "we don't have to nil this out because we use authorization by default if present") + #expect(tccPolicy.authorization == .allowStandardUserToSetSystemService) } - func testJamfProAPIData() throws { - // given - build the test profile + @Test + func jamfProAPIData() throws { let tccProfile = TCCProfileBuilder().buildProfile(allowed: false, authorization: .allow) let expected = try loadTextFile(fileName: "TestTCCProfileForJamfProAPI").trimmingCharacters(in: .whitespacesAndNewlines) - // when - wrap in Jamf Pro API xml + // when let data = try tccProfile.jamfProAPIData(signingIdentity: nil, site: nil) // then let xmlString = String(data: data, encoding: .utf8) - XCTAssertEqual(xmlString, expected) + #expect(xmlString == expected) } private func loadTextFile(fileName: String) throws -> String { - let testBundle = Bundle(for: type(of: self)) - guard let resourceURL = testBundle.url(forResource: fileName, withExtension: "txt") else { - XCTFail("Resource file should exists") - return "" - } + let testBundle = Bundle(for: BundleLocator.self) + let resourceURL = try #require(testBundle.url(forResource: fileName, withExtension: "txt")) return try String(contentsOf: resourceURL) } } diff --git a/docs/plans/xctest-to-swift-testing-migration.md b/docs/plans/xctest-to-swift-testing-migration.md new file mode 100644 index 0000000..7ab4919 --- /dev/null +++ b/docs/plans/xctest-to-swift-testing-migration.md @@ -0,0 +1,84 @@ +# XCTest → Swift Testing Migration Plan + +## Problem +Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swift Testing. The migration should be incremental and phased, starting with one file to establish patterns and build context. + +## Approach +- Migrate one file at a time, review after each phase +- Order files from simplest → most complex +- Keep XCTest and Swift Testing coexisting (the frameworks run side-by-side) +- Update CLAUDE.md with learned patterns/context after the first phase +- Helpers (ModelBuilder, TCCProfileBuilder) don't need conversion — they're plain Swift + +## Key Conversion Patterns + +| XCTest | Swift Testing | +|--------|---------------| +| `class FooTests: XCTestCase` | `@Suite` (line above) `struct FooTests` | +| `import XCTest` | `import Testing` | +| `func testFoo()` | `@Test` (line above) `func foo()` | +| `XCTAssertEqual(a, b)` | `#expect(a == b)` | +| `XCTAssertTrue(x)` | `#expect(x)` | +| `XCTAssertFalse(x)` | `#expect(!x)` | +| `XCTAssertNil(x)` | `#expect(x == nil)` | +| `XCTAssertNotNil(x)` | `try #require(x)` (unwraps) | +| `XCTUnwrap(x)` | `try #require(x)` | +| `XCTFail("msg")` | `Issue.record("msg")` | +| `setUp()` | `init()` | + +## Notes +- `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor` is set — no explicit `@MainActor` needed +- `@testable import PPPC_Utility` stays the same +- Helpers (ModelBuilder, TCCProfileBuilder) are plain Swift — no changes needed +- `test` prefix on method names is dropped — `@Test` already marks them +- `@Test` and `@Suite` annotations go on the line above the declaration, not inline +- NetworkAuthManagerTests has async tests — Swift Testing supports `async throws` natively +- TCCProfileImporterTests uses callback-based patterns — may need `confirmation { }` macro + +## Progress + +- [x] **Phase 1: SemanticVersionTests** (3 tests) +- [x] **Phase 2: TokenTests** (5 tests) +- [x] **Phase 3: JamfProAPIClientTests + PPPCServicesManagerTests** (5 tests) +- [x] **Phase 4: NetworkAuthManagerTests** (7 tests) +- [x] **Phase 5: TCCProfileTests** (6 tests) +- [x] **Phase 6: TCCProfileImporterTests** (5 tests) +- [x] **Phase 7: ModelTests** (21 tests) +- [ ] **Phase 8: Fix stale storyboard outlet** — `addressBookStackView` connection in Main.storyboard references a property removed in Nov 2020 (renamed to `adminFilesStackView`). Pre-existing, not caused by migration. + +## Phases + +### Phase 1: SemanticVersionTests (3 tests) ✅ +- Simplest file: 3 test methods, no setUp/tearDown, no async, no mocks +- Only uses `XCTAssertTrue` and `XCTAssertFalse` +- Good candidate for parameterized tests (`@Test(arguments:)`) since each test runs multiple comparison assertions +- Establishes the basic conversion pattern for review + +### Phase 2: TokenTests (5 tests) ✅ +- Simple-medium: date checks and JSON decoding +- Uses `XCTAssertFalse`, `XCTAssertTrue`, `XCTAssertEqual`, `XCTAssertNotNil`, `XCTUnwrap` +- Introduces `try #require()` pattern (replacing XCTUnwrap) + +### Phase 3: JamfProAPIClientTests (1 test) + PPPCServicesManagerTests (4 tests) ✅ +- Both simple, batch together since JamfProAPIClientTests is only 1 test +- PPPCServicesManagerTests uses `XCTUnwrap` → `try #require()` + +### Phase 4: NetworkAuthManagerTests (7 tests) ✅ +- Async/await tests — Swift Testing handles these natively +- Has a MockNetworking class (stays as-is, it's not XCTest-specific) +- Error handling patterns with `do/catch` + `XCTFail` → `do/catch` + `Issue.record` + +### Phase 5: TCCProfileTests (6 tests) ✅ +- Serialization round-trip tests, uses TCCProfileBuilder helper +- Bundle resource loading (test bundle access pattern may need attention) + +### Phase 6: TCCProfileImporterTests (5 tests) ✅ +- Most complex conversion: callback-based async patterns +- May need Swift Testing's `confirmation { }` macro for callback verification +- Bundle resource loading for .mobileconfig test files + +### Phase 7: ModelTests (21 tests) ✅ +- Largest file (515 lines), saved for last +- Uses `setUp()` → convert to `init()` +- Heavy use of TCCProfileBuilder +- Comprehensive assertion coverage across all XCTAssert variants