diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d5d148e..f2aa4f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ permissions: jobs: test: - runs-on: macos-latest + runs-on: macos-26 steps: - uses: actions/checkout@v4 diff --git a/CLAUDE.md b/CLAUDE.md index d3e46b2..3606719 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ - `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 +## Swift Testing Conventions - Place `@Test` and `@Suite` annotations on the line **above** the declaration, not inline - Use `// when` and `// then` comment blocks; skip `// given` (assumed from context) diff --git a/PPPC Utility.xcodeproj/project.pbxproj b/PPPC Utility.xcodeproj/project.pbxproj index 11b3e67..80803ba 100644 --- a/PPPC Utility.xcodeproj/project.pbxproj +++ b/PPPC Utility.xcodeproj/project.pbxproj @@ -560,8 +560,11 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.jamf.PPPC-UtilityTests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PPPC Utility.app/Contents/MacOS/PPPC Utility"; }; name = Debug; @@ -585,7 +588,10 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.jamf.PPPC-UtilityTests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PPPC Utility.app/Contents/MacOS/PPPC Utility"; }; name = Release; @@ -727,7 +733,10 @@ PRODUCT_BUNDLE_IDENTIFIER = com.jamf.opensource.pppcutility; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -751,7 +760,10 @@ PRODUCT_BUNDLE_IDENTIFIER = com.jamf.opensource.pppcutility; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; }; name = Release; }; diff --git a/PPPC UtilityTests/ModelTests/ModelTests.swift b/PPPC UtilityTests/ModelTests/ModelTests.swift index 8e838cb..c2ad6f1 100644 --- a/PPPC UtilityTests/ModelTests/ModelTests.swift +++ b/PPPC UtilityTests/ModelTests/ModelTests.swift @@ -38,12 +38,12 @@ struct ModelTests { // MARK: - tests for getExecutableFrom* @Test - func getExecutableBasedOnIdentifierAndCodeRequirement_BundleIdentifierType() { + func getExecutableBasedOnIdentifierAndCodeRequirement_BundleIdentifierType() async { let identifier = "com.example.App" let codeRequirement = "testCodeRequirement" // when - let executable = model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) + let executable = await model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) // then #expect(executable.displayName == "App") @@ -52,12 +52,12 @@ struct ModelTests { } @Test - func getExecutableBasedOnIdentifierAndCodeRequirement_PathIdentifierType() { + func getExecutableBasedOnIdentifierAndCodeRequirement_PathIdentifierType() async { let identifier = "/myGreatPath/Awesome/Binary" let codeRequirement = "testCodeRequirement" // when - let executable = model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) + let executable = await model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) // then #expect(executable.displayName == "Binary") @@ -66,12 +66,12 @@ struct ModelTests { } @Test - func getExecutableFromComputerBasedOnIdentifier() { + func getExecutableFromComputerBasedOnIdentifier() async { let identifier = "com.apple.Safari" let codeRequirement = "randomReq" // when - let executable = model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) + let executable = await model.getExecutableFrom(identifier: identifier, codeRequirement: codeRequirement) // then #expect(executable.displayName == "Safari") @@ -80,10 +80,10 @@ struct ModelTests { } @Test - func getExecutableFromSelectedExecutables() { + func getExecutableFromSelectedExecutables() async { let expectedIdentifier = "com.something.1" - let executable = model.getExecutableFrom(identifier: expectedIdentifier, codeRequirement: "testReq") - let executableSecond = model.getExecutableFrom(identifier: "com.something.2", codeRequirement: "testReq2") + let executable = await model.getExecutableFrom(identifier: expectedIdentifier, codeRequirement: "testReq") + let executableSecond = await model.getExecutableFrom(identifier: "com.something.2", codeRequirement: "testReq2") model.selectedExecutables = [executable, executableSecond] // when @@ -97,11 +97,11 @@ struct ModelTests { } @Test - func getExecutableFromSelectedExecutables_Path() { + func getExecutableFromSelectedExecutables_Path() async { let expectedIdentifier = "/path/something/Special" - let executableOneMore = model.getExecutableFrom(identifier: "/path/something/Special1", codeRequirement: "testReq") - let executable = model.getExecutableFrom(identifier: expectedIdentifier, codeRequirement: "testReq") - let executableSecond = model.getExecutableFrom(identifier: "com.something.2", codeRequirement: "testReq2") + let executableOneMore = await model.getExecutableFrom(identifier: "/path/something/Special1", codeRequirement: "testReq") + let executable = await model.getExecutableFrom(identifier: expectedIdentifier, codeRequirement: "testReq") + let executableSecond = await model.getExecutableFrom(identifier: "com.something.2", codeRequirement: "testReq2") model.selectedExecutables = [executableOneMore, executable, executableSecond] // when @@ -184,11 +184,11 @@ struct ModelTests { // MARK: - tests for importProfile @Test - func importProfileUsingAuthorizationKeyAllow() { + func importProfileUsingAuthorizationKeyAllow() async { let profile = TCCProfileBuilder().buildProfile(authorization: .allow) // when - model.importProfile(tccProfile: profile) + await model.importProfile(tccProfile: profile) // then #expect(model.selectedExecutables.count == 1) @@ -196,11 +196,11 @@ struct ModelTests { } @Test - func importProfileUsingAuthorizationKeyDeny() { + func importProfileUsingAuthorizationKeyDeny() async { let profile = TCCProfileBuilder().buildProfile(authorization: .deny) // when - model.importProfile(tccProfile: profile) + await model.importProfile(tccProfile: profile) // then #expect(model.selectedExecutables.count == 1) @@ -208,11 +208,11 @@ struct ModelTests { } @Test - func importProfileUsingAuthorizationKeyAllowStandardUsers() { + func importProfileUsingAuthorizationKeyAllowStandardUsers() async { let profile = TCCProfileBuilder().buildProfile(authorization: .allowStandardUserToSetSystemService) // when - model.importProfile(tccProfile: profile) + await model.importProfile(tccProfile: profile) // then #expect(model.selectedExecutables.count == 1) @@ -220,11 +220,11 @@ struct ModelTests { } @Test - func importProfileUsingLegacyAllowKeyTrue() { + func importProfileUsingLegacyAllowKeyTrue() async { let profile = TCCProfileBuilder().buildProfile(allowed: true) // when - model.importProfile(tccProfile: profile) + await model.importProfile(tccProfile: profile) // then #expect(model.selectedExecutables.count == 1) @@ -232,11 +232,11 @@ struct ModelTests { } @Test - func importProfileUsingLegacyAllowKeyFalse() { + func importProfileUsingLegacyAllowKeyFalse() async { let profile = TCCProfileBuilder().buildProfile(allowed: false) // when - model.importProfile(tccProfile: profile) + await model.importProfile(tccProfile: profile) // then #expect(model.selectedExecutables.count == 1) @@ -244,11 +244,11 @@ struct ModelTests { } @Test - func importProfileUsingAuthorizationKeyThatIsInvalid() { + func importProfileUsingAuthorizationKeyThatIsInvalid() async { let profile = TCCProfileBuilder().buildProfile(authorization: "invalidkey") // when - model.importProfile(tccProfile: profile) + await model.importProfile(tccProfile: profile) // then #expect(model.selectedExecutables.count == 1) @@ -256,18 +256,18 @@ struct ModelTests { } @Test - func importProfileUsingAuthorizationKeyTranslatesToAppleEvents() { + func importProfileUsingAuthorizationKeyTranslatesToAppleEvents() async { let profile = TCCProfileBuilder().buildProfile(authorization: "deny") // when - model.importProfile(tccProfile: profile) + await model.importProfile(tccProfile: profile) // then #expect(model.selectedExecutables.count == 1) #expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny") } - // MARK: - tests for profileToString + // MARK: - tests for policyFromString @Test func policyWhenUsingAllow() { diff --git a/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift b/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift index 4eba732..42b36e2 100644 --- a/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift +++ b/PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift @@ -111,33 +111,33 @@ struct NetworkAuthManagerTests { } @Test - func basicAuthString() throws { + func basicAuthString() async throws { let authManager = NetworkAuthManager(username: "test", password: "none") // when - let actual = try authManager.basicAuthString() + let actual = try await authManager.basicAuthString() // then #expect(actual == "dGVzdDpub25l") } @Test - func basicAuthStringEmptyUsername() { + func basicAuthStringEmptyUsername() async throws { let authManager = NetworkAuthManager(username: "", password: "none") // when/then - #expect(throws: AuthError.invalidUsernamePassword) { - try authManager.basicAuthString() + await #expect(throws: AuthError.invalidUsernamePassword) { + try await authManager.basicAuthString() } } @Test - func basicAuthStringEmptyPassword() { + func basicAuthStringEmptyPassword() async throws { let authManager = NetworkAuthManager(username: "mine", password: "") // when/then - #expect(throws: AuthError.invalidUsernamePassword) { - try authManager.basicAuthString() + await #expect(throws: AuthError.invalidUsernamePassword) { + try await authManager.basicAuthString() } } } diff --git a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift index 4f1af27..042d5ac 100644 --- a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift +++ b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileImporterTests.swift @@ -40,15 +40,13 @@ struct TCCProfileImporterTests { let tccProfileImporter = TCCProfileImporter() let resourceURL = try getResourceProfile(fileName: "TestTCCProfileSigned-Broken") - tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in - switch tccProfileResult { - case .success: - Issue.record("Malformed profile should not succeed") - case .failure(let tccProfileError): - if case TCCProfileImportError.invalidProfileFile = tccProfileError { - } else { - Issue.record("Expected invalidProfileFile error, got \(tccProfileError)") - } + do { + _ = try tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) + Issue.record("Malformed profile should not succeed") + } catch { + if case TCCProfileImportError.invalidProfileFile = error { + } else { + Issue.record("Expected invalidProfileFile error, got \(error)") } } } @@ -59,13 +57,11 @@ struct TCCProfileImporterTests { let resourceURL = try getResourceProfile(fileName: "TestTCCUnsignedProfile-Empty") let expectedTCCProfileError = TCCProfileImportError.invalidProfileFile(description: "PayloadContent") - tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in - switch tccProfileResult { - case .success: - Issue.record("Empty Content, it shouldn't be success") - case .failure(let tccProfileError): - #expect(tccProfileError.localizedDescription == expectedTCCProfileError.localizedDescription) - } + do { + _ = try tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) + Issue.record("Empty Content, it shouldn't be success") + } catch { + #expect(error.localizedDescription == expectedTCCProfileError.localizedDescription) } } @@ -74,15 +70,9 @@ struct TCCProfileImporterTests { let tccProfileImporter = TCCProfileImporter() let resourceURL = try getResourceProfile(fileName: "TestTCCUnsignedProfile") - tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in - switch tccProfileResult { - case .success(let tccProfile): - #expect(!tccProfile.content.isEmpty) - #expect(!tccProfile.content[0].services.isEmpty) - case .failure(let tccProfileError): - Issue.record("Unable to read tccProfile \(tccProfileError.localizedDescription)") - } - } + let tccProfile = try tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) + #expect(!tccProfile.content.isEmpty) + #expect(!tccProfile.content[0].services.isEmpty) } @Test @@ -90,15 +80,9 @@ struct TCCProfileImporterTests { let tccProfileImporter = TCCProfileImporter() let resourceURL = try getResourceProfile(fileName: "TestTCCUnsignedProfile-allLower") - tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) { tccProfileResult in - switch tccProfileResult { - case .success(let tccProfile): - #expect(!tccProfile.content.isEmpty) - #expect(!tccProfile.content[0].services.isEmpty) - case .failure(let tccProfileError): - Issue.record("Unable to read tccProfile \(tccProfileError.localizedDescription)") - } - } + let tccProfile = try tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) + #expect(!tccProfile.content.isEmpty) + #expect(!tccProfile.content[0].services.isEmpty) } @Test @@ -107,13 +91,11 @@ struct TCCProfileImporterTests { 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: - Issue.record("Broken Unsigned Profile, it shouldn't be success") - case .failure(let tccProfileError): - #expect(tccProfileError.localizedDescription == expectedTCCProfileError.localizedDescription) - } + do { + _ = try tccProfileImporter.decodeTCCProfile(fileUrl: resourceURL) + Issue.record("Broken Unsigned Profile, it shouldn't be success") + } catch { + #expect(error.localizedDescription == expectedTCCProfileError.localizedDescription) } } diff --git a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift index 761e274..45e45f2 100644 --- a/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift +++ b/PPPC UtilityTests/TCCProfileImporterTests/TCCProfileTests.swift @@ -154,7 +154,7 @@ struct TCCProfileTests { // unit tests for handling both Auth and allowed keys should fail? @Test - func settingLegacyAllowValueNullifiesAuthorization() throws { + func settingLegacyAllowValueNullifiesAuthorization() { var tccPolicy = TCCPolicy(identifier: "id", codeRequirement: "req", receiverIdentifier: "recId", receiverCodeRequirement: "recreq") tccPolicy.authorization = .allow @@ -180,12 +180,12 @@ struct TCCProfileTests { } @Test - func jamfProAPIData() throws { + func jamfProAPIData() async throws { let tccProfile = TCCProfileBuilder().buildProfile(allowed: false, authorization: .allow) let expected = try loadTextFile(fileName: "TestTCCProfileForJamfProAPI").trimmingCharacters(in: .whitespacesAndNewlines) // when - let data = try tccProfile.jamfProAPIData(signingIdentity: nil, site: nil) + let data = try await tccProfile.jamfProAPIData(signingIdentity: nil, site: nil) // then let xmlString = String(data: data, encoding: .utf8) diff --git a/Resources/Base.lproj/Main.storyboard b/Resources/Base.lproj/Main.storyboard index bd1a5b6..5856c46 100644 --- a/Resources/Base.lproj/Main.storyboard +++ b/Resources/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -706,7 +706,7 @@ - + @@ -720,9 +720,9 @@ - + - + @@ -733,7 +733,7 @@ - + @@ -744,8 +744,8 @@ - - + + identifier "com.jamfsoftware.jamf" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "483DWKW443" @@ -791,19 +791,19 @@ - + - + - + - + - - + + @@ -811,10 +811,10 @@ - + - + @@ -829,7 +829,7 @@ - + @@ -2275,7 +2275,7 @@ - + @@ -2327,7 +2327,7 @@ - - + + @@ -2398,7 +2398,7 @@