diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/BUILD b/internal/platform/implementation/apple/Mediums/BLE/Tests/BUILD index 1fa33571..a79b9042 100644 --- a/internal/platform/implementation/apple/Mediums/BLE/Tests/BUILD +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/BUILD @@ -37,6 +37,7 @@ objc_library( "GNCFakeCentralManager.m", "GNCFakePeripheral.m", "GNCFakePeripheralManager.m", + "GNCMFakeConnection.mm", "NSData+GNCBase85Test.m", "NSData+GNCWebSafeBase64Test.m", ], @@ -49,6 +50,7 @@ objc_library( "GNCFakeCentralManager.h", "GNCFakePeripheral.h", "GNCFakePeripheralManager.h", + "GNCMFakeConnection.h", ], deps = [ "//internal/platform/implementation:comm", diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMFakeConnection.h b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMFakeConnection.h new file mode 100644 index 00000000..24e87666 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMFakeConnection.h @@ -0,0 +1,34 @@ +// 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/BLE/GNCMConnection.h" + +#import + +#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTCharacteristic.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h" + +/** A fake implementation of @c GNCMConnection for testing. */ +@interface GNCMFakeConnection : NSObject + +@property(nonatomic, readwrite) BOOL isConnected; +@property(nonatomic, readwrite) NSInteger maxDataLength; +@property(nonatomic, readwrite) GNCBLEGATTCharacteristic *characteristic; +@property(nonatomic, readwrite) id peripheral; +@property(nonatomic, readwrite) GNCMConnectionHandlers *connectionHandlers; + +- (void)simulateDataReceived:(NSData *)data; +- (void)simulateDisconnect; + +@end diff --git a/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMFakeConnection.mm b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMFakeConnection.mm new file mode 100644 index 00000000..36eed564 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMFakeConnection.mm @@ -0,0 +1,64 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not us/e 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/BLE/Tests/GNCMFakeConnection.h" + +#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTCharacteristic.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCMConnection.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h" + +@implementation GNCMFakeConnection +@synthesize isConnected = _isConnected; +@synthesize maxDataLength = _maxDataLength; +@synthesize characteristic = _characteristic; +@synthesize peripheral = _peripheral; +@synthesize connectionHandlers = _connectionHandlers; + +- (instancetype)init { + self = [super init]; + if (self) { + _isConnected = YES; + _maxDataLength = 20; + } + return self; +} + +- (void)disconnect { + _isConnected = NO; +} + +- (void)sendData:(NSData *)payload + progressHandler:(GNCMProgressHandler)progressHandler + completion:(GNCMPayloadResultHandler)completion { + if (completion) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + completion(GNCMPayloadResultSuccess); + }); + } +} + +- (void)simulateDataReceived:(NSData *)data { + if (_connectionHandlers && _connectionHandlers.payloadHandler) { + _connectionHandlers.payloadHandler(data); + } +} + +- (void)simulateDisconnect { + _isConnected = NO; + if (_connectionHandlers && _connectionHandlers.disconnectedHandler) { + _connectionHandlers.disconnectedHandler(); + } +} + +@end diff --git a/internal/platform/implementation/apple/Tests/BUILD b/internal/platform/implementation/apple/Tests/BUILD index 32fc8778..8c680b98 100644 --- a/internal/platform/implementation/apple/Tests/BUILD +++ b/internal/platform/implementation/apple/Tests/BUILD @@ -37,6 +37,7 @@ objc_library( "GNCWifiLanMediumTest.mm", "UtilsTest.mm", "ble_peripheral_test.mm", + "ble_socket_test.mm", ], deps = [ "//internal/platform:base", @@ -46,6 +47,7 @@ objc_library( "//internal/platform/implementation/apple:Shared", "//internal/platform/implementation/apple:ble_v2", "//internal/platform/implementation/apple/Mediums/BLE", + "//internal/platform/implementation/apple/Mediums/BLE/Tests:BLETestsLib", "//internal/platform/implementation/apple/Mediums/WiFiCommon", "//internal/platform/implementation/apple/Mediums/WiFiCommon/Tests:FakeNWFramework", "//third_party/apple_frameworks:CommonCrypto", diff --git a/internal/platform/implementation/apple/Tests/ble_socket_test.mm b/internal/platform/implementation/apple/Tests/ble_socket_test.mm new file mode 100644 index 00000000..d18f1b73 --- /dev/null +++ b/internal/platform/implementation/apple/Tests/ble_socket_test.mm @@ -0,0 +1,141 @@ +// 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. + +#include "internal/platform/implementation/apple/ble_socket.h" + +#import + +#include "internal/platform/exception.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTCharacteristic.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCCentralManager.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCMConnection.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h" +#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h" +#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCMFakeConnection.h" + +@interface GNCBleSocketTest : XCTestCase +@end + +@implementation GNCBleSocketTest { + nearby::apple::BleSocket *_socket; + GNCMFakeConnection *_fakeGNCMConnection; +} + +- (void)setUp { + [super setUp]; + _fakeGNCMConnection = [[GNCMFakeConnection alloc] init]; + _socket = new nearby::apple::BleSocket(_fakeGNCMConnection); + _fakeGNCMConnection.connectionHandlers = _socket->GetInputStream().GetConnectionHandlers(); +} + +- (void)tearDown { + delete _socket; + [super tearDown]; +} + +- (void)testBleSocketConstructor { + XCTAssertNotEqual(_socket, nullptr); +} + +- (void)testBleSocketGetInputStream { + XCTAssertNotEqual(&_socket->GetInputStream(), nullptr); +} + +- (void)testBleSocketGetOutputStream { + XCTAssertNotEqual(&_socket->GetOutputStream(), nullptr); +} + +- (void)testBleSocketClose { + XCTAssertEqual(_socket->Close().value, nearby::Exception::kSuccess); + XCTAssertTrue(_socket->IsClosed()); +} + +- (void)testBleSocketGetRemotePeripheralId { + XCTAssertEqual(_socket->GetRemotePeripheralId(), 0xffffffffffffffff); +} + +- (void)testBleSocketIsClosed { + XCTAssertFalse(_socket->IsClosed()); + XCTAssertEqual(_socket->Close().value, nearby::Exception::kSuccess); + XCTAssertTrue(_socket->IsClosed()); +} + +- (void)testBleSocketSetCloseNotifier { + bool notifierCalled = false; + _socket->SetCloseNotifier([¬ifierCalled] { notifierCalled = true; }); + XCTAssertEqual(_socket->Close().value, nearby::Exception::kSuccess); + XCTAssertTrue(notifierCalled); +} + +- (void)testBleInputStreamRead { + nearby::apple::BleInputStream &inputStream = _socket->GetInputStream(); + + // Simulate data coming in. + NSString *testString = @"Hello"; + NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding]; + [_fakeGNCMConnection simulateDataReceived:testData]; + + // Read the data. + nearby::ExceptionOr result = inputStream.Read(testData.length); + XCTAssertTrue(result.ok()); + XCTAssertEqual(result.result().size(), testData.length); + XCTAssertEqualObjects(testData, [NSData dataWithBytes:result.result().data() + length:result.result().size()]); +} + +- (void)testBleInputStreamReadAfterDisconnect { + nearby::apple::BleInputStream &inputStream = _socket->GetInputStream(); + + // Simulate data coming in. + NSString *testString = @"Hello"; + NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding]; + [_fakeGNCMConnection simulateDataReceived:testData]; + + // Simulate disconnect. + [_fakeGNCMConnection simulateDisconnect]; + + // Reads should now fail with an IO exception. + nearby::ExceptionOr result = inputStream.Read(testData.length); + XCTAssertFalse(result.ok()); + XCTAssertEqual(result.exception(), nearby::Exception::kIo); + + // Further reads should also fail. + result = inputStream.Read(1); + XCTAssertFalse(result.ok()); + XCTAssertEqual(result.exception(), nearby::Exception::kIo); +} + +- (void)testBleInputStreamClose { + nearby::apple::BleInputStream &inputStream = _socket->GetInputStream(); + XCTAssertEqual(inputStream.Close().value, nearby::Exception::kSuccess); +} + +- (void)testBleOutputStreamWrite { + nearby::apple::BleOutputStream &outputStream = _socket->GetOutputStream(); + nearby::ByteArray data("test"); + XCTAssertEqual(outputStream.Write(data).value, nearby::Exception::kSuccess); +} + +- (void)testBleOutputStreamFlush { + nearby::apple::BleOutputStream &outputStream = _socket->GetOutputStream(); + XCTAssertEqual(outputStream.Flush().value, nearby::Exception::kSuccess); +} + +- (void)testBleOutputStreamClose { + nearby::apple::BleOutputStream &outputStream = _socket->GetOutputStream(); + XCTAssertEqual(outputStream.Close().value, nearby::Exception::kSuccess); +} + +@end