Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8f22710
AI generated removal of the big sur compatibility logic. Needs vetted…
watkyn Jan 29, 2026
bcb3a12
big-sur story/JPCFM-5531 Add minor changes to update minimum supporte…
jjpritzl Mar 10, 2026
5efa226
big-sur story/JPCFM-5531 Remove swiftlint disable call due to reduced…
jjpritzl Mar 10, 2026
93ed2be
big-sur story/JPCFM-5531 Add Copilot suggestion for generating test r…
jjpritzl Mar 13, 2026
dc8df63
big-sur story/JPCFM-5531 Add Copilot refactor to loadExecutable function
jjpritzl Mar 13, 2026
343399a
big-sur story/JPCFM-5531 Remove code coverage addition
jjpritzl Mar 24, 2026
1e0027e
added detailed plan for getting to swift 6 in stages
watkyn Mar 26, 2026
0a14250
Initial plan
Copilot Mar 26, 2026
6741832
Stage 2: Convert NetworkAuthManager from actor to class
Copilot Mar 26, 2026
9256bd0
Stage 3a: Remove DispatchQueue.main.async wrappers
Copilot Mar 26, 2026
7b77113
Stage 3b: Convert UploadManager to async throws
Copilot Mar 26, 2026
a64ed56
Stage 3c: Convert Model.loadExecutable to direct return
Copilot Mar 26, 2026
542a287
Stage 3d: Convert TCCProfileImporter to direct return
Copilot Mar 26, 2026
f38e4fc
Stage 4: Add @concurrent for background I/O
Copilot Mar 26, 2026
23583a4
Stage 5: Enable Swift 6 language mode
Copilot Mar 26, 2026
c24c1e9
create-separate story/JPCFM-5564 Fix failing build and tests
jjpritzl Mar 31, 2026
24d36a5
create-separate story/JPCFM-5564 Fix code signing mistake
jjpritzl Mar 31, 2026
cf78ace
create-separate story/JPCFM-5564 Fix bad syntax in build settings
jjpritzl Mar 31, 2026
60390d5
create-separate story/JPCFM-5564 Fix bad syntax in project settings f…
jjpritzl Mar 31, 2026
2f85f1b
create-separate story/JPCFM-5564 Try to fix failing tests on GH PR
jjpritzl Apr 1, 2026
2ed25d9
create-separate story/JPCFM-5564 Try to fix unit test check issue
jjpritzl Apr 1, 2026
83f107d
create-separate story/JPCFM-5564 Make actual change with the plan to …
jjpritzl Apr 1, 2026
b28109d
create-separate story/JPCFM-5564 merge main and fix merge conflicts
jjpritzl Apr 1, 2026
b165a85
Merge branch 'master' of github.com:jamf/PPPC-Utility into copilot/cr…
watkyn Apr 2, 2026
b3db45b
removed some uneeded concurrency annotations
watkyn Apr 2, 2026
113fcad
removed a couple more spots
watkyn Apr 2, 2026
80f38ae
Update Source/SwiftUI/UploadInfoView.swift
watkyn Apr 6, 2026
4ffc19b
code review changes
watkyn Apr 6, 2026
25e2629
put the apple events selection back onto the main queue
watkyn Apr 6, 2026
798491d
disabled the drop down until it is populated.
watkyn Apr 6, 2026
cb0763a
fixed the sizing issue of the save window
watkyn Apr 6, 2026
858bfe7
added width to the save prompt
watkyn Apr 6, 2026
fdad5c6
changed sequence of model on save
watkyn Apr 6, 2026
a3d3899
reverted main story board
watkyn Apr 6, 2026
83443d5
added timing check for apple events loading
watkyn Apr 6, 2026
5297402
reverted back to sync on the drag drop executables
watkyn Apr 6, 2026
9afcf29
try macos 26
watkyn Apr 6, 2026
ae6f8ce
fixed the upload crashes
watkyn Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions:

jobs:
test:
runs-on: macos-latest
runs-on: macos-26
steps:
- uses: actions/checkout@v4

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 16 additions & 4 deletions PPPC Utility.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
};
Expand All @@ -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;
};
Expand Down
56 changes: 28 additions & 28 deletions PPPC UtilityTests/ModelTests/ModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -184,90 +184,90 @@ 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)
#expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Allow")
}

@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)
#expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny")
}

@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)
#expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Let Standard Users Approve")
}

@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)
#expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Allow")
}

@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)
#expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny")
}

@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)
#expect(model.selectedExecutables.first?.policy.SystemPolicyAllFiles == "Deny")
}

@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() {
Expand Down
16 changes: 8 additions & 8 deletions PPPC UtilityTests/NetworkingTests/NetworkAuthManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
}
}
Expand All @@ -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)
}
}

Expand All @@ -74,31 +70,19 @@ 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
func correctUnsignedProfileContentDataAllLowercase() throws {
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
Expand All @@ -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)
}
}

Expand Down
Loading
Loading