diff --git a/Package.swift b/Package.swift index 54d38762..fe2415f7 100644 --- a/Package.swift +++ b/Package.swift @@ -244,6 +244,7 @@ let package = Package( "internal/platform/implementation/apple/Log/Tests", "internal/platform/implementation/apple/Mediums/BLE/Tests", "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests", + "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/", "internal/platform/implementation/apple/Mediums/Hotspot/Tests", "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests", "internal/platform/implementation/apple/Tests", @@ -274,6 +275,7 @@ let package = Package( "internal/platform/implementation/apple/Mediums/BUILD", "internal/platform/implementation/apple/Mediums/BLE/BUILD", "internal/platform/implementation/apple/Mediums/BLE/Sockets/BUILD", + "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/BUILD", "internal/platform/implementation/apple/Mediums/Hotspot/BUILD", "internal/platform/implementation/apple/Mediums/WiFiCommon/BUILD", "internal/platform/implementation/BUILD", diff --git a/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/BUILD b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/BUILD new file mode 100644 index 00000000..06e6284e --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/BUILD @@ -0,0 +1,58 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_apple//apple:ios.bzl", "ios_unit_test") +load("@rules_cc//cc:objc_library.bzl", "objc_library") +load("//third_party/nearby:minimum_os.bzl", "IOS_MINIMUM_OS") + +package(default_visibility = ["//:__subpackages__"]) + +objc_library( + name = "Fake", + testonly = True, + srcs = ["CLLocationManagerFake.m"], + hdrs = ["CLLocationManagerFake.h"], + deps = [ + "//third_party/apple_frameworks:CoreLocation", + "//third_party/apple_frameworks:Foundation", + ], +) + +objc_library( + name = "CLLocationManagerDelegateFake", + testonly = True, + srcs = ["CLLocationManagerDelegateFake.m"], + hdrs = ["CLLocationManagerDelegateFake.h"], + deps = ["//third_party/apple_frameworks:CoreLocation"], +) + +objc_library( + name = "TestsLib", + testonly = True, + srcs = ["CLLocationManagerFakeTests.m"], + deps = [ + ":CLLocationManagerDelegateFake", + ":Fake", + "//third_party/apple_frameworks:CoreLocation", + "//third_party/apple_frameworks:Foundation", + "//third_party/apple_frameworks:XCTest", + ], +) + +ios_unit_test( + name = "Tests", + minimum_os_version = IOS_MINIMUM_OS, + runner = "//testing/utp/ios:IOS_LATEST", + deps = [":TestsLib"], +) diff --git a/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.h b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.h new file mode 100644 index 00000000..41d3d44f --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.h @@ -0,0 +1,21 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +@interface CLLocationManagerDelegateFake : NSObject + +@property(nonatomic, copy, nonnull) void (^authorizationUpdateBlock)(CLAuthorizationStatus API_AVAILABLE(ios(14.0))); + +@end diff --git a/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.m b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.m new file mode 100644 index 00000000..b03f48cc --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.m @@ -0,0 +1,25 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.h" + +@implementation CLLocationManagerDelegateFake + +- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager { + if (@available(iOS 14.0, *)) { + self.authorizationUpdateBlock(manager.authorizationStatus); + } +} + +@end diff --git a/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h new file mode 100644 index 00000000..b385ccb2 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h @@ -0,0 +1,40 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Fake object of CLLocationManager. */ +__attribute__((objc_subclassing_restricted)) // NO_LINT +@interface CLLocationManagerFake : CLLocationManager + +/// Returns the current authorization status. +- (CLAuthorizationStatus)authorizationStatus; + +/// Request permission to access location data when the app is in use. +- (void)requestWhenInUseAuthorization; + +/// Request permission to always access location data. +- (void)requestAlwaysAuthorization; + +/// Sets the authorization status for location permission. +- (void)setAuthorizationStatus:(CLAuthorizationStatus)authorizationStatus; + +@end + +NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.m b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.m new file mode 100644 index 00000000..4a4b6235 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.m @@ -0,0 +1,66 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h" + +#import +#import +#import + +@implementation CLLocationManagerFake { + CLAuthorizationStatus _authorizationStatus; + id _delegate; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _authorizationStatus = kCLAuthorizationStatusNotDetermined; + } + + return self; +} + +- (void)setDelegate:(id)newValue { + _delegate = newValue; +} + +- (id)delegate { + return _delegate; +} + +- (CLAuthorizationStatus)authorizationStatus { + if (@available(iOS 14.0, *)) { + return _authorizationStatus; + } + return 0; +} + +- (void)requestWhenInUseAuthorization { + if (@available(iOS 14.0, *)) { + [self.delegate locationManagerDidChangeAuthorization:self]; + } +} + +- (void)requestAlwaysAuthorization { + if (@available(iOS 14.0, *)) { + [self.delegate locationManagerDidChangeAuthorization:self]; + } +} + +- (void)setAuthorizationStatus:(CLAuthorizationStatus)authorizationStatus { + _authorizationStatus = authorizationStatus; +} + +@end diff --git a/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFakeTests.m b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFakeTests.m new file mode 100644 index 00000000..55d93290 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFakeTests.m @@ -0,0 +1,107 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import +#import + +#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.h" +#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h" + +@interface CLLocationManagerFakeTests : XCTestCase +@end + +@implementation CLLocationManagerFakeTests { + CLLocationManagerFake *_fakeLocationManager; +} + +- (void)setUp { + [super setUp]; + _fakeLocationManager = [[CLLocationManagerFake alloc] init]; +} + +- (void)testRequestWhenInUseAuthorizationGranted { + // GIVEN + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedWhenInUse]; + + // WHEN + [_fakeLocationManager requestWhenInUseAuthorization]; + + // THEN + if (@available(iOS 14.0, *)) { + XCTAssertEqual([_fakeLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedWhenInUse); + } +} + +- (void)testRequestWhenInUseAuthorizationDenied { + // GIVEN + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusDenied]; + + // WHEN + [_fakeLocationManager requestWhenInUseAuthorization]; + + // THEN + if (@available(iOS 14.0, *)) { + XCTAssertEqual([_fakeLocationManager authorizationStatus], kCLAuthorizationStatusDenied); + } +} + +- (void)testRequestAlwaysAuthorizationGranted { + // GIVEN + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedAlways]; + + // WHEN + [_fakeLocationManager requestAlwaysAuthorization]; + + // THEN + if (@available(iOS 14.0, *)) { + XCTAssertEqual([_fakeLocationManager authorizationStatus], kCLAuthorizationStatusAuthorizedAlways); + } +} + +- (void)testRequestAlwaysAuthorizationDenied { + // GIVEN + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusDenied]; + + // WHEN + [_fakeLocationManager requestAlwaysAuthorization]; + + // THEN + if (@available(iOS 14.0, *)) { + XCTAssertEqual([_fakeLocationManager authorizationStatus], kCLAuthorizationStatusDenied); + } +} + +- (void)testCallsDelegateAfterRequestingAuthorization { + // GIVEN + CLAuthorizationStatus expectedStatus = kCLAuthorizationStatusAuthorizedAlways; + [_fakeLocationManager setAuthorizationStatus:expectedStatus]; + + XCTestExpectation *delegateExpectation = + [self expectationWithDescription:@"Delegate should have been called."]; + CLLocationManagerDelegateFake *fakeDelegate = [[CLLocationManagerDelegateFake alloc] init]; + fakeDelegate.authorizationUpdateBlock = ^(CLAuthorizationStatus newStatus) { + XCTAssertEqual(newStatus, expectedStatus); + [delegateExpectation fulfill]; + }; + [_fakeLocationManager setDelegate:fakeDelegate]; + + // WHEN + [_fakeLocationManager requestAlwaysAuthorization]; + + // THEN + [self waitForExpectations:@[ delegateExpectation ]]; +} + +@end diff --git a/internal/platform/implementation/apple/Mediums/Hotspot/Tests/BUILD b/internal/platform/implementation/apple/Mediums/Hotspot/Tests/BUILD index 81f15651..6221f82d 100644 --- a/internal/platform/implementation/apple/Mediums/Hotspot/Tests/BUILD +++ b/internal/platform/implementation/apple/Mediums/Hotspot/Tests/BUILD @@ -25,12 +25,19 @@ objc_library( "GNCHotspotMediumTest.m", ], deps = [ + "//internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake", "//internal/platform/implementation/apple/Mediums/Hotspot", "//internal/platform/implementation/apple/Mediums/WiFiCommon", "//third_party/apple_frameworks:CoreLocation", "//third_party/apple_frameworks:NetworkExtension", "//third_party/apple_frameworks:XCTest", - ], + "//third_party/objective_c/ocmock/v3:OCMock", + ] + select({ + "@platforms//os:platform_macos": [ + "//third_party/apple_frameworks:CoreWLAN", + ], + "//conditions:default": [], + }), ) ios_unit_test( diff --git a/internal/platform/implementation/apple/Mediums/Hotspot/Tests/GNCHotspotMediumTest.m b/internal/platform/implementation/apple/Mediums/Hotspot/Tests/GNCHotspotMediumTest.m index df97f423..d91977df 100644 --- a/internal/platform/implementation/apple/Mediums/Hotspot/Tests/GNCHotspotMediumTest.m +++ b/internal/platform/implementation/apple/Mediums/Hotspot/Tests/GNCHotspotMediumTest.m @@ -12,49 +12,253 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import - #import "internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h" +#if TARGET_OS_OSX +#import +#endif #import #import +#import +#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkError.h" +#import "third_party/objective_c/ocmock/v3/Source/OCMock/OCMock.h" NS_ASSUME_NONNULL_BEGIN +static NSString *const kSSID = @"TestSSID"; +static NSString *const kPassword = @"TestPassword"; +static NSString *const kWrongSSID = @"WrongSSID"; + // Expose for testing @interface GNCHotspotMedium (Testing) -- (nullable GNCHotspotSocket *)connectToEndpoint:(nw_endpoint_t)endpoint - includePeerToPeer:(BOOL)includePeerToPeer - cancelSource:(nullable dispatch_source_t)cancelSource - error:(NSError *_Nullable *_Nullable)error; +@property(nonatomic) CLLocationManager *locationManager; @end @interface GNCHotspotMediumTests : XCTestCase @end -// TODO: b/434870979 - Use fake LocationManager and NWFramework instead of mocking. @implementation GNCHotspotMediumTests { GNCHotspotMedium *_hotspotMedium; + CLLocationManagerFake *_fakeLocationManager; +#if TARGET_OS_OSX + id _mockWiFiClient; + id _mockWiFiClientClass; + id _mockWiFiInterface; + id _mockNetwork; +#endif // TARGET_OS_OSX } - (void)setUp { [super setUp]; _hotspotMedium = [[GNCHotspotMedium alloc] initWithQueue:dispatch_get_main_queue()]; + _fakeLocationManager = [[CLLocationManagerFake alloc] init]; + _hotspotMedium.locationManager = _fakeLocationManager; +#if TARGET_OS_OSX + // Success/failure tests for connecting on macOS require mocking CoreWLAN. + _mockWiFiInterface = OCMClassMock([CWInterface class]); + _mockWiFiClient = OCMClassMock([CWWiFiClient class]); + _mockWiFiClientClass = OCMClassMock([CWWiFiClient class]); + OCMStub([_mockWiFiClientClass sharedWiFiClient]).andReturn(_mockWiFiClient); + OCMStub([_mockWiFiClient interface]).andReturn(_mockWiFiInterface); + _mockNetwork = OCMClassMock([CWNetwork class]); +#endif // TARGET_OS_OSX +} + +- (void)tearDown { + [super tearDown]; +#if TARGET_OS_OSX + [_mockWiFiClient stopMocking]; + [_mockWiFiClientClass stopMocking]; + [_mockWiFiInterface stopMocking]; + [_mockNetwork stopMocking]; +#endif // TARGET_OS_OSX } - (void)testInit { - XCTAssertNotNil(_hotspotMedium); -} - -- (void)testDefaultInit { GNCHotspotMedium *medium = [[GNCHotspotMedium alloc] init]; XCTAssertNotNil(medium); } +- (void)testInitWithQueue { + XCTAssertNotNil(_hotspotMedium); +} + +- (void)testConnectToWifiNetworkWithSSID_EmptySSID { + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:@"" password:kPassword]); +} + +- (void)testConnectToWifiNetworkWithSSID_EmptyPassword { + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:@""]); +} + +#if TARGET_OS_IOS +- (void)testConnectToWifiNetworkWithSSID_iOS { + // iOS implementation via NEHotspotConfigurationManager is not supported in unit tests + // and the current implementation returns NO. + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:kPassword]); +} +#elif TARGET_OS_OSX +- (void)testConnectToWifiNetworkWithSSID_PermissionDenied_MacOS { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusDenied]; + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:kPassword]); +} + +- (void)testConnectToWifiNetworkWithSSID_PermissionNotDetermined_MacOS { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusNotDetermined]; + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:kPassword]); +} + +- (void)testConnectToWifiNetworkWithSSID_Success_MacOS { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedAlways]; + OCMStub([_mockWiFiInterface scanForNetworksWithSSID:kSSID error:[OCMArg anyObjectRef]]) + .andReturn([NSSet setWithObject:_mockNetwork]); + OCMStub([_mockWiFiInterface associateToNetwork:_mockNetwork + password:kPassword + error:[OCMArg anyObjectRef]]) + .andReturn(YES); + XCTAssertTrue([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:kPassword]); +} + +- (void)testConnectToWifiNetworkWithSSID_ScanFailure_MacOS { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedAlways]; + OCMStub([_mockWiFiInterface scanForNetworksWithSSID:kSSID error:[OCMArg anyObjectRef]]) + .andReturn(nil); + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:kPassword]); +} + +- (void)testConnectToWifiNetworkWithSSID_NetworkNotFound_MacOS { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedAlways]; + OCMStub([_mockWiFiInterface scanForNetworksWithSSID:kSSID error:[OCMArg anyObjectRef]]) + .andReturn([NSSet set]); + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:kPassword]); +} + +- (void)testConnectToWifiNetworkWithSSID_AssociationFailure_MacOS { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedAlways]; + OCMStub([_mockWiFiInterface scanForNetworksWithSSID:kSSID error:[OCMArg anyObjectRef]]) + .andReturn([NSSet setWithObject:_mockNetwork]); + OCMStub([_mockWiFiInterface associateToNetwork:_mockNetwork + password:kPassword + error:[OCMArg anyObjectRef]]) + .andReturn(NO); + XCTAssertFalse([_hotspotMedium connectToWifiNetworkWithSSID:kSSID password:kPassword]); +} +#endif + +#if TARGET_OS_IOS +- (void)testDisconnectToWifiNetworkWithSSID_iOS { + // iOS implementation is empty, this is a no-op. + [_hotspotMedium disconnectToWifiNetworkWithSSID:kSSID]; +} +#elif TARGET_OS_OSX +- (void)testDisconnectToWifiNetworkWithSSID_MacOS { + OCMStub([_mockWiFiInterface ssid]).andReturn(kSSID); + OCMExpect([_mockWiFiInterface disassociate]); + [_hotspotMedium disconnectToWifiNetworkWithSSID:kSSID]; + OCMVerifyAll(_mockWiFiInterface); +} + +- (void)testDisconnectToWifiNetworkWithSSID_WrongSSID_MacOS { + OCMStub([_mockWiFiInterface ssid]).andReturn(kWrongSSID); + OCMReject([_mockWiFiInterface disassociate]); + [_hotspotMedium disconnectToWifiNetworkWithSSID:kSSID]; + OCMVerifyAll(_mockWiFiInterface); +} + +- (void)testDisconnectToWifiNetworkWithSSID_EmptySSID_MacOS { + OCMReject([_mockWiFiInterface disassociate]); + [_hotspotMedium disconnectToWifiNetworkWithSSID:@""]; + OCMVerifyAll(_mockWiFiInterface); +} +#endif + +- (void)testConnectToHost { + GNCIPv4Address *host = [[GNCIPv4Address alloc] initWithByte1:192 byte2:168 byte3:1 byte4:1]; + NSInteger port = 8080; + NSError *error = nil; + // This method is hard to test without network access or more complex mocking of nw_connection + // or an enhanced GNCFakeNWConnectionWrapper to control connection states. + XCTAssertNil([_hotspotMedium connectToHost:host port:port cancelSource:nil error:&error]); +} + +- (void)testConnectToHost_WithCancelSource { + GNCIPv4Address *host = [[GNCIPv4Address alloc] initWithByte1:192 byte2:168 byte3:1 byte4:1]; + NSInteger port = 8080; + NSError *error = nil; + dispatch_source_t cancelSource = dispatch_source_create( + DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)); + dispatch_source_set_event_handler(cancelSource, ^{ + }); + dispatch_resume(cancelSource); + // Trigger the cancel source. + dispatch_source_merge_data(cancelSource, 1); + XCTAssertNil([_hotspotMedium connectToHost:host + port:port + cancelSource:cancelSource + error:&error]); +} + +- (void)testConnectToHost_InvalidHost { + GNCIPv4Address *host = [GNCIPv4Address addressWithDottedRepresentation:@"256.1.1.1"]; + NSInteger port = 8080; + NSError *error = nil; + XCTAssertNil([_hotspotMedium connectToHost:host port:port cancelSource:nil error:&error]); + XCTAssertNotNil(error); + XCTAssertEqualObjects(error.domain, GNCNWFrameworkErrorDomain); + XCTAssertEqual(error.code, GNCNWFrameworkErrorUnknown); +} + +- (void)testGetCurrentWifiSSID_Success { +#if TARGET_OS_IOS + if (@available(iOS 14.0, *)) { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedWhenInUse]; + // NEHotspotNetwork returns nil on simulator. + XCTAssertNil([_hotspotMedium getCurrentWifiSSID]); + } else { + XCTAssertNil([_hotspotMedium getCurrentWifiSSID]); + } +#else // TARGET_OS_OSX + NSString *expectedSSID = @"CurrentSSID"; + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedWhenInUse]; + OCMStub([_mockWiFiInterface ssid]).andReturn(expectedSSID); + XCTAssertEqualObjects([_hotspotMedium getCurrentWifiSSID], expectedSSID); +#endif // TARGET_OS_IOS +} + +- (void)testGetCurrentWifiSSID_PermissionDenied { + if (@available(iOS 14.0, *)) { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusDenied]; + XCTAssertNil([_hotspotMedium getCurrentWifiSSID]); + } +} + +#if TARGET_OS_IOS +- (void)testGetCurrentWifiSSID_PermissionGranted_iOS { + if (@available(iOS 14.0, *)) { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusAuthorizedAlways]; + // NEHotspotNetwork returns nil on simulator. + XCTAssertNil([_hotspotMedium getCurrentWifiSSID]); + } +} + +- (void)testGetCurrentWifiSSID_PermissionNotDetermined_iOS { + if (@available(iOS 14.0, *)) { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusNotDetermined]; + XCTAssertNil([_hotspotMedium getCurrentWifiSSID]); + } +} + +- (void)testGetCurrentWifiSSID_PermissionRestricted_iOS { + if (@available(iOS 14.0, *)) { + [_fakeLocationManager setAuthorizationStatus:kCLAuthorizationStatusRestricted]; + XCTAssertNil([_hotspotMedium getCurrentWifiSSID]); + } +} +#endif // TARGET_OS_IOS @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END