import Testing @testable import bookstax // MARK: - URL Validation @Suite("OnboardingViewModel – URL Validation") struct OnboardingViewModelURLTests { // MARK: Empty input @Test("Empty URL sets error and returns false") func emptyURL() { let vm = OnboardingViewModel() vm.serverURLInput = "" #expect(vm.validateServerURL() == false) #expect(vm.serverURLError != nil) } @Test("Whitespace-only URL sets error and returns false") func whitespaceURL() { let vm = OnboardingViewModel() vm.serverURLInput = " " #expect(vm.validateServerURL() == false) #expect(vm.serverURLError != nil) } // MARK: Auto-prefix https:// @Test("URL without scheme gets https:// prepended") func autoprefixHTTPS() { let vm = OnboardingViewModel() vm.serverURLInput = "wiki.example.com" let result = vm.validateServerURL() #expect(result == true) #expect(vm.serverURLInput.hasPrefix("https://")) } @Test("URL already starting with https:// is left unchanged") func httpsAlreadyPresent() { let vm = OnboardingViewModel() vm.serverURLInput = "https://wiki.example.com" let result = vm.validateServerURL() #expect(result == true) #expect(vm.serverURLInput == "https://wiki.example.com") } @Test("URL starting with http:// is left as-is (not double-prefixed)") func httpNotDoublePrefixed() { let vm = OnboardingViewModel() vm.serverURLInput = "http://wiki.example.com" let result = vm.validateServerURL() #expect(result == true) #expect(vm.serverURLInput == "http://wiki.example.com") } // MARK: Trailing slash removal @Test("Trailing slash is stripped from URL") func trailingSlash() { let vm = OnboardingViewModel() vm.serverURLInput = "https://wiki.example.com/" _ = vm.validateServerURL() #expect(vm.serverURLInput == "https://wiki.example.com") } @Test("Multiple trailing slashes – only last slash stripped then accepted") func multipleSlashesInPath() { let vm = OnboardingViewModel() vm.serverURLInput = "https://wiki.example.com/path/" _ = vm.validateServerURL() #expect(vm.serverURLInput == "https://wiki.example.com/path") } // MARK: Successful validation @Test("Valid URL clears any previous error") func validURLClearsError() { let vm = OnboardingViewModel() vm.serverURLError = "Previous error" vm.serverURLInput = "https://books.mycompany.com" #expect(vm.validateServerURL() == true) #expect(vm.serverURLError == nil) } } // MARK: - isHTTP Flag @Suite("OnboardingViewModel – isHTTP") struct OnboardingViewModelHTTPTests { @Test("http:// URL sets isHTTP = true") func httpURL() { let vm = OnboardingViewModel() vm.serverURLInput = "http://wiki.local" #expect(vm.isHTTP == true) } @Test("https:// URL sets isHTTP = false") func httpsURL() { let vm = OnboardingViewModel() vm.serverURLInput = "https://wiki.local" #expect(vm.isHTTP == false) } @Test("Empty URL is not HTTP") func emptyNotHTTP() { let vm = OnboardingViewModel() vm.serverURLInput = "" #expect(vm.isHTTP == false) } } // MARK: - isRemoteServer Detection @Suite("OnboardingViewModel – isRemoteServer") struct OnboardingViewModelRemoteTests { // MARK: Local / private addresses @Test("localhost is not remote") func localhost() { let vm = OnboardingViewModel() vm.serverURLInput = "http://localhost" #expect(vm.isRemoteServer == false) } @Test("127.0.0.1 is not remote") func loopback() { let vm = OnboardingViewModel() vm.serverURLInput = "http://127.0.0.1" #expect(vm.isRemoteServer == false) } @Test("IPv6 loopback ::1 is not remote") func ipv6Loopback() { let vm = OnboardingViewModel() vm.serverURLInput = "http://[::1]" #expect(vm.isRemoteServer == false) } @Test(".local mDNS host is not remote") func mdnsLocal() { let vm = OnboardingViewModel() vm.serverURLInput = "http://bookstack.local" #expect(vm.isRemoteServer == false) } @Test("Plain hostname without dots is not remote") func plainHostname() { let vm = OnboardingViewModel() vm.serverURLInput = "http://mywiki" #expect(vm.isRemoteServer == false) } @Test("10.x.x.x private range is not remote") func privateClass_A() { let vm = OnboardingViewModel() vm.serverURLInput = "http://10.0.1.50" #expect(vm.isRemoteServer == false) } @Test("192.168.x.x private range is not remote") func privateClass_C() { let vm = OnboardingViewModel() vm.serverURLInput = "http://192.168.1.100" #expect(vm.isRemoteServer == false) } @Test("172.16.x.x private range is not remote") func privateClass_B_low() { let vm = OnboardingViewModel() vm.serverURLInput = "http://172.16.0.1" #expect(vm.isRemoteServer == false) } @Test("172.31.x.x private range is not remote") func privateClass_B_high() { let vm = OnboardingViewModel() vm.serverURLInput = "http://172.31.255.255" #expect(vm.isRemoteServer == false) } @Test("172.15.x.x is outside private range and is remote") func justBelowPrivateClass_B() { let vm = OnboardingViewModel() vm.serverURLInput = "http://172.15.0.1" #expect(vm.isRemoteServer == true) } @Test("172.32.x.x is outside private range and is remote") func justAbovePrivateClass_B() { let vm = OnboardingViewModel() vm.serverURLInput = "http://172.32.0.1" #expect(vm.isRemoteServer == true) } // MARK: Remote addresses @Test("Public IP 8.8.8.8 is remote") func publicIP() { let vm = OnboardingViewModel() vm.serverURLInput = "http://8.8.8.8" #expect(vm.isRemoteServer == true) } @Test("Public domain with subdomain is remote") func publicDomain() { let vm = OnboardingViewModel() vm.serverURLInput = "https://wiki.mycompany.com" #expect(vm.isRemoteServer == true) } @Test("Top-level domain name is remote") func topLevelDomain() { let vm = OnboardingViewModel() vm.serverURLInput = "https://bookstack.io" #expect(vm.isRemoteServer == true) } @Test("Empty URL is not remote") func emptyIsNotRemote() { let vm = OnboardingViewModel() vm.serverURLInput = "" #expect(vm.isRemoteServer == false) } }