diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 82088d5..67b0ccb 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -1361,6 +1361,11 @@ isa = PBXGroup; children = ( C4AF9F76275447F100D44ED0 /* ValetConfigurationTest.swift */, + C4F780AD25D80B37000DBC97 /* PhpExtensionTest.swift */, + C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */, + C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */, + C4551656297AED18009B8466 /* ValetRcTest.swift */, + C456A0D02AA6175D0080144F /* Config */, ); path = Parsers; sourceTree = ""; @@ -2188,11 +2193,6 @@ C4C1019727C65A11001FACC2 /* Parsers */ = { isa = PBXGroup; children = ( - C456A0D02AA6175D0080144F /* Config */, - C4F780AD25D80B37000DBC97 /* PhpExtensionTest.swift */, - C43A8A2325D9D20D00591B77 /* HomebrewPackageTest.swift */, - C42CFB1927DFE8BD00862737 /* NginxConfigurationTest.swift */, - C4551656297AED18009B8466 /* ValetRcTest.swift */, C40934AA298EEDA900D25014 /* CaskFileParserTest.swift */, C4AFC4B229C4F43300BF4E0D /* HomebrewUpgradableTest.swift */, C4F520662AF03791006787F2 /* ExtensionEnumeratorTest.swift */, diff --git a/phpmon/Common/Core/Constants.swift b/phpmon/Common/Core/Constants.swift index 430c2a9..155099d 100644 --- a/phpmon/Common/Core/Constants.swift +++ b/phpmon/Common/Core/Constants.swift @@ -26,34 +26,34 @@ struct Constants { be displayed. This is based on an appropriate launch time on a basic M1 Apple chip, with some margin for slower Intel chips. */ - static let SlowBootThresholdInterval: TimeInterval = 30.0 + static let SlowBootThresholdInterval: TimeInterval = .seconds(30) /** The interval between automatic background update checks. */ - static let AutomaticUpdateCheckInterval: TimeInterval = 60.0 * 60 * 24 // 24 hours + static let AutomaticUpdateCheckInterval: TimeInterval = .hours(24) /** The minimum interval that must pass before allowing another automatic update check. This prevents excessive checking on frequent app restarts (due to crashes or bad config). */ - static let MinimumUpdateCheckInterval: TimeInterval = 60.0 * 60 // 60 minutes + static let MinimumUpdateCheckInterval: TimeInterval = .minutes(60) /** Retry intervals for failed automatic update checks. Uses exponential backoff before falling back to normal schedule. */ static let UpdateCheckRetryIntervals: [TimeInterval] = [ - 60 * 5, // 5 minutes - 60 * 15, // 15 minutes - 60 * 60, // 1 hour - 60 * 60 * 3 // 3 hours (final attempt) + .minutes(5), + .minutes(15), + .hours(1), + .hours(3) ] /** PHP Monitor supplies a hardcoded list of PHP packages in its own - PHP Version Manager. + PHP Version Manager. This hardcoded list will expire and will need to be modified when the cutoff date occurs, which is when the `php` formula will @@ -62,8 +62,12 @@ struct Constants { If users launch an older version of the app, then a warning will be displayed to let them know that certain operations will not work correctly and that they need to update their app. + + The cutoff date is always a few days after GA of the latest + release, as it often takes a while for Homebrew to make the + new release available and not everyone uses a separate tap. */ - static let PhpFormulaeCutoffDate = "2025-11-30" // YYYY-MM-DD + static let PhpFormulaeCutoffDate = "2025-11-20" // YYYY-MM-DD /** * The PHP versions that are considered pre-release versions. @@ -72,7 +76,8 @@ struct Constants { */ static var ExperimentalPhpVersions: Set { let releaseDates = [ - "8.5": Date.fromString(Self.PhpFormulaeCutoffDate), + // "8.6": Date.fromString("2026-11-30"), // TBD + "8.5": Date.fromString(PhpFormulaeCutoffDate), "8.4": Date.fromString("2024-11-22") ] @@ -108,6 +113,7 @@ struct Constants { "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5" // DEV + // "8.6" // TBD ] /** @@ -130,48 +136,28 @@ struct Constants { "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5" // DEV + // "8.6" // TBD ] ] struct Urls { - // phpmon.app URLs (these are aliased to redirect correctly) + static let DonationPage = url("https://phpmon.app/sponsor") - static let DonationPage = URL( - string: "https://phpmon.app/sponsor" - )! + static let FrequentlyAskedQuestions = url("https://phpmon.app/faq") - static let FrequentlyAskedQuestions = URL( - string: "https://phpmon.app/faq" - )! + static let WikiPhpUnavailable = url("https://phpmon.app/php-unavailable") - static let WikiPhpUnavailable = URL( - string: "https://phpmon.app/php-unavailable" - )! + static let WikiPhpUpgrade = url("https://phpmon.app/php-upgrade") - static let WikiPhpUpgrade = URL( - string: "https://phpmon.app/php-upgrade" - )! + static let DonationPayment = url("https://phpmon.app/sponsor/now") - static let DonationPayment = URL( - string: "https://phpmon.app/sponsor/now" - )! - - static let EarlyAccessChangelog = URL( - string: "https://phpmon.app/early-access/release-notes" - )! + static let EarlyAccessChangelog = url("https://phpmon.app/early-access/release-notes") // API endpoints (via api.phpmon.app) - - static let UpdateCheckEndpoint = URL( - string: "https://api.phpmon.app/api/v1/update-check" - )! + static let UpdateCheckEndpoint = url("https://api.phpmon.app/api/v1/update-check") // GitHub URLs (do not alias these) - - static let GitHubReleases = URL( - string: "https://github.com/nicoverbruggen/phpmon/releases" - )! + static let GitHubReleases = url("https://github.com/nicoverbruggen/phpmon/releases") } - } diff --git a/phpmon/Common/Core/Helpers.swift b/phpmon/Common/Core/Helpers.swift index ef5a0b1..64ea165 100644 --- a/phpmon/Common/Core/Helpers.swift +++ b/phpmon/Common/Core/Helpers.swift @@ -52,7 +52,9 @@ func delay(seconds: Double) async { try! await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) } -/** A simpler way to initialize a fixed, valid URL. */ +/** + A simpler way to initialize a fixed, valid URL. + */ func url(_ string: String) -> URL { return URL(string: string)! } diff --git a/phpmon/Common/Extensions/TimeIntervalExtension.swift b/phpmon/Common/Extensions/TimeIntervalExtension.swift index bbf8990..8fb1e51 100644 --- a/phpmon/Common/Extensions/TimeIntervalExtension.swift +++ b/phpmon/Common/Extensions/TimeIntervalExtension.swift @@ -9,7 +9,14 @@ import Foundation extension TimeInterval { - public static func minutes(_ amount: Int) -> TimeInterval { - return Double(amount * 60) + static func seconds(_ value: Double) -> TimeInterval { value } + static func minutes(_ value: Double) -> TimeInterval { value * 60 } + static func hours(_ value: Double) -> TimeInterval { value * 3600 } + static func days(_ value: Double) -> TimeInterval { value * 86400 } +} + +extension Date { + func adding(_ interval: TimeInterval) -> Date { + return self.addingTimeInterval(interval) } } diff --git a/tests/unit/Parsers/Config/BytePhpPreferenceTest.swift b/tests/unit/Parsers/Config/BytePhpPreferenceTest.swift deleted file mode 100644 index 4e48c15..0000000 --- a/tests/unit/Parsers/Config/BytePhpPreferenceTest.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// BytePhpPreferenceTest.swift -// Unit Tests -// -// Created by Nico Verbruggen on 04/09/2023. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import XCTest - -class BytePhpPreferenceTest: XCTestCase { - - func test_can_extract_memory_value() throws { - let pref = BytePhpPreference(key: "memory_limit") - - XCTAssertEqual(pref.internalValue, "512M") - XCTAssertEqual(pref.unit, .megabyte) - XCTAssertEqual(pref.value, 512) - } - - func test_can_parse_all_kinds_of_values() throws { - var (unit, value) = BytePhpPreference.readFrom(internalValue: "1G")! - XCTAssertEqual(unit, .gigabyte) - XCTAssertEqual(value, 1) - - (unit, value) = BytePhpPreference.readFrom(internalValue: "256M")! - XCTAssertEqual(unit, .megabyte) - XCTAssertEqual(value, 256) - - (unit, value) = BytePhpPreference.readFrom(internalValue: "512K")! - XCTAssertEqual(unit, .kilobyte) - XCTAssertEqual(value, 512) - - (unit, value) = BytePhpPreference.readFrom(internalValue: "1024")! - XCTAssertEqual(unit, .kilobyte) - XCTAssertEqual(value, 1024) - - (unit, value) = BytePhpPreference.readFrom(internalValue: "-1")! - XCTAssertEqual(unit, .kilobyte) - XCTAssertEqual(value, -1) - } -} diff --git a/tests/unit/Parsers/NginxConfigurationTest.swift b/tests/unit/Parsers/NginxConfigurationTest.swift deleted file mode 100644 index 5306072..0000000 --- a/tests/unit/Parsers/NginxConfigurationTest.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// NginxConfigurationTest.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 29/11/2021. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import XCTest - -class NginxConfigurationTest: XCTestCase { - - // MARK: - Test Files - - static var regularUrl: URL { - return Bundle(for: Self.self).url(forResource: "nginx-site", withExtension: "test")! - } - - static var isolatedUrl: URL { - return Bundle(for: Self.self).url(forResource: "nginx-site-isolated", withExtension: "test")! - } - - static var proxyUrl: URL { - return Bundle(for: Self.self).url(forResource: "nginx-proxy", withExtension: "test")! - } - - static var secureProxyUrl: URL { - return Bundle(for: Self.self).url(forResource: "nginx-secure-proxy", withExtension: "test")! - } - - static var customTldProxyUrl: URL { - return Bundle(for: Self.self).url(forResource: "nginx-secure-proxy-custom-tld", withExtension: "test")! - } - - // MARK: - Tests - - func test_can_determine_site_name_and_tld() throws { - XCTAssertEqual( - "nginx-site", - NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.domain - ) - XCTAssertEqual( - "test", - NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.tld - ) - } - - func test_can_determine_isolation() throws { - XCTAssertNil( - NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)?.isolatedVersion - ) - - XCTAssertEqual( - "8.1", - NginxConfigurationFile.from(filePath: NginxConfigurationTest.isolatedUrl.path)?.isolatedVersion - ) - } - - func test_can_determine_proxy() throws { - let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.proxyUrl.path)! - XCTAssertTrue(proxied.contents.contains("# valet stub: proxy.valet.conf")) - XCTAssertEqual("http://127.0.0.1:90", proxied.proxy) - - let normal = NginxConfigurationFile.from(filePath: NginxConfigurationTest.regularUrl.path)! - XCTAssertFalse(normal.contents.contains("# valet stub: proxy.valet.conf")) - XCTAssertEqual(nil, normal.proxy) - } - - func test_can_determine_secured_proxy() throws { - let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.secureProxyUrl.path)! - XCTAssertTrue(proxied.contents.contains("# valet stub: secure.proxy.valet.conf")) - XCTAssertEqual("http://127.0.0.1:90", proxied.proxy) - } - - func test_can_determine_proxy_with_custom_tld() throws { - let proxied = NginxConfigurationFile.from(filePath: NginxConfigurationTest.customTldProxyUrl.path)! - XCTAssertTrue(proxied.contents.contains("# valet stub: secure.proxy.valet.conf")) - XCTAssertEqual("http://localhost:8080", proxied.proxy) - } - -} diff --git a/tests/unit/Parsers/PhpExtensionTest.swift b/tests/unit/Parsers/PhpExtensionTest.swift deleted file mode 100644 index 94f7f04..0000000 --- a/tests/unit/Parsers/PhpExtensionTest.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// ExtensionParserTest.swift -// PHP Monitor -// -// Created by Nico Verbruggen on 13/02/2021. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import XCTest - -class PhpExtensionTest: XCTestCase { - - static var phpIniFileUrl: URL { - return Bundle(for: Self.self).url(forResource: "php", withExtension: "ini")! - } - - func test_can_load_extension() throws { - let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) - - XCTAssertGreaterThan(extensions.count, 0) - } - - func test_extension_name_is_correct() throws { - let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) - - let extensionNames = extensions.map { (ext) -> String in - return ext.name - } - - // These 6 should be found - XCTAssertTrue(extensionNames.contains("xdebug")) - XCTAssertTrue(extensionNames.contains("imagick")) - XCTAssertTrue(extensionNames.contains("sodium-next")) - XCTAssertTrue(extensionNames.contains("opcache")) - XCTAssertTrue(extensionNames.contains("yaml")) - XCTAssertTrue(extensionNames.contains("custom")) - - XCTAssertFalse(extensionNames.contains("fake")) - XCTAssertFalse(extensionNames.contains("nice")) - } - - func test_extension_status_is_correct() throws { - let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) - - // xdebug should be enabled - XCTAssertEqual(extensions[0].enabled, true) - - // imagick should be disabled - XCTAssertEqual(extensions[1].enabled, false) - } - - func test_toggle_works_as_expected() async throws { - let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! - let extensions = PhpExtension.from(filePath: destination.path) - XCTAssertEqual(extensions.count, 6) - - // Try to disable xdebug (should be detected first)! - let xdebug = extensions.first! - XCTAssertTrue(xdebug.name == "xdebug") - XCTAssertEqual(xdebug.enabled, true) - await xdebug.toggle() - XCTAssertEqual(xdebug.enabled, false) - - // Check if the file contains the appropriate data - let file = try! String(contentsOf: destination, encoding: .utf8) - XCTAssertTrue(file.contains("; zend_extension=\"xdebug.so\"")) - - // Make sure if we load the data again, it's disabled - XCTAssertEqual(PhpExtension.from(filePath: destination.path).first!.enabled, false) - } - -} diff --git a/tests/unit/Parsers/ValetRcTest.swift b/tests/unit/Parsers/ValetRcTest.swift deleted file mode 100644 index 3a4428f..0000000 --- a/tests/unit/Parsers/ValetRcTest.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// ValetRcTest.swift -// Unit Tests -// -// Created by Nico Verbruggen on 20/01/2023. -// Copyright © 2023 Nico Verbruggen. All rights reserved. -// - -import XCTest - -class ValetRcTest: XCTestCase { - - // MARK: - Test Files - - static var validPath: URL { - return Bundle(for: Self.self) - .url(forResource: "valetrc", withExtension: "valid")! - } - - static var brokenPath: URL { - return Bundle(for: Self.self) - .url(forResource: "valetrc", withExtension: "broken")! - } - - // MARK: - Tests - - func test_can_extract_fields_from_valetrc_file() throws { - let fakeFile = RCFile.fromPath("/Users/fake/file.rc") - XCTAssertNil(fakeFile) - - // Can parse the file - let validFile = RCFile.fromPath(ValetRcTest.validPath.path) - XCTAssertNotNil(validFile) - - let fields = validFile!.fields - - // Correctly parses and trims (and omits double quotes) per line - XCTAssertEqual(fields["PHP"], "php@8.2") - XCTAssertEqual(fields["OTHER"], "thing") - XCTAssertEqual(fields["PHPMON_WATCH"], "true") - XCTAssertEqual(fields["SYNTAX"], "variable") - - // Ignores entries prefixed with # - XCTAssertTrue(!fields.keys.contains("#PHP")) - - // Ignores invalid lines - XCTAssertTrue(!fields.keys.contains("OOF")) - } -} diff --git a/tests/unit/_ST/Api/TestableApiTest.swift b/tests/unit/_ST/Api/TestableApiTest.swift index 5ac9584..e7326ef 100644 --- a/tests/unit/_ST/Api/TestableApiTest.swift +++ b/tests/unit/_ST/Api/TestableApiTest.swift @@ -9,13 +9,14 @@ import Testing import Foundation -@Suite("Api") struct TestableApiTest { - - @Test - func createFakeApi() { + @Test func createFakeApi() { let api = TestableApi(responses: [ - url("https://api.phpmon.test"): FakeApiResponse(statusCode: 200, headers: [:], text: "{\"success\": true}") + url("https://api.phpmon.test"): FakeApiResponse( + statusCode: 200, + headers: [:], + text: "{\"success\": true}" + ) ]) #expect(api.hasResponse(for: url("https://api.phpmon.test")) == true) @@ -25,5 +26,4 @@ struct TestableApiTest { #expect(response.statusCode == 200) #expect(response.text.contains("success")) } - } diff --git a/tests/unit/_ST/Commands/CommandTest.swift b/tests/unit/_ST/Commands/CommandTest.swift index 5b7feee..8dfe410 100644 --- a/tests/unit/_ST/Commands/CommandTest.swift +++ b/tests/unit/_ST/Commands/CommandTest.swift @@ -8,11 +8,8 @@ import Testing -@Suite("Commands") struct CommandTest { - - @Test - func determinePhpVersion() { + @Test func determinePhpVersion() { let version = Command.execute( path: Paths.php, arguments: ["-v"], @@ -24,5 +21,4 @@ struct CommandTest { #expect(version.contains("built")) #expect(version.contains("Zend")) } - } diff --git a/tests/unit/_ST/Integration/PackagistTest.swift b/tests/unit/_ST/Integration/PackagistTest.swift index 045e17a..3ed6450 100644 --- a/tests/unit/_ST/Integration/PackagistTest.swift +++ b/tests/unit/_ST/Integration/PackagistTest.swift @@ -8,7 +8,6 @@ import Testing -@Suite("Integration") struct PackagistTest { @Test func canRetrieveLaravelValetVersion() async { let packageToCheck = "laravel/valet" diff --git a/tests/unit/_ST/Parsers/Config/BytePhpPreferenceTest.swift b/tests/unit/_ST/Parsers/Config/BytePhpPreferenceTest.swift new file mode 100644 index 0000000..319c0e9 --- /dev/null +++ b/tests/unit/_ST/Parsers/Config/BytePhpPreferenceTest.swift @@ -0,0 +1,41 @@ +// +// BytePhpPreferenceTest.swift +// Unit Tests +// +// Created by Nico Verbruggen on 04/09/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Testing + +struct BytePhpPreferenceTest { + @Test func test_can_extract_memory_value() throws { + let pref = BytePhpPreference(key: "memory_limit") + + #expect(pref.internalValue == "512M") + #expect(pref.unit == .megabyte) + #expect(pref.value == 512) + } + + @Test func test_can_parse_all_kinds_of_values() throws { + var (unit, value) = BytePhpPreference.readFrom(internalValue: "1G")! + #expect(unit == .gigabyte) + #expect(value == 1) + + (unit, value) = BytePhpPreference.readFrom(internalValue: "256M")! + #expect(unit == .megabyte) + #expect(value == 256) + + (unit, value) = BytePhpPreference.readFrom(internalValue: "512K")! + #expect(unit == .kilobyte) + #expect(value == 512) + + (unit, value) = BytePhpPreference.readFrom(internalValue: "1024")! + #expect(unit == .kilobyte) + #expect(value == 1024) + + (unit, value) = BytePhpPreference.readFrom(internalValue: "-1")! + #expect(unit == .kilobyte) + #expect(value == -1) + } +} diff --git a/tests/unit/Parsers/Config/PhpConfigurationFileTest.swift b/tests/unit/_ST/Parsers/Config/PhpConfigurationFileTest.swift similarity index 51% rename from tests/unit/Parsers/Config/PhpConfigurationFileTest.swift rename to tests/unit/_ST/Parsers/Config/PhpConfigurationFileTest.swift index 036c5a9..6e2cc49 100644 --- a/tests/unit/Parsers/Config/PhpConfigurationFileTest.swift +++ b/tests/unit/_ST/Parsers/Config/PhpConfigurationFileTest.swift @@ -6,41 +6,41 @@ // Copyright © 2023 Nico Verbruggen. All rights reserved. // -import XCTest +import Testing +import Foundation -class PhpConfigurationFileTest: XCTestCase { +class PhpConfigurationFileTest { static var phpIniFileUrl: URL { - return Bundle(for: Self.self).url(forResource: "php", withExtension: "ini")! + return TestBundle.url(forResource: "php", withExtension: "ini")! } - func test_can_load_extension() throws { + @Test func test_can_load_extension() throws { + let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path) + + #expect(iniFile != nil) + #expect(!iniFile!.extensions.isEmpty) + } + + @Test func test_can_check_key_existence() throws { let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)! - XCTAssertNotNil(iniFile) - - XCTAssertGreaterThan(iniFile.extensions.count, 0) + #expect(iniFile.has(key: "error_reporting")) + #expect(iniFile.has(key: "display_errors")) + #expect(false == iniFile.has(key: "my_unknown_key")) } - func test_can_check_key_existence() throws { + @Test func test_can_check_key_value() throws { let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)! - XCTAssertTrue(iniFile.has(key: "error_reporting")) - XCTAssertTrue(iniFile.has(key: "display_errors")) - XCTAssertFalse(iniFile.has(key: "my_unknown_key")) + #expect(iniFile.get(for: "error_reporting") != nil) + #expect(iniFile.get(for: "error_reporting") == "E_ALL") + + #expect(iniFile.get(for: "display_errors") != nil) + #expect(iniFile.get(for: "display_errors") == "On") } - func test_can_check_key_value() throws { - let iniFile = PhpConfigurationFile.from(filePath: Self.phpIniFileUrl.path)! - - XCTAssertNotNil(iniFile.get(for: "error_reporting")) - XCTAssert(iniFile.get(for: "error_reporting") == "E_ALL") - - XCTAssertNotNil(iniFile.get(for: "display_errors")) - XCTAssert(iniFile.get(for: "display_errors") == "On") - } - - func test_can_customize_configuration_value() throws { + @Test func test_can_customize_configuration_value() throws { let destination = Utility .copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! @@ -48,15 +48,15 @@ class PhpConfigurationFileTest: XCTestCase { .from(filePath: destination.path)! // 0. Verify the original value - XCTAssertEqual(configurationFile.get(for: "error_reporting"), "E_ALL") + #expect(configurationFile.get(for: "error_reporting") == "E_ALL") // 1. Change the value try! configurationFile.replace( key: "error_reporting", value: "E_ALL & ~E_DEPRECATED & ~E_STRICT" ) - XCTAssertEqual( - configurationFile.get(for: "error_reporting"), + #expect( + configurationFile.get(for: "error_reporting") == "E_ALL & ~E_DEPRECATED & ~E_STRICT" ) @@ -65,20 +65,14 @@ class PhpConfigurationFileTest: XCTestCase { key: "error_reporting", value: "error_reporting" ) - XCTAssertEqual( - configurationFile.get(for: "error_reporting"), - "error_reporting" - ) + #expect(configurationFile.get(for: "error_reporting") == "error_reporting") // 3. Verify subsequent saves weren't broken try! configurationFile.replace( key: "error_reporting", value: "E_ALL" ) - XCTAssertEqual( - configurationFile.get(for: "error_reporting"), - "E_ALL" - ) + #expect(configurationFile.get(for: "error_reporting") == "E_ALL") } } diff --git a/tests/unit/Parsers/HomebrewPackageTest.swift b/tests/unit/_ST/Parsers/HomebrewPackageTest.swift similarity index 70% rename from tests/unit/Parsers/HomebrewPackageTest.swift rename to tests/unit/_ST/Parsers/HomebrewPackageTest.swift index e6a5f8a..eb5627a 100644 --- a/tests/unit/Parsers/HomebrewPackageTest.swift +++ b/tests/unit/_ST/Parsers/HomebrewPackageTest.swift @@ -6,53 +6,54 @@ // Copyright © 2023 Nico Verbruggen. All rights reserved. // -import XCTest +import Testing +import Foundation -class HomebrewPackageTest: XCTestCase { +struct HomebrewPackageTest { // - MARK: SYNTHETIC TESTS static var jsonBrewFile: URL { - return Bundle(for: Self.self) + return TestBundle .url(forResource: "brew-formula", withExtension: "json")! } - func test_can_load_extension_json() throws { + static var jsonBrewServicesFile: URL { + return TestBundle + .url(forResource: "brew-services", withExtension: "json")! + } + + @Test func test_can_load_extension_json() throws { let json = try! String(contentsOf: Self.jsonBrewFile, encoding: .utf8) let package = try! JSONDecoder().decode( [HomebrewPackage].self, from: json.data(using: .utf8)! ).first! - XCTAssertEqual(package.full_name, "php") - XCTAssertEqual(package.aliases.first!, "php@8.4") - XCTAssertEqual(package.installed.contains(where: { installed in + #expect(package.full_name == "php") + #expect(package.aliases.first! == "php@8.4") + #expect(package.installed.contains(where: { installed in installed.version.starts(with: "8.4") - }), true) + }) == true) } - static var jsonBrewServicesFile: URL { - return Bundle(for: Self.self) - .url(forResource: "brew-services", withExtension: "json")! - } - - func test_can_parse_services_json() throws { + @Test func test_can_parse_services_json() throws { let json = try! String(contentsOf: Self.jsonBrewServicesFile, encoding: .utf8) let services = try! JSONDecoder().decode( [HomebrewService].self, from: json.data(using: .utf8)! ) - XCTAssertGreaterThan(services.count, 0) - XCTAssertEqual(services.first?.name, "dnsmasq") - XCTAssertEqual(services.first?.service_name, "homebrew.mxcl.dnsmasq") + #expect(!services.isEmpty) + #expect(services.first?.name == "dnsmasq") + #expect(services.first?.service_name == "homebrew.mxcl.dnsmasq") } - /* // - MARK: LIVE TESTS /// This test requires that you have a valid Homebrew installation set up, /// and requires the Valet services to be installed: php, nginx and dnsmasq. /// If this test fails, there is an issue with your Homebrew installation /// or the JSON API of the Homebrew output may have changed. + @Test(.disabled("Uses system command; enable at your own risk")) func test_can_parse_services_json_from_cli_output() async throws { ActiveShell.useSystem() @@ -65,17 +66,19 @@ class HomebrewPackageTest: XCTestCase { return ["php", "nginx", "dnsmasq"].contains(service.name) }) - XCTAssertTrue(services.contains(where: {$0.name == "php"})) - XCTAssertTrue(services.contains(where: {$0.name == "nginx"})) - XCTAssertTrue(services.contains(where: {$0.name == "dnsmasq"})) - XCTAssertEqual(services.count, 3) + #expect(services.contains(where: {$0.name == "php"})) + #expect(services.contains(where: {$0.name == "nginx"})) + #expect(services.contains(where: {$0.name == "dnsmasq"})) + #expect(services.count == 3) } /// This test requires that you have a valid Homebrew installation set up, /// and requires the `php` formula to be installed. /// If this test fails, there is an issue with your Homebrew installation /// or the JSON API of the Homebrew output may have changed. + @Test(.disabled("Uses system command; enable at your own risk")) func test_can_load_extension_json_from_cli_output() async throws { + ActiveShell.useSystem() let package = try! JSONDecoder().decode( @@ -83,7 +86,6 @@ class HomebrewPackageTest: XCTestCase { from: await Shell.pipe("\(Paths.brew) info php --json").out.data(using: .utf8)! ).first! - XCTAssertTrue(package.name == "php") + #expect(package.full_name == "php") } - */ } diff --git a/tests/unit/_ST/Parsers/NginxConfigurationTest.swift b/tests/unit/_ST/Parsers/NginxConfigurationTest.swift new file mode 100644 index 0000000..a188638 --- /dev/null +++ b/tests/unit/_ST/Parsers/NginxConfigurationTest.swift @@ -0,0 +1,70 @@ +// +// NginxConfigurationTest.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 29/11/2021. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Testing +import Foundation + +struct NginxConfigurationTest { + + // MARK: - Test Files + + static var regularUrl: URL { + TestBundle.url(forResource: "nginx-site", withExtension: "test")! + } + + static var isolatedUrl: URL { + TestBundle.url(forResource: "nginx-site-isolated", withExtension: "test")! + } + + static var proxyUrl: URL { + TestBundle.url(forResource: "nginx-proxy", withExtension: "test")! + } + + static var secureProxyUrl: URL { + TestBundle.url(forResource: "nginx-secure-proxy", withExtension: "test")! + } + + static var customTldProxyUrl: URL { + TestBundle.url(forResource: "nginx-secure-proxy-custom-tld", withExtension: "test")! + } + + // MARK: - Tests + + @Test func test_can_determine_site_name_and_tld() throws { + #expect("nginx-site" == NginxConfigurationFile.from(filePath: Self.regularUrl.path)?.domain) + #expect("test" == NginxConfigurationFile.from(filePath: Self.regularUrl.path)?.tld) + } + + @Test func test_can_determine_isolation() throws { + #expect(nil == NginxConfigurationFile.from(filePath: Self.regularUrl.path)?.isolatedVersion) + #expect("8.1" == NginxConfigurationFile.from(filePath: Self.isolatedUrl.path)?.isolatedVersion) + } + + @Test func test_can_determine_proxy() throws { + let proxied = NginxConfigurationFile.from(filePath: Self.proxyUrl.path)! + #expect(proxied.contents.contains("# valet stub: proxy.valet.conf")) + #expect("http://127.0.0.1:90" == proxied.proxy) + + let normal = NginxConfigurationFile.from(filePath: Self.regularUrl.path)! + #expect(false == normal.contents.contains("# valet stub: proxy.valet.conf")) + #expect(nil == normal.proxy) + } + + @Test func test_can_determine_secured_proxy() throws { + let proxied = NginxConfigurationFile.from(filePath: Self.secureProxyUrl.path)! + #expect(proxied.contents.contains("# valet stub: secure.proxy.valet.conf")) + #expect("http://127.0.0.1:90" == proxied.proxy) + } + + @Test func test_can_determine_proxy_with_custom_tld() throws { + let proxied = NginxConfigurationFile.from(filePath: Self.customTldProxyUrl.path)! + #expect(proxied.contents.contains("# valet stub: secure.proxy.valet.conf")) + #expect("http://localhost:8080" == proxied.proxy) + } + +} diff --git a/tests/unit/_ST/Parsers/PhpExtensionTest.swift b/tests/unit/_ST/Parsers/PhpExtensionTest.swift new file mode 100644 index 0000000..c81b296 --- /dev/null +++ b/tests/unit/_ST/Parsers/PhpExtensionTest.swift @@ -0,0 +1,71 @@ +// +// ExtensionParserTest.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 13/02/2021. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Testing +import Foundation + +struct PhpExtensionTest { + static var phpIniFileUrl: URL { + return TestBundle.url(forResource: "php", withExtension: "ini")! + } + + @Test func test_can_load_extension() throws { + let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) + + #expect(!extensions.isEmpty) + } + + @Test func test_extension_name_is_correct() throws { + let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) + + let extensionNames = extensions.map { (ext) -> String in + return ext.name + } + + // These 6 should be found + #expect(extensionNames.contains("xdebug")) + #expect(extensionNames.contains("imagick")) + #expect(extensionNames.contains("sodium-next")) + #expect(extensionNames.contains("opcache")) + #expect(extensionNames.contains("yaml")) + #expect(extensionNames.contains("custom")) + + #expect(extensionNames.contains("fake") == false) + #expect(extensionNames.contains("nice") == false) + } + + @Test func test_extension_status_is_correct() throws { + let extensions = PhpExtension.from(filePath: Self.phpIniFileUrl.path) + + // xdebug should be enabled + #expect(extensions[0].enabled == true) + + // imagick should be disabled + #expect(extensions[1].enabled == false) + } + + @Test func test_toggle_works_as_expected() async throws { + let destination = Utility.copyToTemporaryFile(resourceName: "php", fileExtension: "ini")! + let extensions = PhpExtension.from(filePath: destination.path) + #expect(extensions.count == 6) + + // Try to disable xdebug (should be detected first)! + let xdebug = extensions.first! + #expect(xdebug.name == "xdebug") + #expect(xdebug.enabled == true) + await xdebug.toggle() + #expect(xdebug.enabled == false) + + // Check if the file contains the appropriate data + let file = try! String(contentsOf: destination, encoding: .utf8) + #expect(file.contains("; zend_extension=\"xdebug.so\"")) + + // Make sure if we load the data again, it's disabled + #expect(PhpExtension.from(filePath: destination.path).first!.enabled == false) + } +} diff --git a/tests/unit/_ST/Parsers/ValetConfigurationTest.swift b/tests/unit/_ST/Parsers/ValetConfigurationTest.swift index d251cc8..a14904f 100644 --- a/tests/unit/_ST/Parsers/ValetConfigurationTest.swift +++ b/tests/unit/_ST/Parsers/ValetConfigurationTest.swift @@ -9,7 +9,6 @@ import Testing import Foundation -@Suite("Parsers") struct ValetConfigurationTest { static var jsonConfigFileUrl: URL { return TestBundle.url( @@ -18,8 +17,7 @@ struct ValetConfigurationTest { )! } - @Test("Can load config file") - func can_load_config_file() throws { + @Test func can_load_config_file() throws { let json = try? String( contentsOf: Self.jsonConfigFileUrl, encoding: .utf8 @@ -37,5 +35,4 @@ struct ValetConfigurationTest { #expect(config.defaultSite == "/Users/username/default-site") #expect(config.loopback == "127.0.0.1") } - } diff --git a/tests/unit/_ST/Parsers/ValetRcTest.swift b/tests/unit/_ST/Parsers/ValetRcTest.swift new file mode 100644 index 0000000..ac0b981 --- /dev/null +++ b/tests/unit/_ST/Parsers/ValetRcTest.swift @@ -0,0 +1,45 @@ +// +// ValetRcTest.swift +// Unit Tests +// +// Created by Nico Verbruggen on 20/01/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Testing +import Foundation + +struct ValetRcTest { + // MARK: - Test Files + static var validPath: URL { + return TestBundle.url(forResource: "valetrc", withExtension: "valid")! + } + + static var brokenPath: URL { + return TestBundle.url(forResource: "valetrc", withExtension: "broken")! + } + + // MARK: - Tests + @Test func test_can_extract_fields_from_valet_rc_file() throws { + let fakeFile = RCFile.fromPath("/Users/fake/file.rc") + #expect(nil == fakeFile) + + // Can parse the file + let validFile = RCFile.fromPath(ValetRcTest.validPath.path) + #expect(nil != validFile) + + let fields = validFile!.fields + + // Correctly parses and trims (and omits double quotes) per line + #expect(fields["PHP"] == "php@8.2") + #expect(fields["OTHER"] == "thing") + #expect(fields["PHPMON_WATCH"] == "true") + #expect(fields["SYNTAX"] == "variable") + + // Ignores entries prefixed with # + #expect(!fields.keys.contains("#PHP")) + + // Ignores invalid lines + #expect(!fields.keys.contains("OOF")) + } +}