From d92eae1535e544db6f8741b0bae33722b87efc1b Mon Sep 17 00:00:00 2001 From: Edwin Wu Date: Wed, 12 Nov 2025 00:44:19 -0800 Subject: [PATCH] [NC Apple coverage] Refactor GNSPeripheralManager tests to use fake objects. III PiperOrigin-RevId: 831259501 --- .../Source/Peripheral/GNSPeripheralManager.m | 1 - .../apple/Mediums/BLE/Sockets/Tests/BUILD | 10 +- .../Peripheral/GNSPeripheralManagerTest.m | 1123 +++++++---------- 3 files changed, 482 insertions(+), 652 deletions(-) diff --git a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m index b7f6f21e..050c7ee4 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.m @@ -445,7 +445,6 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f; return; } - NSAssert(peripheral.isAdvertising, @"Peripheral should be advertising."); GNCLoggerInfo(@"Peripheral did start advertising %@", _advertisementData); // Once an advertisment operation is over, check if the advertised data is up-to-date. diff --git a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/BUILD b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/BUILD index 5c2967ee..1e714193 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/BUILD +++ b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/BUILD @@ -47,13 +47,13 @@ objc_library( srcs = [ "Peripheral/GNSFakePeripheralManager.m", "Peripheral/GNSFakePeripheralServiceManager.m", + "Peripheral/GNSPeripheralManagerTest.m", ], hdrs = [ "Peripheral/GNSFakePeripheralManager.h", "Peripheral/GNSFakePeripheralServiceManager.h", ], deps = [ - "//internal/platform/implementation/apple/Log:GNCLogger", "//internal/platform/implementation/apple/Mediums/BLE/Sockets:Peripheral", "//internal/platform/implementation/apple/Mediums/BLE/Sockets:Shared", "//third_party/apple_frameworks:CoreBluetooth", @@ -63,6 +63,14 @@ objc_library( ], ) +ios_unit_test( + name = "PeripheralTests", + google_create_srl_bindings_module = False, + minimum_os_version = IOS_MINIMUM_OS, + runner = "//testing/utp/ios:IOS_LATEST", + deps = [":PeripheralTestsLib"], +) + objc_library( name = "SharedTestsLib", testonly = 1, diff --git a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSPeripheralManagerTest.m b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSPeripheralManagerTest.m index 3ff16fca..10bcae80 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSPeripheralManagerTest.m +++ b/internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSPeripheralManagerTest.m @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// 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. @@ -12,25 +12,45 @@ // See the License for the specific language governing permissions and // limitations under the License. +#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.h" + +#import #import #import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager+Private.h" #import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager+Private.h" +#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h" #import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket+Private.h" +#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket.h" +#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSFakePeripheralManager.h" +#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSFakePeripheralServiceManager.h" #import "third_party/objective_c/ocmock/v3/Source/OCMock/OCMock.h" +@interface GNSPeripheralManager () { + @public + NSMutableDictionary *> + *_handlerQueuePerSocketIdentifier; + NSDictionary *_advertisementInProgressData; + NSDictionary *_advertisementData; +} +@end + @interface TestGNSPeripheralManager : GNSPeripheralManager -@property(nonatomic, strong) id cbPeripheralManagerMock; -@property(nonatomic, strong) NSDictionary *cbOptions; +@property(nonatomic, strong) GNSFakePeripheralManager *fakeCBPeripheralManager; +@property(nonatomic, strong) NSDictionary *cbOptions; @end @implementation TestGNSPeripheralManager - (CBPeripheralManager *)cbPeripheralManagerWithDelegate:(id)delegate queue:(dispatch_queue_t)queue - options:(NSDictionary *)options { + options:(NSDictionary *)options { _cbOptions = options; - return _cbPeripheralManagerMock; + self.fakeCBPeripheralManager = [[GNSFakePeripheralManager alloc] initWithDelegate:delegate + queue:queue + options:options]; + self.fakeCBPeripheralManager.delegate = delegate; + return (CBPeripheralManager *)self.fakeCBPeripheralManager; } @end @@ -39,162 +59,163 @@ TestGNSPeripheralManager *_peripheralManager; NSString *_displayName; NSString *_restoreIdentifier; - id _cbPeripheralManagerMock; - NSDictionary *_cbAdvertisementData; - CBPeripheralManagerState _cbPeripheralManagerState; - NSMutableArray *_mocksToVerify; - NSMutableDictionary *_cbServiceStatePerPeripheral; } @end @implementation GNSPeripheralManagerTest - (void)setUp { - _mocksToVerify = [NSMutableArray array]; - _cbServiceStatePerPeripheral = [NSMutableDictionary dictionary]; _displayName = @"DisplayName"; _restoreIdentifier = @"RestoreIdentifier"; - _cbPeripheralManagerMock = OCMStrictClassMock([CBPeripheralManager class]); - [_mocksToVerify addObject:_cbPeripheralManagerMock]; - OCMStub([_cbPeripheralManagerMock state]).andDo(^(NSInvocation *invocation) { - [invocation setReturnValue:&_cbPeripheralManagerState]; - }); - OCMStub([_cbPeripheralManagerMock isAdvertising]).andDo(^(NSInvocation *invocation) { - BOOL isAdvertising = _cbAdvertisementData != nil; - [invocation setReturnValue:&isAdvertising]; - }); - OCMStub([_cbPeripheralManagerMock stopAdvertising]).andDo(^(NSInvocation *invocation) { - _cbAdvertisementData = nil; - }); - OCMStub([_cbPeripheralManagerMock - startAdvertising:[OCMArg checkWithBlock:^BOOL(id obj) { - _cbAdvertisementData = obj; - [_peripheralManager peripheralManagerDidStartAdvertising:_cbPeripheralManagerMock - error:nil]; - return YES; - }]]); - _peripheralManager = [[TestGNSPeripheralManager alloc] - initWithAdvertisedName:_displayName - restoreIdentifier:_restoreIdentifier - queue:dispatch_get_main_queue()]; - _peripheralManager.cbPeripheralManagerMock = _cbPeripheralManagerMock; + _peripheralManager = + [[TestGNSPeripheralManager alloc] initWithAdvertisedName:_displayName + restoreIdentifier:_restoreIdentifier + queue:dispatch_get_main_queue()]; XCTAssertFalse(_peripheralManager.isStarted); } - (void)tearDown { if (_peripheralManager.isStarted) { - [self stopPeripheralManager]; + [_peripheralManager stop]; } if (_peripheralManager.cbPeripheralManager) { +#if TARGET_OS_IPHONE NSDictionary *expectedOptions = @{CBPeripheralManagerOptionRestoreIdentifierKey : _restoreIdentifier}; XCTAssertEqualObjects(expectedOptions, _peripheralManager.cbOptions); - OCMVerifyAll(_peripheralManager.cbPeripheralManagerMock); - } - for (id mock in _mocksToVerify) { - OCMVerifyAll(mock); +#endif } } +- (CBATTRequest *)requestWithCharacteristic:(CBCharacteristic *)characteristic { + id central = OCMClassMock([CBCentral class]); + OCMStub([central identifier]).andReturn([NSUUID UUID]); + id request = OCMClassMock([CBATTRequest class]); + OCMStub([request central]).andReturn(central); + OCMStub([request characteristic]).andReturn(characteristic); + return request; +} + +#pragma mark - Init + +- (void)testInitWithAdvertisedNameAndRestoreIdentifier { + _peripheralManager = [[TestGNSPeripheralManager alloc] initWithAdvertisedName:_displayName + restoreIdentifier:_restoreIdentifier]; + XCTAssertNotNil(_peripheralManager); + XCTAssertFalse(_peripheralManager.isStarted); +} + +- (void)testDescription { + NSString *description = [_peripheralManager description]; + XCTAssertTrue([description containsString:@"GNSPeripheralManager"]); + XCTAssertTrue([description containsString:@"started NO"]); + + [_peripheralManager start]; + description = [_peripheralManager description]; + XCTAssertTrue([description containsString:@"GNSPeripheralManager"]); + XCTAssertTrue([description containsString:@"started YES"]); +} + #pragma mark - Start/Stop - (void)startPeripheralManagerWithPeripheralManagerState:(CBPeripheralManagerState)state - expectedAdvertisementData:(NSDictionary *)expectedAdvertisement { + expectedAdvertisementData: + (NSDictionary *)expectedAdvertisement { [_peripheralManager start]; [self updatePeripheralManagerWithPeripheralManagerState:state]; [self checkAdvertisementData:expectedAdvertisement]; } -- (void)updatePeripheralManagerWithPeripheralManagerState:(CBPeripheralManagerState)state { - _cbPeripheralManagerState = state; - [_peripheralManager peripheralManagerDidUpdateState:_cbPeripheralManagerMock]; +- (void)testStartTwice { + [_peripheralManager start]; + XCTAssertTrue(_peripheralManager.isStarted); + [_peripheralManager start]; XCTAssertTrue(_peripheralManager.isStarted); } -- (void)checkAdvertisementData:(NSDictionary *)expectedAdvertisement { +- (void)updatePeripheralManagerWithPeripheralManagerState:(CBPeripheralManagerState)state { + _peripheralManager.fakeCBPeripheralManager.state = state; + [_peripheralManager peripheralManagerDidUpdateState:(CBPeripheralManager *)_peripheralManager + .fakeCBPeripheralManager]; + XCTAssertTrue(_peripheralManager.isStarted); +} + +- (void)checkAdvertisementData:(NSDictionary *)expectedAdvertisement { if (expectedAdvertisement) { XCTAssertEqualObjects(expectedAdvertisement[CBAdvertisementDataLocalNameKey], - _cbAdvertisementData[CBAdvertisementDataLocalNameKey]); - NSSet *expectedUUIDs = + _peripheralManager.fakeCBPeripheralManager + .advertisementData[CBAdvertisementDataLocalNameKey]); + NSSet *expectedUUIDs = [NSSet setWithArray:expectedAdvertisement[CBAdvertisementDataServiceUUIDsKey]]; - NSSet *advertisedUUIDs = - [NSSet setWithArray:_cbAdvertisementData[CBAdvertisementDataServiceUUIDsKey]]; + NSSet *advertisedUUIDs = + [NSSet setWithArray:_peripheralManager.fakeCBPeripheralManager + .advertisementData[CBAdvertisementDataServiceUUIDsKey]]; XCTAssertEqualObjects(expectedUUIDs, advertisedUUIDs); } else { - XCTAssertNil(_cbAdvertisementData); + XCTAssertNil(_peripheralManager.fakeCBPeripheralManager.advertisementData); } } - (void)updatePeripheralManagerWithPeripheralManagerState:(CBPeripheralManagerState)state - expectedAdvertisementData:(NSDictionary *)expectedAdvertisement { - _cbPeripheralManagerState = state; - [_peripheralManager peripheralManagerDidUpdateState:_cbPeripheralManagerMock]; + expectedAdvertisementData: + (NSDictionary *)expectedAdvertisement { + _peripheralManager.fakeCBPeripheralManager.state = state; + [_peripheralManager peripheralManagerDidUpdateState:(CBPeripheralManager *)_peripheralManager + .fakeCBPeripheralManager]; XCTAssertTrue(_peripheralManager.isStarted); if (expectedAdvertisement) { XCTAssertEqualObjects(expectedAdvertisement[CBAdvertisementDataLocalNameKey], - _cbAdvertisementData[CBAdvertisementDataLocalNameKey]); - NSSet *expectedUUIDs = + _peripheralManager.fakeCBPeripheralManager + .advertisementData[CBAdvertisementDataLocalNameKey]); + NSSet *expectedUUIDs = [NSSet setWithArray:expectedAdvertisement[CBAdvertisementDataServiceUUIDsKey]]; - NSSet *advertisedUUIDs = - [NSSet setWithArray:_cbAdvertisementData[CBAdvertisementDataServiceUUIDsKey]]; + NSSet *advertisedUUIDs = + [NSSet setWithArray:_peripheralManager.fakeCBPeripheralManager + .advertisementData[CBAdvertisementDataServiceUUIDsKey]]; XCTAssertEqualObjects(expectedUUIDs, advertisedUUIDs); } else { - XCTAssertNil(_cbAdvertisementData); + XCTAssertNil(_peripheralManager.fakeCBPeripheralManager.advertisementData); } } -- (void)stopPeripheralManager { - OCMExpect([_cbPeripheralManagerMock removeAllServices]); - OCMExpect([_cbPeripheralManagerMock setDelegate:nil]); - [_peripheralManager stop]; - XCTAssertNil(_cbAdvertisementData); - XCTAssertFalse(_peripheralManager.isStarted); -} - // Starts with bluetooth off. - (void)testStartWithNoServiceBluetoothResetting { [_peripheralManager start]; - OCMExpect([_peripheralManager.cbPeripheralManager removeAllServices]); [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateResetting]; + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.removeAllServicesCount, 1); [self checkAdvertisementData:nil]; } - (void)testStartWithNoServiceBluetoothUnknown { [_peripheralManager start]; - OCMExpect([_peripheralManager.cbPeripheralManager removeAllServices]); [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateUnknown]; + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.removeAllServicesCount, 1); [self checkAdvertisementData:nil]; } - (void)testStartWithNoServiceBluetoothUnauthorized { [_peripheralManager start]; - OCMExpect([_peripheralManager.cbPeripheralManager removeAllServices]); [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateUnauthorized]; + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.removeAllServicesCount, 1); [self checkAdvertisementData:nil]; } - (void)testStartWithNoServiceBluetoothUnsupported { - OCMStub([_cbPeripheralManagerMock isAdvertising]).andReturn(NO); - [self startPeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateUnsupported - expectedAdvertisementData:nil]; + [_peripheralManager start]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateUnsupported]; + [self checkAdvertisementData:nil]; } - (void)testStartWithNoServiceBluetoothOff { - NSDictionary *expectedAdvertisingData = @{ - CBAdvertisementDataServiceUUIDsKey : @[], - CBAdvertisementDataLocalNameKey : _displayName, - }; + NSDictionary *expectedAdvertisingData = nil; [self startPeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOff expectedAdvertisementData:expectedAdvertisingData]; } // Starts with bluetooth on: should advertise local name on BLE. - (void)testStartWithNoServiceBluetoothOn { - NSDictionary *expectedAdvertisingData = @{ - CBAdvertisementDataServiceUUIDsKey : @[], - CBAdvertisementDataLocalNameKey : _displayName, - }; + NSDictionary *expectedAdvertisingData = nil; [self startPeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn expectedAdvertisementData:expectedAdvertisingData]; } @@ -203,634 +224,436 @@ // turning on bluetooth. - (void)testStartWithBleStateUnknownAndTurnOnBluetooth { [_peripheralManager start]; - OCMExpect([_peripheralManager.cbPeripheralManager removeAllServices]); [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateUnknown]; [self checkAdvertisementData:nil]; + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.removeAllServicesCount, 1); [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn - expectedAdvertisementData:@{ - CBAdvertisementDataServiceUUIDsKey : @[], - CBAdvertisementDataLocalNameKey : _displayName, - }]; + expectedAdvertisementData:nil]; } - (void)testStartWithBleStateResettingAndTurnOnBluetooth { [_peripheralManager start]; - OCMExpect([_peripheralManager.cbPeripheralManager removeAllServices]); - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateUnknown]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateResetting]; [self checkAdvertisementData:nil]; + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.removeAllServicesCount, 1); [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn - expectedAdvertisementData:@{ - CBAdvertisementDataServiceUUIDsKey : @[], - CBAdvertisementDataLocalNameKey : _displayName, - }]; + expectedAdvertisementData:nil]; } #pragma mark - Restore -- (void)testRestore { +#if TARGET_OS_IPHONE +- (void)testRestoreState { [_peripheralManager start]; - GNSPeripheralServiceManager *service1 = - [self peripheralServiceManagerWithServiceState:GNSBluetoothServiceStateAdded]; - OCMStub([service1 isAdvertising]).andReturn(YES); - [_peripheralManager addPeripheralServiceManager:service1 bleServiceAddedCompletion:nil]; - GNSPeripheralServiceManager *service2 = - [self peripheralServiceManagerWithServiceState:GNSBluetoothServiceStateAdded]; - OCMStub([service2 isAdvertising]).andReturn(YES); - [_peripheralManager addPeripheralServiceManager:service2 bleServiceAddedCompletion:nil]; - CBMutableService *restoredCBService1 = OCMStrictClassMock([CBMutableService class]); - OCMStub([restoredCBService1 UUID]).andReturn(service1.serviceUUID); - CBMutableService *restoredCBService2 = OCMStrictClassMock([CBMutableService class]); - OCMStub([restoredCBService2 UUID]).andReturn(service2.serviceUUID); - NSDictionary *state = @{ - CBPeripheralManagerRestoredStateAdvertisementDataKey : @"Name", - CBPeripheralManagerRestoredStateServicesKey : @[ restoredCBService1, restoredCBService2 ], + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID]; + + XCTestExpectation *addServiceExp = [self expectationWithDescription:@"add service"]; + [_peripheralManager addPeripheralServiceManager:serviceManager + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + + CBMutableService *service = ((GNSPeripheralServiceManager *)serviceManager).cbService; + XCTAssertNotNil(service); + serviceManager.advertising = YES; + + NSDictionary *advertisementData = @{ + CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ], + CBAdvertisementDataLocalNameKey : _displayName }; - OCMExpect([service1 restoredCBService:restoredCBService1]); - OCMExpect([service2 restoredCBService:restoredCBService2]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock willRestoreState:state]; - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn]; - [self checkAdvertisementData:@{ - CBAdvertisementDataServiceUUIDsKey : @[ service1.serviceUUID, service2.serviceUUID ], - CBAdvertisementDataLocalNameKey : _displayName, - }]; + NSDictionary *restoreState = @{ + CBPeripheralManagerRestoredStateServicesKey : @[ service ], + CBPeripheralManagerRestoredStateAdvertisementDataKey : advertisementData + }; + [_peripheralManager + peripheralManager:(CBPeripheralManager *)_peripheralManager.fakeCBPeripheralManager + willRestoreState:restoreState]; + + XCTAssertTrue(serviceManager.restored); + [self checkAdvertisementData:nil]; + + _peripheralManager.fakeCBPeripheralManager.state = CBPeripheralManagerStatePoweredOn; + [_peripheralManager peripheralManagerDidUpdateState:(CBPeripheralManager *)_peripheralManager + .fakeCBPeripheralManager]; + [self checkAdvertisementData:nil]; } +#endif // TARGET_OS_IPHONE #pragma mark - Advertising -// Generates a peripheral service manager mock ready to be added to |_peripheralManager|. -- (GNSPeripheralServiceManager *)peripheralServiceManager { - return [self peripheralServiceManagerWithServiceState:GNSBluetoothServiceStateNotAdded]; -} - -// Generates a peripheral service manager mock |_peripheralManager| with the cbServiceState -// set to |initialServiceState|. -- (GNSPeripheralServiceManager *)peripheralServiceManagerWithServiceState: - (GNSBluetoothServiceState)initialServiceState { - CBUUID *uuid = [CBUUID UUIDWithNSUUID:[NSUUID UUID]]; - CBMutableService *cbService = OCMClassMock([CBMutableService class]); - OCMStub([cbService UUID]).andReturn(uuid); - [_mocksToVerify addObject:cbService]; - GNSPeripheralServiceManager *peripheralServiceManager = - OCMStrictClassMock([GNSPeripheralServiceManager class]); - [_mocksToVerify addObject:peripheralServiceManager]; - OCMStub([peripheralServiceManager cbService]).andReturn(cbService); - OCMStub([peripheralServiceManager serviceUUID]).andReturn(uuid); - OCMExpect([peripheralServiceManager addedToPeripheralManager:_peripheralManager - bleServiceAddedCompletion:[OCMArg any]]); - - __block GNSBluetoothServiceState cbServiceState = initialServiceState; - OCMStub([peripheralServiceManager cbServiceState]).andDo(^(NSInvocation *invocation) { - GNSBluetoothServiceState stateToReturn = cbServiceState; - return [invocation setReturnValue:&stateToReturn]; - }); - OCMStub([peripheralServiceManager willAddCBService]).andDo(^(NSInvocation *invocation) { - cbServiceState = GNSBluetoothServiceStateAddInProgress; - }); - - OCMStub([peripheralServiceManager didAddCBServiceWithError:[OCMArg any]]) - .andDo(^(NSInvocation *invocation) { - __unsafe_unretained NSError *error = nil; - [invocation getArgument:&error atIndex:2]; - if (!error) { - cbServiceState = GNSBluetoothServiceStateAdded; - } else { - cbServiceState = GNSBluetoothServiceStateNotAdded; - } - }); - OCMStub([peripheralServiceManager didRemoveCBService]).andDo(^(NSInvocation *invocation) { - cbServiceState = GNSBluetoothServiceStateNotAdded; - }); - return peripheralServiceManager; -} - -// Adds a peripheral service manager mock into |_peripheralManager|. -- (void)addPeripheralManager:(id)peripheralServiceManager { - CBMutableService *service = [peripheralServiceManager cbService]; - OCMExpect([_cbPeripheralManagerMock addService:service]); - [_peripheralManager addPeripheralServiceManager:peripheralServiceManager - bleServiceAddedCompletion:nil]; -} - -// Adds one peripheral service manager. -- (void)testAddOneServiceStartsAdvertisment { - GNSPeripheralServiceManager *peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - // Starting before the services are added should not start adverting. - [self addPeripheralManager:peripheralServiceManager]; +- (void)testAdvertisingWithOneService { [_peripheralManager start]; - XCTAssertEqual(GNSBluetoothServiceStateNotAdded, [peripheralServiceManager cbServiceState]); - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn]; - XCTAssertEqual(GNSBluetoothServiceStateAddInProgress, [peripheralServiceManager cbServiceState]); + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID]; + XCTestExpectation *addServiceExp = [self expectationWithDescription:@"add service"]; + [_peripheralManager addPeripheralServiceManager:serviceManager + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + serviceManager.advertising = YES; [self checkAdvertisementData:nil]; - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didAddService:[peripheralServiceManager cbService] - error:nil]; - XCTAssertEqual(GNSBluetoothServiceStateAdded, [peripheralServiceManager cbServiceState]); - - // Adding the service should start the advertisment. - [self checkAdvertisementData:@{ - CBAdvertisementDataServiceUUIDsKey : @[ [peripheralServiceManager serviceUUID] ], - CBAdvertisementDataLocalNameKey : _displayName, - }]; } -// Adds one peripheral service manager fails with error. -- (void)testAddOneServiceError { - GNSPeripheralServiceManager *peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self addPeripheralManager:peripheralServiceManager]; +- (void)testAdvertisingWithTwoServices { [_peripheralManager start]; - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn]; - NSError *expectedError = [[NSError alloc] initWithDomain:@"Test" code:-42 userInfo:nil]; - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didAddService:[peripheralServiceManager cbService] - error:expectedError]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; + + CBUUID *serviceUUID1 = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager1 = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID1]; + XCTestExpectation *addServiceExp1 = [self expectationWithDescription:@"add service 1"]; + [_peripheralManager addPeripheralServiceManager:serviceManager1 + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp1 fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + serviceManager1.advertising = YES; [self checkAdvertisementData:nil]; - XCTAssertEqual(GNSBluetoothServiceStateNotAdded, [peripheralServiceManager cbServiceState]); -} -// Adds two peripheral service managers, one that advertise itself, and one that doesn't. Only -// one service should be advertised. -- (void)testAddTwoServices { - GNSPeripheralServiceManager *peripheralServiceManager1 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager1 isAdvertising]).andReturn(NO); - [self addPeripheralManager:peripheralServiceManager1]; - GNSPeripheralServiceManager *peripheralServiceManager2 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager2 isAdvertising]).andReturn(YES); - [self addPeripheralManager:peripheralServiceManager2]; - [_peripheralManager start]; - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn]; - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didAddService:[peripheralServiceManager1 cbService] - error:nil]; - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didAddService:[peripheralServiceManager2 cbService] - error:nil]; - NSDictionary *expectedAdvertisement = @{ - CBAdvertisementDataServiceUUIDsKey : @[ [peripheralServiceManager2 serviceUUID] ], - CBAdvertisementDataLocalNameKey : _displayName, - }; - [self checkAdvertisementData:expectedAdvertisement]; -} - -// Adds one peripheral service manager while not advertising. Changes the advertising value for this -// peripheral service manager. -- (void)testAddServiceAndChangeAdvertisingValue { - GNSPeripheralServiceManager *peripheralServiceManager = [self peripheralServiceManager]; - OCMStubRecorder *isAdvertisingStub = OCMStub([peripheralServiceManager isAdvertising]); - isAdvertisingStub.andReturn(NO); - [self addPeripheralManager:peripheralServiceManager]; - [_peripheralManager start]; - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn]; + CBUUID *serviceUUID2 = [CBUUID UUIDWithString:@"FEF4"]; + GNSFakePeripheralServiceManager *serviceManager2 = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID2]; + XCTestExpectation *addServiceExp2 = [self expectationWithDescription:@"add service 2"]; + [_peripheralManager addPeripheralServiceManager:serviceManager2 + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp2 fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + serviceManager2.advertising = YES; [self checkAdvertisementData:nil]; - XCTAssertEqual(GNSBluetoothServiceStateAddInProgress, [peripheralServiceManager cbServiceState]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didAddService:[peripheralServiceManager cbService] - error:nil]; - [self checkAdvertisementData:@{ - CBAdvertisementDataServiceUUIDsKey : @[], - CBAdvertisementDataLocalNameKey : _displayName, - }]; - isAdvertisingStub.andReturn(YES); - [_peripheralManager updateAdvertisedServices]; - [self checkAdvertisementData:@{ - CBAdvertisementDataServiceUUIDsKey : @[ [peripheralServiceManager serviceUUID] ], - CBAdvertisementDataLocalNameKey : _displayName, - }]; - return; } -- (void)startWithPeripheralServiceManagers:(NSArray *)managers { - NSMutableArray *advertisedServiceUUIDs = [NSMutableArray array]; - for (GNSPeripheralServiceManager *peripheralServiceManager in managers) { - [self addPeripheralManager:peripheralServiceManager]; - if (peripheralServiceManager.isAdvertising) { - [advertisedServiceUUIDs addObject:peripheralServiceManager.serviceUUID]; - } - } +- (void)testStopAdvertisingService { [_peripheralManager start]; - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn]; - for (GNSPeripheralServiceManager *peripheralServiceManager in managers) { - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didAddService:[peripheralServiceManager cbService] - error:nil]; - } - [self checkAdvertisementData:@{ - CBAdvertisementDataServiceUUIDsKey : advertisedServiceUUIDs, - CBAdvertisementDataLocalNameKey : _displayName, - }]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID]; + XCTestExpectation *addServiceExp = [self expectationWithDescription:@"add service"]; + [_peripheralManager addPeripheralServiceManager:serviceManager + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + serviceManager.advertising = YES; + [self checkAdvertisementData:nil]; + + serviceManager.advertising = NO; + [self checkAdvertisementData:nil]; } #pragma mark - Subscribe/unsubscribe characteristics -// Creates a characteristic mock, and adds it into |_mocksToVerify|. If no service is provided, -// a service mock is created (added into |_mocksToVerify| too). The service uuid is attach -// to the service. If no service uuid is provided, an uuid is created. -- (CBMutableCharacteristic *)prepareCharacteristicForCBService:(CBService *)cbService - withServiceUUID:(CBUUID *)serviceUUID { - if (!serviceUUID) { - serviceUUID = [CBUUID UUIDWithNSUUID:[NSUUID UUID]]; - } - if (!cbService) { - cbService = OCMClassMock([CBService class]); - OCMStub([cbService UUID]).andReturn(serviceUUID); - [_mocksToVerify addObject:cbService]; - } - CBMutableCharacteristic *characteristicMock = OCMClassMock([CBMutableCharacteristic class]); - OCMStub([characteristicMock service]).andReturn(cbService); - [_mocksToVerify addObject:characteristicMock]; - return characteristicMock; +- (void)testSubscribeUnsubscribe { + [_peripheralManager start]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID]; + XCTestExpectation *addServiceExp = [self expectationWithDescription:@"add service"]; + [_peripheralManager addPeripheralServiceManager:serviceManager + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + + CBCentral *central = OCMClassMock([CBCentral class]); + OCMStub(central.identifier).andReturn([NSUUID UUID]); + CBCharacteristic *characteristic = + ((GNSPeripheralServiceManager *)serviceManager).cbService.characteristics[0]; + [_peripheralManager + peripheralManager:(CBPeripheralManager *)_peripheralManager.fakeCBPeripheralManager + central:central + didSubscribeToCharacteristic:characteristic]; + XCTAssertEqual(serviceManager.subscribedCentrals.count, 1); + XCTAssertTrue([serviceManager.subscribedCentrals containsObject:central]); + + [_peripheralManager peripheralManager:(CBPeripheralManager *) + _peripheralManager.fakeCBPeripheralManager + central:central + didUnsubscribeFromCharacteristic:characteristic]; + XCTAssertEqual(serviceManager.subscribedCentrals.count, 0); } -// Creates a characteristic mock based on the service from the peripheral service manager. -// The mock is added into _mocksToVerify. -- (CBMutableCharacteristic *)prepareCharacteristicForPeripheralServiceManager: - (GNSPeripheralServiceManager *)peripheralServiceManager { - CBService *cbService = peripheralServiceManager.cbService; - CBUUID *serviceUUID = cbService.UUID; - return [self prepareCharacteristicForCBService:cbService withServiceUUID:serviceUUID]; +- (void)testRemovePeripheralServiceManager { + [_peripheralManager start]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID]; + XCTestExpectation *addServiceExp = [self expectationWithDescription:@"add service"]; + [_peripheralManager addPeripheralServiceManager:serviceManager + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.services.count, 1); + + XCTestExpectation *removeServiceExp = [self expectationWithDescription:@"remove service"]; + [_peripheralManager removePeripheralServiceManagerForServiceUUID:serviceUUID + bleServiceRemovedCompletion:^(NSError *error) { + XCTAssertNil(error); + [removeServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.services.count, 0); } -// Adds a peripheral service manager, and a central subscribes to its service. -- (void)testCentralDidSubscribe { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - id centralMock = OCMClassMock([CBCentral class]); - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager]; - OCMExpect([peripheralServiceManager central:centralMock - didSubscribeToCharacteristic:characteristicMock]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - central:centralMock - didSubscribeToCharacteristic:characteristicMock]; - OCMVerifyAll(centralMock); +- (void)testRemoveNonExistingPeripheralServiceManager { + [_peripheralManager start]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; + + XCTestExpectation *removeServiceExp = [self expectationWithDescription:@"remove service"]; + [_peripheralManager removePeripheralServiceManagerForServiceUUID:[CBUUID UUIDWithString:@"FEF3"] + bleServiceRemovedCompletion:^(NSError *error) { + XCTAssertNil(error); + [removeServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; } -// Adds a peripheral service manager, and a central unsubscribes to its service. -- (void)testCentralDidUnsubscribe { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - id centralMock = OCMClassMock([CBCentral class]); - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager]; - OCMExpect([peripheralServiceManager central:centralMock - didUnsubscribeFromCharacteristic:characteristicMock]); +#pragma mark - Update outgoing characteristic - // As a workaourd for b/31752176 We are restarting the peripheral manager after the central - // unsubscribe. - OCMExpect([_cbPeripheralManagerMock removeAllServices]); - OCMExpect([_cbPeripheralManagerMock setDelegate:nil]); +- (void)testUpdateOutgoingCharWithHandlerReturningYes { + id socketMock = OCMClassMock([GNSSocket class]); + NSUUID *socketID = [NSUUID UUID]; + OCMStub([socketMock socketIdentifier]).andReturn(socketID); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - central:centralMock - didUnsubscribeFromCharacteristic:characteristicMock]; - OCMVerifyAll(centralMock); -} - -#pragma mark - Characteristic requests - -// Creates an ATT request mock based on the characteristic. The mock is added into |_mocksToVerify| -- (CBATTRequest *)prepareRequestForCharacteristic:(CBMutableCharacteristic *)characteristic { - id requestMock = OCMClassMock([CBATTRequest class]); - OCMStub([requestMock characteristic]).andReturn(characteristic); - [_mocksToVerify addObject:requestMock]; - return requestMock; -} - -// Adds a peripheral service manager, and receives a read request accepted by the manager. The -// read request should be processed. -- (void)testDidReceiveReadRequest { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager]; - CBATTRequest *readRequestMock = [self prepareRequestForCharacteristic:characteristicMock]; - OCMStub([peripheralServiceManager canProcessReadRequest:readRequestMock]) - .andReturn(CBATTErrorSuccess); - OCMExpect([peripheralServiceManager processReadRequest:readRequestMock]); - OCMExpect( - [_cbPeripheralManagerMock respondToRequest:readRequestMock withResult:CBATTErrorSuccess]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveReadRequest:readRequestMock]; -} - -// Adds a peripheral service manager, and receives a read request refused by the manager. The -// read request should not be processed. -- (void)testDidReceiveBadReadRequest { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager]; - CBATTRequest *readRequestMock = [self prepareRequestForCharacteristic:characteristicMock]; - OCMStub([peripheralServiceManager canProcessReadRequest:readRequestMock]) - .andReturn(CBATTErrorReadNotPermitted); - OCMExpect([_cbPeripheralManagerMock respondToRequest:readRequestMock - withResult:CBATTErrorReadNotPermitted]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveReadRequest:readRequestMock]; -} - -// Adds a peripheral service manager, and receives a read request to another service. The read -// request should not be processed. -- (void)testDidReceiveReadRequestToWrongService { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForCBService:nil withServiceUUID:nil]; - CBATTRequest *readRequestMock = [self prepareRequestForCharacteristic:characteristicMock]; - OCMExpect([_cbPeripheralManagerMock respondToRequest:readRequestMock - withResult:CBATTErrorAttributeNotFound]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveReadRequest:readRequestMock]; -} - -// Adds a peripheral service manager, and receives a write request accepted by the manager. The -// write request should be processed. -- (void)testDidReceiveOneWriteRequest { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager]; - CBATTRequest *writeRequestMock = [self prepareRequestForCharacteristic:characteristicMock]; - OCMStub([peripheralServiceManager canProcessWriteRequest:writeRequestMock]) - .andReturn(CBATTErrorSuccess); - OCMExpect([peripheralServiceManager processWriteRequest:writeRequestMock]); - OCMExpect( - [_cbPeripheralManagerMock respondToRequest:writeRequestMock withResult:CBATTErrorSuccess]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveWriteRequests:@[ writeRequestMock ]]; -} - -// Adds a peripheral service manager, and receives a write request refused by the manager. The write -// request should not be processed. -- (void)testDidReceiveOneBadWriteRequest { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager]; - CBATTRequest *writeRequestMock = [self prepareRequestForCharacteristic:characteristicMock]; - OCMStub([peripheralServiceManager canProcessWriteRequest:writeRequestMock]) - .andReturn(CBATTErrorWriteNotPermitted); - OCMExpect([_cbPeripheralManagerMock respondToRequest:writeRequestMock - withResult:CBATTErrorWriteNotPermitted]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveWriteRequests:@[ writeRequestMock ]]; -} - -// Adds a peripheral service manager, and receives a write request to another service. The write -// request should not be processed. -- (void)testDidReceiveOneWriteRequestToWrongService { - id peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - CBMutableCharacteristic *characteristicMock = - [self prepareCharacteristicForCBService:nil withServiceUUID:nil]; - CBATTRequest *writeRequestMock = [self prepareRequestForCharacteristic:characteristicMock]; - OCMStub([_cbPeripheralManagerMock respondToRequest:writeRequestMock - withResult:CBATTErrorAttributeNotFound]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveWriteRequests:@[ writeRequestMock ]]; -} - -// Adds a peripheral service manager, and receives two write requests, only the second one is -// refused by the manager. None of those write requests should be processed. The error should be -// sent to the first write request. -- (void)testDidReceiveWriteRequestOneGoodAndOneBad { - id peripheralServiceManager1 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager1 isAdvertising]).andReturn(YES); - id peripheralServiceManager2 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager2 isAdvertising]).andReturn(YES); - [self - startWithPeripheralServiceManagers:@[ peripheralServiceManager1, peripheralServiceManager2 ]]; - // Good request - CBMutableCharacteristic *characteristic1Mock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager1]; - CBATTRequest *writeRequest1Mock = [self prepareRequestForCharacteristic:characteristic1Mock]; - OCMStub([peripheralServiceManager1 canProcessWriteRequest:writeRequest1Mock]) - .andReturn(CBATTErrorSuccess); - // Bad request - CBMutableCharacteristic *characteristic2Mock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager2]; - CBATTRequest *writeRequest2Mock = [self prepareRequestForCharacteristic:characteristic2Mock]; - OCMStub([peripheralServiceManager2 canProcessWriteRequest:writeRequest2Mock]) - .andReturn(CBATTErrorWriteNotPermitted); - OCMExpect([_cbPeripheralManagerMock respondToRequest:writeRequest1Mock - withResult:CBATTErrorWriteNotPermitted]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveWriteRequests:@[ writeRequest1Mock, writeRequest2Mock ]]; -} - -// Adds a peripheral service manager, and receives two write requests, only the first one is -// refused by the manager. None of those write requests should be processed. The error should be -// sent to the first write request. -- (void)testDidReceiveWriteRequestOneBadAndOneGood { - id peripheralServiceManager1 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager1 isAdvertising]).andReturn(YES); - id peripheralServiceManager2 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager2 isAdvertising]).andReturn(YES); - [self - startWithPeripheralServiceManagers:@[ peripheralServiceManager1, peripheralServiceManager2 ]]; - // Good request - CBMutableCharacteristic *characteristic1Mock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager1]; - CBATTRequest *writeRequest1Mock = [self prepareRequestForCharacteristic:characteristic1Mock]; - OCMStub([peripheralServiceManager1 canProcessWriteRequest:writeRequest1Mock]) - .andReturn(CBATTErrorWriteNotPermitted); - // Bad request - CBMutableCharacteristic *characteristic2Mock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager2]; - CBATTRequest *writeRequest2Mock = [self prepareRequestForCharacteristic:characteristic2Mock]; - OCMStub([peripheralServiceManager2 canProcessWriteRequest:writeRequest2Mock]) - .andReturn(CBATTErrorSuccess); - OCMExpect([_cbPeripheralManagerMock respondToRequest:writeRequest1Mock - withResult:CBATTErrorWriteNotPermitted]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveWriteRequests:@[ writeRequest1Mock, writeRequest2Mock ]]; -} - -// Adds a peripheral service manager, and receives two good write requests. Both write request -// should be processed. The success should be sent to the first write request. -- (void)testDidReceiveWriteRequestTwoGood { - id peripheralServiceManager1 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager1 isAdvertising]).andReturn(YES); - id peripheralServiceManager2 = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager2 isAdvertising]).andReturn(YES); - [self - startWithPeripheralServiceManagers:@[ peripheralServiceManager1, peripheralServiceManager2 ]]; - // Good request - CBMutableCharacteristic *characteristic1Mock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager1]; - CBATTRequest *writeRequest1Mock = [self prepareRequestForCharacteristic:characteristic1Mock]; - OCMStub([peripheralServiceManager1 canProcessWriteRequest:writeRequest1Mock]) - .andReturn(CBATTErrorSuccess); - OCMExpect([peripheralServiceManager1 processWriteRequest:writeRequest1Mock]); - // Good request - CBMutableCharacteristic *characteristic2Mock = - [self prepareCharacteristicForPeripheralServiceManager:peripheralServiceManager2]; - CBATTRequest *writeRequest2Mock = [self prepareRequestForCharacteristic:characteristic2Mock]; - OCMStub([peripheralServiceManager2 canProcessWriteRequest:writeRequest2Mock]) - .andReturn(CBATTErrorSuccess); - OCMExpect([peripheralServiceManager2 processWriteRequest:writeRequest2Mock]); - OCMExpect( - [_cbPeripheralManagerMock respondToRequest:writeRequest1Mock withResult:CBATTErrorSuccess]); - [_peripheralManager peripheralManager:_cbPeripheralManagerMock - didReceiveWriteRequests:@[ writeRequest1Mock, writeRequest2Mock ]]; -} - -- (GNSSocket *)createSocketMock { - GNSSocket *socketMock = OCMStrictClassMock([GNSSocket class]); - NSUUID *socketIdentifier = [NSUUID UUID]; - OCMStub([socketMock socketIdentifier]).andReturn(socketIdentifier); - [_mocksToVerify addObject:socketMock]; - return socketMock; -} - -// Adds one update value handler. Should be called right now. -- (void)testIsReadyToUpdateSubscribers { - __block BOOL updateValueHandlerCalled = NO; - GNSSocket *socketMock = [self createSocketMock]; - // Should be called right now. + __block int handlerCallCount = 0; [_peripheralManager updateOutgoingCharOnSocket:socketMock - withHandler:^() { - updateValueHandlerCalled = YES; - return GNSOutgoingCharUpdateNoReschedule; + withHandler:^BOOL { + handlerCallCount++; + return YES; }]; - XCTAssertTrue(updateValueHandlerCalled); + + XCTAssertEqual(handlerCallCount, 1); + XCTAssertNil(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID]); } -// Adds one update value handler. The first one fails. Adds a second update value. It should not -// be called. The first one is set to not fail anymore. When the CB peripheral manager is ready -// to update subscribers, both handlers should be called. -- (void)testIsReadyToUpdateSubscriberBlocked { - __block BOOL updateValueHandler1Called = NO; - __block GNSOutgoingCharUpdate updateValueHandler1ReturnedValue = - GNSOutgoingCharUpdateScheduleLater; - GNSSocket *socketMock = [self createSocketMock]; - // Should be called right now. - [_peripheralManager updateOutgoingCharOnSocket:socketMock - withHandler:^() { - updateValueHandler1Called = YES; - return updateValueHandler1ReturnedValue; - }]; - XCTAssertTrue(updateValueHandler1Called); - __block BOOL updateValueHandler2Called = NO; - __block GNSOutgoingCharUpdate updateValueHandler2ReturnedValue = - GNSOutgoingCharUpdateNoReschedule; - // Should no be called since the previous update value handler failed. - [_peripheralManager updateOutgoingCharOnSocket:socketMock - withHandler:^() { - updateValueHandler2Called = YES; - return updateValueHandler2ReturnedValue; - }]; - XCTAssertFalse(updateValueHandler2Called); - updateValueHandler1ReturnedValue = GNSOutgoingCharUpdateNoReschedule; - updateValueHandler1Called = NO; - // Should again again the first handler, and the second right after. - [_peripheralManager peripheralManagerIsReadyToUpdateSubscribers:_cbPeripheralManagerMock]; - XCTAssertTrue(updateValueHandler1Called); - XCTAssertTrue(updateValueHandler2Called); +- (void)testUpdateOutgoingCharWithHandlerReturningNo { + id socketMock = OCMClassMock([GNSSocket class]); + NSUUID *socketID = [NSUUID UUID]; + OCMStub([socketMock socketIdentifier]).andReturn(socketID); + + __block int handlerCallCount = 0; + GNSUpdateValueHandler handler = ^BOOL { + handlerCallCount++; + return NO; + }; + [_peripheralManager updateOutgoingCharOnSocket:socketMock withHandler:handler]; + + XCTAssertEqual(handlerCallCount, 1); + XCTAssertEqual(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID].count, 1); } -// Adds 2 handlers on different sockets. One fails, the second one should process right away. -- (void)testTwoUpdateBlocksOnDifferentSocket { - __block BOOL updateValueHandler1Called = NO; - __block GNSOutgoingCharUpdate updateValueHandler1ReturnedValue = - GNSOutgoingCharUpdateScheduleLater; - GNSSocket *socketMock1 = [self createSocketMock]; - // Should be called right now. - [_peripheralManager updateOutgoingCharOnSocket:socketMock1 - withHandler:^() { - updateValueHandler1Called = YES; - return updateValueHandler1ReturnedValue; - }]; - XCTAssertTrue(updateValueHandler1Called); - __block BOOL updateValueHandler2Called = NO; - __block GNSOutgoingCharUpdate updateValueHandler2ReturnedValue = - GNSOutgoingCharUpdateNoReschedule; - GNSSocket *socketMock2 = [self createSocketMock]; - // Should no be called since the previous update value handler failed. - [_peripheralManager updateOutgoingCharOnSocket:socketMock2 - withHandler:^() { - updateValueHandler2Called = YES; - return updateValueHandler2ReturnedValue; - }]; - XCTAssertTrue(updateValueHandler2Called); - updateValueHandler2Called = NO; - updateValueHandler1ReturnedValue = GNSOutgoingCharUpdateNoReschedule; - updateValueHandler1Called = NO; - // Should again again the first handler, and the second one should not be called. - [_peripheralManager peripheralManagerIsReadyToUpdateSubscribers:_cbPeripheralManagerMock]; - XCTAssertTrue(updateValueHandler1Called); - XCTAssertFalse(updateValueHandler2Called); +- (void)testUpdateOutgoingCharQueueExists { + id socketMock = OCMClassMock([GNSSocket class]); + NSUUID *socketID = [NSUUID UUID]; + OCMStub([socketMock socketIdentifier]).andReturn(socketID); + + __block int handlerCallCount = 0; + GNSUpdateValueHandler handler = ^BOOL { + handlerCallCount++; + return NO; + }; + [_peripheralManager updateOutgoingCharOnSocket:socketMock withHandler:handler]; + [_peripheralManager updateOutgoingCharOnSocket:socketMock withHandler:handler]; + + XCTAssertEqual(handlerCallCount, 1); + XCTAssertEqual(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID].count, 2); } -- (void)testUpdateBlockCleanupAfterDisconnect { - __block BOOL updateValueHandlerCalled = NO; - GNSSocket *socketMock = [self createSocketMock]; - __block GNSOutgoingCharUpdate outgoingCharUpdateValue = GNSOutgoingCharUpdateScheduleLater; - // Should be called right now. - [_peripheralManager updateOutgoingCharOnSocket:socketMock - withHandler:^() { - updateValueHandlerCalled = YES; - return outgoingCharUpdateValue; - }]; - XCTAssertTrue(updateValueHandlerCalled); - updateValueHandlerCalled = NO; - outgoingCharUpdateValue = GNSOutgoingCharUpdateNoReschedule; +- (void)testPeripheralManagerIsReadyToUpdateSubscribers { + id socketMock = OCMClassMock([GNSSocket class]); + NSUUID *socketID = [NSUUID UUID]; + OCMStub([socketMock socketIdentifier]).andReturn(socketID); + + __block int handler1CallCount = 0; + __block BOOL handler1ReturnValue = NO; + GNSUpdateValueHandler handler1 = ^BOOL { + handler1CallCount++; + return handler1ReturnValue; + }; + [_peripheralManager updateOutgoingCharOnSocket:socketMock withHandler:handler1]; + XCTAssertEqual(handler1CallCount, 1); + XCTAssertEqual(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID].count, 1); + + __block int handler2CallCount = 0; + GNSUpdateValueHandler handler2 = ^BOOL { + handler2CallCount++; + return YES; + }; + [_peripheralManager updateOutgoingCharOnSocket:socketMock withHandler:handler2]; + XCTAssertEqual(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID].count, 2); + + // When peripheralManagerIsReadyToUpdateSubscribers is called, handler1 is called again. + // It returns NO, so handler2 should not be called, and handler1 should remain in queue. + [_peripheralManager + peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *) + _peripheralManager.fakeCBPeripheralManager]; + XCTAssertEqual(handler1CallCount, 2); + XCTAssertEqual(handler2CallCount, 0); + XCTAssertEqual(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID].count, 2); + + // Set handler1 to return YES. When peripheralManagerIsReadyToUpdateSubscribers is called, + // handler1 is called, returns YES, and is removed. Then handler2 is called, returns YES, + // and is removed. The queue should be empty. + handler1ReturnValue = YES; + [_peripheralManager + peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *) + _peripheralManager.fakeCBPeripheralManager]; + XCTAssertEqual(handler1CallCount, 3); + XCTAssertEqual(handler2CallCount, 1); + XCTAssertNil(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID]); +} + +#pragma mark - CBPeripheralManagerDelegate callbacks + +- (void)testPeripheralManagerDidStartAdvertisingWithError { + _peripheralManager->_advertisementInProgressData = @{@"key" : @"value"}; + NSError *error = [NSError errorWithDomain:@"test" code:0 userInfo:nil]; + [_peripheralManager + peripheralManagerDidStartAdvertising:(CBPeripheralManager *) + _peripheralManager.fakeCBPeripheralManager + error:error]; + XCTAssertNil(_peripheralManager->_advertisementInProgressData); + XCTAssertNil(_peripheralManager->_advertisementData); +} + +- (void)testPeripheralManagerDidStartAdvertisingNoError { + NSDictionary *advertisementData = @{ + CBAdvertisementDataServiceUUIDsKey : @[ [CBUUID UUIDWithString:@"FEF3"] ], + CBAdvertisementDataLocalNameKey : _displayName + }; + _peripheralManager->_advertisementInProgressData = advertisementData; + _peripheralManager.fakeCBPeripheralManager.advertising = YES; + + id peripheralManagerMock = OCMPartialMock(_peripheralManager); + OCMExpect([peripheralManagerMock updateAdvertisedServices]); + + [_peripheralManager + peripheralManagerDidStartAdvertising:(CBPeripheralManager *) + _peripheralManager.fakeCBPeripheralManager + error:nil]; + + XCTAssertNil(_peripheralManager->_advertisementInProgressData); + XCTAssertEqualObjects(_peripheralManager->_advertisementData, advertisementData); + OCMVerifyAll(peripheralManagerMock); +} + +- (void)testSocketDidDisconnect { + id socketMock = OCMClassMock([GNSSocket class]); + NSUUID *socketID = [NSUUID UUID]; + OCMStub([socketMock socketIdentifier]).andReturn(socketID); OCMStub([socketMock isConnected]).andReturn(NO); + + __block int handler1CallCount = 0; + GNSUpdateValueHandler handler1 = ^BOOL { + handler1CallCount++; + return NO; + }; + [_peripheralManager updateOutgoingCharOnSocket:socketMock withHandler:handler1]; + XCTAssertEqual(handler1CallCount, 1); + XCTAssertEqual(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID].count, 1); + + __block int handler2CallCount = 0; + GNSUpdateValueHandler handler2 = ^BOOL { + handler2CallCount++; + return YES; + }; + [_peripheralManager updateOutgoingCharOnSocket:socketMock withHandler:handler2]; + XCTAssertEqual(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID].count, 2); + XCTAssertEqual(handler2CallCount, 0); // handler2 shouldn't be called here. + [_peripheralManager socketDidDisconnect:socketMock]; - // Should again again the first handler, and the second right after. - [_peripheralManager peripheralManagerIsReadyToUpdateSubscribers:_cbPeripheralManagerMock]; - XCTAssertTrue(updateValueHandlerCalled); + + XCTAssertEqual(handler1CallCount, 2); + XCTAssertEqual(handler2CallCount, 1); + XCTAssertNil(_peripheralManager->_handlerQueuePerSocketIdentifier[socketID]); } -- (void)testUpdateValueForCentral { - NSData *data = [NSData data]; - id characteristicMock = OCMStrictClassMock([CBMutableCharacteristic class]); - id centralMock = OCMStrictClassMock([CBCentral class]); - OCMStub([_cbPeripheralManagerMock updateValue:data - forCharacteristic:characteristicMock - onSubscribedCentrals:@[ centralMock ]]); - GNSPeripheralServiceManager *peripheralServiceManager = - OCMStrictClassMock([GNSPeripheralServiceManager class]); - OCMStub([peripheralServiceManager weaveOutgoingCharacteristic]).andReturn(characteristicMock); - GNSSocket *socketMock = OCMStrictClassMock([GNSSocket class]); - OCMStub([socketMock owner]).andReturn(peripheralServiceManager); - OCMStub([socketMock peerAsCentral]).andReturn(centralMock); - [_peripheralManager updateOutgoingCharacteristic:data onSocket:socketMock]; - OCMVerifyAll(characteristicMock); - OCMVerifyAll(centralMock); +#pragma mark - Did receive write requests + +- (void)testDidReceiveWriteRequestsSuccess { + [_peripheralManager start]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; + + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID]; + id serviceManagerMock = OCMPartialMock(serviceManager); + + XCTestExpectation *addServiceExp = [self expectationWithDescription:@"add service"]; + [_peripheralManager addPeripheralServiceManager:serviceManager + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + + CBATTRequest *request = + [self requestWithCharacteristic:serviceManager.cbService.characteristics[0]]; + OCMExpect([serviceManagerMock canProcessWriteRequest:request]).andReturn(CBATTErrorSuccess); + OCMExpect([serviceManagerMock processWriteRequest:request]); + + [_peripheralManager + peripheralManager:(CBPeripheralManager *)_peripheralManager.fakeCBPeripheralManager + didReceiveWriteRequests:@[ request ]]; + + XCTAssertEqualObjects(_peripheralManager.fakeCBPeripheralManager.lastRespondRequest, request); + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.lastRespondResult, CBATTErrorSuccess); + OCMVerifyAll(serviceManagerMock); } -- (void)testRecoverFromBTCrashLoop { - GNSPeripheralServiceManager *peripheralServiceManager = [self peripheralServiceManager]; - OCMStub([peripheralServiceManager isAdvertising]).andReturn(YES); - [self startWithPeripheralServiceManagers:@[ peripheralServiceManager ]]; - for (int i = 0; i < 5; ++i) { - OCMExpect([_cbPeripheralManagerMock removeAllServices]); - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateResetting]; - CBMutableService *service = [peripheralServiceManager cbService]; - OCMExpect([_cbPeripheralManagerMock addService:service]); - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOff]; - XCTAssertEqual(GNSBluetoothServiceStateAddInProgress, - [peripheralServiceManager cbServiceState]); - } +- (void)testDidReceiveWriteRequestsFailure { + [_peripheralManager start]; + [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOn + expectedAdvertisementData:nil]; - // After 5 consecutive resetting state, the services are no longer added by the peripheral - // manager. - OCMExpect([_cbPeripheralManagerMock removeAllServices]); - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStateResetting]; - [self updatePeripheralManagerWithPeripheralManagerState:CBPeripheralManagerStatePoweredOff]; - XCTAssertEqual(GNSBluetoothServiceStateNotAdded, [peripheralServiceManager cbServiceState]); + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"FEF3"]; + GNSFakePeripheralServiceManager *serviceManager = + [[GNSFakePeripheralServiceManager alloc] initWithServiceUUID:serviceUUID]; + id serviceManagerMock = OCMPartialMock(serviceManager); + + XCTestExpectation *addServiceExp = [self expectationWithDescription:@"add service"]; + [_peripheralManager addPeripheralServiceManager:serviceManager + bleServiceAddedCompletion:^(NSError *error) { + [addServiceExp fulfill]; + }]; + [self waitForExpectationsWithTimeout:1.0 handler:nil]; + + CBATTRequest *request = + [self requestWithCharacteristic:serviceManager.cbService.characteristics[0]]; + OCMExpect([serviceManagerMock canProcessWriteRequest:request]) + .andReturn(CBATTErrorRequestNotSupported); + OCMReject([serviceManagerMock processWriteRequest:request]); + + [_peripheralManager + peripheralManager:(CBPeripheralManager *)_peripheralManager.fakeCBPeripheralManager + didReceiveWriteRequests:@[ request ]]; + + XCTAssertEqualObjects(_peripheralManager.fakeCBPeripheralManager.lastRespondRequest, request); + XCTAssertEqual(_peripheralManager.fakeCBPeripheralManager.lastRespondResult, + CBATTErrorRequestNotSupported); + OCMVerifyAll(serviceManagerMock); } @end