From 5979592be8a8ec703722a891fcdfa447d5bafb40 Mon Sep 17 00:00:00 2001 From: Tony Eichelberger Date: Wed, 1 Apr 2026 15:42:16 -0500 Subject: [PATCH 1/8] started on a claude.md and converting a couple tests --- CLAUDE.md | 10 +++ .../ModelTests/SemanticVersionTests.swift | 86 ++++++------------- .../NetworkingTests/TokenTests.swift | 57 ++++++------ 3 files changed, 67 insertions(+), 86 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5c34915 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,10 @@ +# 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) 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/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) } } From 0da611bd2b69a9ba643d4ee54fa628e38a670978 Mon Sep 17 00:00:00 2001 From: Tony Eichelberger Date: Wed, 1 Apr 2026 15:50:14 -0500 Subject: [PATCH 2/8] added plan --- .../xctest-to-swift-testing-migration.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/plans/xctest-to-swift-testing-migration.md 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..3598fc0 --- /dev/null +++ b/docs/plans/xctest-to-swift-testing-migration.md @@ -0,0 +1,73 @@ +# 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 + +## Phases + +### Phase 1: SemanticVersionTests (3 tests) — FIRST, for review +- 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 From 79bb27e2b478d443b9cfaa37963ba43a061e13ff Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Thu, 2 Apr 2026 10:08:35 -0500 Subject: [PATCH 3/8] phase 3 done --- PPPC Utility.xcodeproj/project.pbxproj | 20 +++++--- .../ModelTests/PPPCServicesManagerTests.swift | 50 ++++++++++--------- .../JamfProAPIClientTests.swift | 13 ++--- .../xctest-to-swift-testing-migration.md | 16 ++++-- 4 files changed, 58 insertions(+), 41 deletions(-) diff --git a/PPPC Utility.xcodeproj/project.pbxproj b/PPPC Utility.xcodeproj/project.pbxproj index 5c9d327..78cc73c 100644 --- a/PPPC Utility.xcodeproj/project.pbxproj +++ b/PPPC Utility.xcodeproj/project.pbxproj @@ -551,9 +551,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)", @@ -575,9 +576,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)", @@ -715,9 +717,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 = ( @@ -738,9 +741,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/PPPCServicesManagerTests.swift b/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift index 3cd1315..144c1e1 100644 --- a/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift +++ b/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift @@ -26,68 +26,70 @@ // 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, + #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"]) + let service = try #require(services.allServices["Camera"]) // when - let actual = try XCTUnwrap(service.denyOnly) + let actual = try #require(service.denyOnly) // then - XCTAssertTrue(actual) + #expect(actual) } - func testScreenCaptureAllowsStandardUsers() throws { - // given + @Test + func screenCaptureAllowsStandardUsers() throws { let services = PPPCServicesManager() - let service = try XCTUnwrap(services.allServices["ScreenCapture"]) + let service = try #require(services.allServices["ScreenCapture"]) // when - let actual = try XCTUnwrap(service.allowStandardUsersMacOS11Plus) + let actual = try #require(service.allowStandardUsersMacOS11Plus) // then - XCTAssertTrue(actual) + #expect(actual) } } 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/docs/plans/xctest-to-swift-testing-migration.md b/docs/plans/xctest-to-swift-testing-migration.md index 3598fc0..4ba1bde 100644 --- a/docs/plans/xctest-to-swift-testing-migration.md +++ b/docs/plans/xctest-to-swift-testing-migration.md @@ -35,20 +35,30 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - 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) +- [ ] **Phase 4: NetworkAuthManagerTests** (7 tests) +- [ ] **Phase 5: TCCProfileTests** (6 tests) +- [ ] **Phase 6: TCCProfileImporterTests** (5 tests) +- [ ] **Phase 7: ModelTests** (21 tests) + ## Phases -### Phase 1: SemanticVersionTests (3 tests) — FIRST, for review +### 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) +### 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) +### Phase 3: JamfProAPIClientTests (1 test) + PPPCServicesManagerTests (4 tests) ✅ - Both simple, batch together since JamfProAPIClientTests is only 1 test - PPPCServicesManagerTests uses `XCTUnwrap` → `try #require()` From b3385d0df134ae6f2ad8c8cb2f7f01bb3d0f4192 Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Thu, 2 Apr 2026 10:33:33 -0500 Subject: [PATCH 4/8] phase 4 --- CLAUDE.md | 1 + .../NetworkAuthManagerTests.swift | 97 ++++++++----------- .../xctest-to-swift-testing-migration.md | 4 +- 3 files changed, 42 insertions(+), 60 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5c34915..23b8a0f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,3 +8,4 @@ - Place `@Test` and `@Suite` annotations on the line **above** the declaration, not inline - Use `// when` and `// then` comment blocks; skip `// given` (assumed from context) +- Capture a baseline of compiler warnings before each phase, then verify no new warnings after 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/docs/plans/xctest-to-swift-testing-migration.md b/docs/plans/xctest-to-swift-testing-migration.md index 4ba1bde..19880bc 100644 --- a/docs/plans/xctest-to-swift-testing-migration.md +++ b/docs/plans/xctest-to-swift-testing-migration.md @@ -40,7 +40,7 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - [x] **Phase 1: SemanticVersionTests** (3 tests) - [x] **Phase 2: TokenTests** (5 tests) - [x] **Phase 3: JamfProAPIClientTests + PPPCServicesManagerTests** (5 tests) -- [ ] **Phase 4: NetworkAuthManagerTests** (7 tests) +- [x] **Phase 4: NetworkAuthManagerTests** (7 tests) - [ ] **Phase 5: TCCProfileTests** (6 tests) - [ ] **Phase 6: TCCProfileImporterTests** (5 tests) - [ ] **Phase 7: ModelTests** (21 tests) @@ -62,7 +62,7 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - Both simple, batch together since JamfProAPIClientTests is only 1 test - PPPCServicesManagerTests uses `XCTUnwrap` → `try #require()` -### Phase 4: NetworkAuthManagerTests (7 tests) +### 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` From ddbf5ed9166c0f6d5aabe8c4f12e5d559c7edd6d Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Thu, 2 Apr 2026 11:16:38 -0500 Subject: [PATCH 5/8] phase 5 --- CLAUDE.md | 7 +- .../ModelTests/PPPCServicesManagerTests.swift | 10 +- .../TCCProfileTests.swift | 172 +++++++++--------- .../xctest-to-swift-testing-migration.md | 4 +- 4 files changed, 98 insertions(+), 95 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 23b8a0f..7209f3d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,4 +8,9 @@ - Place `@Test` and `@Suite` annotations on the line **above** the declaration, not inline - Use `// when` and `// then` comment blocks; skip `// given` (assumed from context) -- Capture a baseline of compiler warnings before each phase, then verify no new warnings after +- 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" + ``` diff --git a/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift b/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift index 144c1e1..6a69000 100644 --- a/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift +++ b/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift @@ -74,11 +74,8 @@ struct PPPCServicesManagerTests { let services = PPPCServicesManager() let service = try #require(services.allServices["Camera"]) - // when - let actual = try #require(service.denyOnly) - // then - #expect(actual) + #expect(service.denyOnly == true) } @Test @@ -86,10 +83,7 @@ struct PPPCServicesManagerTests { let services = PPPCServicesManager() let service = try #require(services.allServices["ScreenCapture"]) - // when - let actual = try #require(service.allowStandardUsersMacOS11Plus) - // then - #expect(actual) + #expect(service.allowStandardUsersMacOS11Plus == true) } } 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 index 19880bc..173e199 100644 --- a/docs/plans/xctest-to-swift-testing-migration.md +++ b/docs/plans/xctest-to-swift-testing-migration.md @@ -41,7 +41,7 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - [x] **Phase 2: TokenTests** (5 tests) - [x] **Phase 3: JamfProAPIClientTests + PPPCServicesManagerTests** (5 tests) - [x] **Phase 4: NetworkAuthManagerTests** (7 tests) -- [ ] **Phase 5: TCCProfileTests** (6 tests) +- [x] **Phase 5: TCCProfileTests** (6 tests) - [ ] **Phase 6: TCCProfileImporterTests** (5 tests) - [ ] **Phase 7: ModelTests** (21 tests) @@ -67,7 +67,7 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - 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) +### Phase 5: TCCProfileTests (6 tests) ✅ - Serialization round-trip tests, uses TCCProfileBuilder helper - Bundle resource loading (test bundle access pattern may need attention) From 46115eaf91a035d4a7cf1fc9bf4a6f76074f958c Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Thu, 2 Apr 2026 13:39:42 -0500 Subject: [PATCH 6/8] phase 6 --- .../TCCProfileImporterTests.swift | 73 +++++++++---------- .../xctest-to-swift-testing-migration.md | 5 +- 2 files changed, 38 insertions(+), 40 deletions(-) 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/docs/plans/xctest-to-swift-testing-migration.md b/docs/plans/xctest-to-swift-testing-migration.md index 173e199..54b0656 100644 --- a/docs/plans/xctest-to-swift-testing-migration.md +++ b/docs/plans/xctest-to-swift-testing-migration.md @@ -42,8 +42,9 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - [x] **Phase 3: JamfProAPIClientTests + PPPCServicesManagerTests** (5 tests) - [x] **Phase 4: NetworkAuthManagerTests** (7 tests) - [x] **Phase 5: TCCProfileTests** (6 tests) -- [ ] **Phase 6: TCCProfileImporterTests** (5 tests) +- [x] **Phase 6: TCCProfileImporterTests** (5 tests) - [ ] **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 @@ -71,7 +72,7 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - Serialization round-trip tests, uses TCCProfileBuilder helper - Bundle resource loading (test bundle access pattern may need attention) -### Phase 6: TCCProfileImporterTests (5 tests) +### 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 From 7de879750ecb1e938849e7e767dcbf7738aaad1b Mon Sep 17 00:00:00 2001 From: Mike Anderson Date: Thu, 2 Apr 2026 14:02:33 -0500 Subject: [PATCH 7/8] phase 7 --- CLAUDE.md | 2 + PPPC UtilityTests/ModelTests/ModelTests.swift | 383 ++++++++---------- .../xctest-to-swift-testing-migration.md | 4 +- 3 files changed, 183 insertions(+), 206 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7209f3d..d3e46b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,3 +14,5 @@ ``` 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 UtilityTests/ModelTests/ModelTests.swift b/PPPC UtilityTests/ModelTests/ModelTests.swift index 38f3b5e..8973cc6 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() { model.usingLegacyAllowKey = false let exe1 = Executable(identifier: "one", codeRequirement: "oneReq") let exe2 = Executable(identifier: "two", codeRequirement: "twoReq") @@ -143,50 +140,50 @@ 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) } } - func testExportProfileWithAppleEventsAndLegacyAllowed() { - // given + @Test + func exportProfileWithAppleEventsAndLegacyAllowed() { let exe1 = Executable(identifier: "one", codeRequirement: "oneReq") let exe2 = Executable(identifier: "two", codeRequirement: "twoReq") exe1.appleEvents = [AppleEventRule(source: exe1, destination: exe2, value: true)] @@ -198,137 +195,137 @@ 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 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 appleEventsPolicy = content.services["AppleEvents"]?.first - XCTAssertNotNil(appleEventsPolicy) - 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?.allowed == true) - XCTAssertNil(appleEventsPolicy?.authorization) + #expect(appleEventsPolicy != nil) + #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?.allowed == true) + #expect(appleEventsPolicy?.authorization == nil) let allFilesPolicy = content.services["SystemPolicyAllFiles"]?.first - XCTAssertNotNil(allFilesPolicy) - XCTAssertEqual("two", allFilesPolicy?.identifier) - XCTAssertEqual("twoReq", allFilesPolicy?.codeRequirement) - XCTAssertEqual("bundleID", allFilesPolicy?.identifierType) - XCTAssertNil(allFilesPolicy?.receiverIdentifier) - XCTAssertNil(allFilesPolicy?.receiverCodeRequirement) - XCTAssertNil(allFilesPolicy?.receiverIdentifierType) - XCTAssertTrue(allFilesPolicy?.allowed == true) - XCTAssertNil(allFilesPolicy?.authorization) + #expect(allFilesPolicy != nil) + #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?.allowed == true) + #expect(allFilesPolicy?.authorization == nil) } } // 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 profileToString - func testPolicyWhenUsingAllowAndAuthorizationKey() { - // given + @Test + func policyWhenUsingAllowAndAuthorizationKey() { model.usingLegacyAllowKey = false let app = Executable(identifier: "id", codeRequirement: "req") @@ -336,12 +333,12 @@ class ModelTests: XCTestCase { let policy = model.policyFromString(executable: app, value: "Allow") // then - XCTAssertEqual(policy?.authorization, TCCPolicyAuthorizationValue.allow) - XCTAssertNil(policy?.allowed) + #expect(policy?.authorization == .allow) + #expect(policy?.allowed == nil) } - func testPolicyWhenUsingDeny() { - // given + @Test + func policyWhenUsingDeny() { model.usingLegacyAllowKey = false let app = Executable(identifier: "id", codeRequirement: "req") @@ -349,12 +346,12 @@ class ModelTests: XCTestCase { let policy = model.policyFromString(executable: app, value: "Deny") // then - XCTAssertEqual(policy?.authorization, TCCPolicyAuthorizationValue.deny) - XCTAssertNil(policy?.allowed) + #expect(policy?.authorization == .deny) + #expect(policy?.allowed == nil) } - func testPolicyWhenUsingAllowForStandardUsers() { - // given + @Test + func policyWhenUsingAllowForStandardUsers() { model.usingLegacyAllowKey = false let app = Executable(identifier: "id", codeRequirement: "req") @@ -362,23 +359,23 @@ class ModelTests: XCTestCase { let policy = model.policyFromString(executable: app, value: "Let Standard Users Approve") // then - XCTAssertEqual(policy?.authorization, TCCPolicyAuthorizationValue.allowStandardUserToSetSystemService) - XCTAssertNil(policy?.allowed) + #expect(policy?.authorization == .allowStandardUserToSetSystemService) + #expect(policy?.allowed == nil) } - 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") } - func testPolicyWhenUsingLegacyDeny() { - // given + @Test + func policyWhenUsingLegacyDeny() { let app = Executable(identifier: "id", codeRequirement: "req") model.usingLegacyAllowKey = true @@ -386,12 +383,12 @@ class ModelTests: XCTestCase { let policy = model.policyFromString(executable: app, value: "Deny") // then - XCTAssertNil(policy?.authorization, "should not set authorization when in legacy mode") - XCTAssertEqual(policy?.allowed, false) + #expect(policy?.authorization == nil, "should not set authorization when in legacy mode") + #expect(policy?.allowed == false) } - func testPolicyWhenUsingLegacyAllow() { - // given + @Test + func policyWhenUsingLegacyAllow() { let app = Executable(identifier: "id", codeRequirement: "req") model.usingLegacyAllowKey = true @@ -399,13 +396,13 @@ class ModelTests: XCTestCase { let policy = model.policyFromString(executable: app, value: "Allow") // then - XCTAssertNil(policy?.authorization, "should not set authorization when in legacy mode") - XCTAssertEqual(policy?.allowed, true) + #expect(policy?.authorization == nil, "should not set authorization when in legacy mode") + #expect(policy?.allowed == true) } // test for the unrecognized strings for both legacy and normal - func testPolicyWhenUsingLegacyAllowButNonLegacyValueUsed() { - // given + @Test + func policyWhenUsingLegacyAllowButNonLegacyValueUsed() { let app = Executable(identifier: "id", codeRequirement: "req") model.usingLegacyAllowKey = true @@ -413,48 +410,26 @@ class ModelTests: XCTestCase { let policy = model.policyFromString(executable: app, value: "Let Standard Users Approve") // then - XCTAssertNil(policy, "should have errored out because of an invalid value") + #expect(policy == nil, "should have errored out because of an invalid value") } // MARK: - tests for requiresAuthorizationKey - func testWhenServiceIsUsingAllowStandarUsersToApprove() { - // given - let profile = TCCProfileBuilder().buildProfile(authorization: .allowStandardUserToSetSystemService) - - // when + @Test(arguments: [ + (TCCPolicyAuthorizationValue.allowStandardUserToSetSystemService, true), + (TCCPolicyAuthorizationValue.allow, false), + (TCCPolicyAuthorizationValue.deny, false), + ]) + func requiresAuthorizationKey(authorization: TCCPolicyAuthorizationValue, expected: Bool) { + let profile = TCCProfileBuilder().buildProfile(authorization: authorization) model.importProfile(tccProfile: profile) - - // then - XCTAssertTrue(model.requiresAuthorizationKey()) - } - - func testWhenServiceIsUsingOnlyAllowKey() { - // given - let profile = TCCProfileBuilder().buildProfile(authorization: .allow) - - // when - model.importProfile(tccProfile: profile) - - // then - XCTAssertFalse(model.requiresAuthorizationKey()) - } - - func testWhenServiceIsUsingOnlyDenyKey() { - // given - let profile = TCCProfileBuilder().buildProfile(authorization: .deny) - - // when - model.importProfile(tccProfile: profile) - - // then - XCTAssertFalse(model.requiresAuthorizationKey()) + #expect(model.requiresAuthorizationKey() == expected) } // MARK: - tests for changeToUseLegacyAllowKey - func testChangingFromAuthorizationKeyToLegacyAllowKey() { - // given + @Test + func changingFromAuthorizationKeyToLegacyAllowKey() { let allowStandard = TCCProfileDisplayValue.allowStandardUsersToApprove.rawValue let exeSettings = ["AddressBook": "Allow", "ListenEvent": allowStandard, "ScreenCapture": allowStandard] let model = ModelBuilder().addExecutable(settings: exeSettings).build() @@ -464,17 +439,17 @@ class ModelTests: XCTestCase { model.changeToUseLegacyAllowKey() // then - XCTAssertEqual(1, model.selectedExecutables.count, "should have only one exe") + #expect(model.selectedExecutables.count == 1, "should have only one exe") let policy = model.selectedExecutables.first?.policy - XCTAssertEqual("Allow", policy?.AddressBook) - XCTAssertEqual("-", policy?.Camera) - XCTAssertEqual("-", policy?.ListenEvent) - XCTAssertEqual("-", policy?.ScreenCapture) - XCTAssertTrue(model.usingLegacyAllowKey) + #expect(policy?.AddressBook == "Allow") + #expect(policy?.Camera == "-") + #expect(policy?.ListenEvent == "-") + #expect(policy?.ScreenCapture == "-") + #expect(model.usingLegacyAllowKey) } - func testChangingFromAuthorizationKeyToLegacyAllowKeyWithMoreComplexVaues() { - // given + @Test + func changingFromAuthorizationKeyToLegacyAllowKeyWithMoreComplexVaues() { let allowStandard = TCCProfileDisplayValue.allowStandardUsersToApprove.rawValue let p1Settings = [ "SystemPolicyAllFiles": "Allow", @@ -489,26 +464,26 @@ class ModelTests: XCTestCase { "Calendar": "Allow" ] let builder = ModelBuilder().addExecutable(settings: p1Settings) - model = builder.addExecutable(settings: p2Settings).build() + let model = builder.addExecutable(settings: p2Settings).build() model.usingLegacyAllowKey = false // when model.changeToUseLegacyAllowKey() // then - XCTAssertEqual(2, model.selectedExecutables.count, "should have only one exe") + #expect(model.selectedExecutables.count == 2, "should have only one exe") let policy1 = model.selectedExecutables[0].policy - XCTAssertEqual("Allow", policy1.SystemPolicyAllFiles) - XCTAssertEqual("-", policy1.ListenEvent) - XCTAssertEqual("Deny", policy1.ScreenCapture) - XCTAssertEqual("Deny", policy1.Camera) + #expect(policy1.SystemPolicyAllFiles == "Allow") + #expect(policy1.ListenEvent == "-") + #expect(policy1.ScreenCapture == "Deny") + #expect(policy1.Camera == "Deny") let policy2 = model.selectedExecutables[1].policy - XCTAssertEqual("Deny", policy2.SystemPolicyAllFiles) - XCTAssertEqual("-", policy2.ListenEvent) - XCTAssertEqual("-", policy2.ScreenCapture) - XCTAssertEqual("Allow", policy2.Calendar) - XCTAssertTrue(model.usingLegacyAllowKey) + #expect(policy2.SystemPolicyAllFiles == "Deny") + #expect(policy2.ListenEvent == "-") + #expect(policy2.ScreenCapture == "-") + #expect(policy2.Calendar == "Allow") + #expect(model.usingLegacyAllowKey) } } diff --git a/docs/plans/xctest-to-swift-testing-migration.md b/docs/plans/xctest-to-swift-testing-migration.md index 54b0656..7ab4919 100644 --- a/docs/plans/xctest-to-swift-testing-migration.md +++ b/docs/plans/xctest-to-swift-testing-migration.md @@ -43,7 +43,7 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - [x] **Phase 4: NetworkAuthManagerTests** (7 tests) - [x] **Phase 5: TCCProfileTests** (6 tests) - [x] **Phase 6: TCCProfileImporterTests** (5 tests) -- [ ] **Phase 7: ModelTests** (21 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 @@ -77,7 +77,7 @@ Convert 52 test methods across 8 test files (plus 2 helpers) from XCTest to Swif - May need Swift Testing's `confirmation { }` macro for callback verification - Bundle resource loading for .mobileconfig test files -### Phase 7: ModelTests (21 tests) +### Phase 7: ModelTests (21 tests) ✅ - Largest file (515 lines), saved for last - Uses `setUp()` → convert to `init()` - Heavy use of TCCProfileBuilder From fef7c5ae8434280f4104766962406b77011099dd Mon Sep 17 00:00:00 2001 From: Tony Eichelberger Date: Thu, 2 Apr 2026 15:34:38 -0500 Subject: [PATCH 8/8] swift format --- PPPC UtilityTests/ModelTests/ModelTests.swift | 3 --- PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/PPPC UtilityTests/ModelTests/ModelTests.swift b/PPPC UtilityTests/ModelTests/ModelTests.swift index 676a7bb..8e838cb 100644 --- a/PPPC UtilityTests/ModelTests/ModelTests.swift +++ b/PPPC UtilityTests/ModelTests/ModelTests.swift @@ -181,7 +181,6 @@ struct ModelTests { } } - // MARK: - tests for importProfile @Test @@ -314,6 +313,4 @@ struct ModelTests { #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 a38a25e..ca8bf7e 100644 --- a/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift +++ b/PPPC UtilityTests/ModelTests/PPPCServicesManagerTests.swift @@ -52,8 +52,8 @@ struct PPPCServicesManagerTests { // then #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\"]" + 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\"]" ) }