<commit_before>package com.polidea.rxandroidble.internal.connection

import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.os.Build
import android.support.v4.util.Pair
import com.polidea.rxandroidble.BuildConfig
import com.polidea.rxandroidble.FlatRxBleRadio
import com.polidea.rxandroidble.RxBleConnection
import com.polidea.rxandroidble.RxBleDeviceServices
import com.polidea.rxandroidble.exceptions.*
import org.robolectric.annotation.Config
import org.robospock.GradleRoboSpecification
import rx.observers.TestSubscriber
import rx.subjects.BehaviorSubject
import rx.subjects.PublishSubject
import spock.lang.Unroll

import static com.polidea.rxandroidble.exceptions.BleGattOperationType.DESCRIPTOR_WRITE
import static java.util.Collections.emptyList
import static rx.Observable.from
import static rx.Observable.just

@Config(manifest = Config.NONE, constants = BuildConfig, sdk = Build.VERSION_CODES.LOLLIPOP)
class RxBleConnectionTest extends GradleRoboSpecification {
    public static final CHARACTERISTIC_UUID = UUID.randomUUID()
    public static final OTHER_UUID = UUID.randomUUID()
    public static final byte[] EMPTY_DATA = [] as byte[]
    public static final byte[] NOT_EMPTY_DATA = [1, 2, 3] as byte[]
    public static final byte[] OTHER_DATA = [2, 2, 3] as byte[]
    def flatRadio = new FlatRxBleRadio()
    def callback = Mock RxBleGattCallback
    def bluetoothGattMock = Mock BluetoothGatt
    def objectUnderTest = new RxBleConnectionImpl(flatRadio, callback, bluetoothGattMock)
    def connectionStateChange = BehaviorSubject.create()
    def TestSubscriber testSubscriber

    def setup() {
        testSubscriber = new TestSubscriber()
        callback.getOnConnectionStateChange() >> connectionStateChange
    }

    def "should proxy connection state"() {
        given:
        connectionStateChange.onNext(pushedStatus)

        when:
        objectUnderTest.getConnectionState().subscribe(testSubscriber)

        then:
        testSubscriber.assertValue(expectedState)

        where:
        pushedStatus                                       | expectedState
        RxBleConnection.RxBleConnectionState.CONNECTED     | RxBleConnection.RxBleConnectionState.CONNECTED
        RxBleConnection.RxBleConnectionState.DISCONNECTED  | RxBleConnection.RxBleConnectionState.DISCONNECTED
        RxBleConnection.RxBleConnectionState.CONNECTING    | RxBleConnection.RxBleConnectionState.CONNECTING
        RxBleConnection.RxBleConnectionState.DISCONNECTING | RxBleConnection.RxBleConnectionState.DISCONNECTING
    }

    def "should emit error if failed to start retrieving services"() {
        given:
        callback.getOnServicesDiscovered() >> PublishSubject.create()
        shouldGattContainNoServices()
        shouldFailStartingDiscovery()

        when:
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertError(BleGattCannotStartException)
        testSubscriber.assertError {
            it.bleGattOperationType == BleGattOperationType.SERVICE_DISCOVERY
        }
    }

    def "should return cached services"() {
        given:
        def services = [Mock(BluetoothGattService), Mock(BluetoothGattService)]
        shouldSuccessfullyStartDiscovery()

        when:
        objectUnderTest.discoverServices().subscribe() // <-- it must be here hence mocks are not configured yet in given block.
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertServices services
        testSubscriber.assertCompleted()
        (_..1) * bluetoothGattMock.getServices() >> emptyList()
        1 * callback.getOnServicesDiscovered() >> just(new RxBleDeviceServices(services))
    }

    def "should return services instantly if they were already discovered"() {
        given:
        def services = [Mock(BluetoothGattService), Mock(BluetoothGattService)]
        bluetoothGattMock.getServices() >> services

        when:
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertServices services
        testSubscriber.assertCompleted()
        0 * bluetoothGattMock.discoverServices()
    }

    def "should try to discover services if cached services are empty"() {
        given:
        def services = [Mock(BluetoothGattService), Mock(BluetoothGattService)]
        shouldSuccessfullyStartDiscovery()
        shouldGattContainNoServices()
        callback.getOnServicesDiscovered() >> just(new RxBleDeviceServices(services))

        when:
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertServices services
        testSubscriber.assertCompleted()
        1 * bluetoothGattMock.discoverServices() >> true
    }

    def "should emit BleCharacteristicNotFoundException if there are no services during read operation"() {
        given:
        shouldGattCallbackReturnServicesOnDiscovery([])
        shouldGattContainNoServices()

        when:
        objectUnderTest.readCharacteristic(CHARACTERISTIC_UUID).subscribe(testSubscriber)

        then:
        testSubscriber.assertError(BleCharacteristicNotFoundException)
    }

    def "should emit BleCharacteristicNotFoundException if characteristic was not found during read operation"() {
        given:
        def service = Mock BluetoothGattService
        shouldGattCallbackReturnServicesOnDiscovery([service])
        shouldGattContainServices([service])
        service.getCharacteristic(_) >> null

        when:
        objectUnderTest.readCharacteristic(CHARACTERISTIC_UUID).subscribe(testSubscriber)

        then:
        testSubscriber.assertError(BleCharacteristicNotFoundException)
        testSubscriber.assertError { it.charactersisticUUID == CHARACTERISTIC_UUID }
    }

    def "should read first found characteristic with matching UUID"() {
        given:
        def firstCharacteristicUUID = UUID.randomUUID()
        def secondCharacteristicUUID = UUID.randomUUID()
        def firstCharacteristicValue = [1, 2, 3] as byte[]
        def secondCharacteristicValue = [3, 4, 5] as byte[]
        def service = Mock BluetoothGattService
        shouldServiceContainCharacteristic(service, firstCharacteristicUUID, firstCharacteristicValue)
        shouldServiceContainCharacteristic(service, secondCharacteristicUUID, secondCharacteristicValue)
        shouldGattCallbackReturnServicesOnDiscovery([service])
        shouldGattContainServices([service])
        shouldGattCallbackReturnDataOnRead(
                [uuid: secondCharacteristicUUID, value: secondCharacteristicValue],
                [uuid: firstCharacteristicUUID, value: firstCharacteristicValue])

        when:
        objectUnderTest.readCharacteristic(firstCharacteristicUUID).subscribe(testSubscriber)

        then:
        testSubscriber.assertValue(firstCharacteristicValue)
    }

    def "should emit BleCharacteristicNotFoundException if there are no services during write operation"() {
        given:
        def dataToWrite = [1, 2, 3] as byte[]
        shouldGattCallbackReturnServicesOnDiscovery([])
        shouldGattContainNoServices()

        when:<commit_msg>Code review fixes<commit_after>package com.polidea.rxandroidble.internal.connection

import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
import android.os.Build
import android.support.v4.util.Pair
import com.polidea.rxandroidble.BuildConfig
import com.polidea.rxandroidble.FlatRxBleRadio
import com.polidea.rxandroidble.RxBleConnection
import com.polidea.rxandroidble.RxBleDeviceServices
import com.polidea.rxandroidble.exceptions.*
import org.robolectric.annotation.Config
import org.robospock.GradleRoboSpecification
import rx.observers.TestSubscriber
import rx.subjects.BehaviorSubject
import rx.subjects.PublishSubject
import spock.lang.Unroll

import static com.polidea.rxandroidble.exceptions.BleGattOperationType.DESCRIPTOR_WRITE
import static java.util.Collections.emptyList
import static rx.Observable.from
import static rx.Observable.just

@Config(manifest = Config.NONE, constants = BuildConfig, sdk = Build.VERSION_CODES.LOLLIPOP)
class RxBleConnectionTest extends GradleRoboSpecification {
    public static final CHARACTERISTIC_UUID = UUID.fromString("f301f518-5414-471c-8a7b-2ef6d1b7373d")
    public static final OTHER_UUID = UUID.fromString("ab906173-5daa-4d6b-8604-c2be69122d57")
    public static final byte[] EMPTY_DATA = [] as byte[]
    public static final byte[] NOT_EMPTY_DATA = [1, 2, 3] as byte[]
    public static final byte[] OTHER_DATA = [2, 2, 3] as byte[]
    def flatRadio = new FlatRxBleRadio()
    def callback = Mock RxBleGattCallback
    def bluetoothGattMock = Mock BluetoothGatt
    def objectUnderTest = new RxBleConnectionImpl(flatRadio, callback, bluetoothGattMock)
    def connectionStateChange = BehaviorSubject.create()
    def TestSubscriber testSubscriber

    def setup() {
        testSubscriber = new TestSubscriber()
        callback.getOnConnectionStateChange() >> connectionStateChange
    }

    def "should proxy connection state"() {
        given:
        connectionStateChange.onNext(pushedStatus)

        when:
        objectUnderTest.getConnectionState().subscribe(testSubscriber)

        then:
        testSubscriber.assertValue(expectedState)

        where:
        pushedStatus                                       | expectedState
        RxBleConnection.RxBleConnectionState.CONNECTED     | RxBleConnection.RxBleConnectionState.CONNECTED
        RxBleConnection.RxBleConnectionState.DISCONNECTED  | RxBleConnection.RxBleConnectionState.DISCONNECTED
        RxBleConnection.RxBleConnectionState.CONNECTING    | RxBleConnection.RxBleConnectionState.CONNECTING
        RxBleConnection.RxBleConnectionState.DISCONNECTING | RxBleConnection.RxBleConnectionState.DISCONNECTING
    }

    def "should emit error if failed to start retrieving services"() {
        given:
        callback.getOnServicesDiscovered() >> PublishSubject.create()
        shouldGattContainNoServices()
        shouldFailStartingDiscovery()

        when:
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertError(BleGattCannotStartException)
        testSubscriber.assertError {
            it.bleGattOperationType == BleGattOperationType.SERVICE_DISCOVERY
        }
    }

    def "should return cached services"() {
        given:
        def services = [Mock(BluetoothGattService), Mock(BluetoothGattService)]
        shouldSuccessfullyStartDiscovery()

        when:
        objectUnderTest.discoverServices().subscribe() // <-- it must be here hence mocks are not configured yet in given block.
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertServices services
        testSubscriber.assertCompleted()
        (_..1) * bluetoothGattMock.getServices() >> emptyList()
        1 * callback.getOnServicesDiscovered() >> just(new RxBleDeviceServices(services))
    }

    def "should return services instantly if they were already discovered"() {
        given:
        def services = [Mock(BluetoothGattService), Mock(BluetoothGattService)]
        bluetoothGattMock.getServices() >> services

        when:
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertServices services
        testSubscriber.assertCompleted()
        0 * bluetoothGattMock.discoverServices()
    }

    def "should try to discover services if cached services are empty"() {
        given:
        def services = [Mock(BluetoothGattService), Mock(BluetoothGattService)]
        shouldSuccessfullyStartDiscovery()
        shouldGattContainNoServices()
        callback.getOnServicesDiscovered() >> just(new RxBleDeviceServices(services))

        when:
        objectUnderTest.discoverServices().subscribe(testSubscriber)

        then:
        testSubscriber.assertServices services
        testSubscriber.assertCompleted()
        1 * bluetoothGattMock.discoverServices() >> true
    }

    def "should emit BleCharacteristicNotFoundException if there are no services during read operation"() {
        given:
        shouldGattCallbackReturnServicesOnDiscovery([])
        shouldGattContainNoServices()

        when:
        objectUnderTest.readCharacteristic(CHARACTERISTIC_UUID).subscribe(testSubscriber)

        then:
        testSubscriber.assertError(BleCharacteristicNotFoundException)
    }

    def "should emit BleCharacteristicNotFoundException if characteristic was not found during read operation"() {
        given:
        def service = Mock BluetoothGattService
        shouldGattCallbackReturnServicesOnDiscovery([service])
        shouldGattContainServices([service])
        service.getCharacteristic(_) >> null

        when:
        objectUnderTest.readCharacteristic(CHARACTERISTIC_UUID).subscribe(testSubscriber)

        then:
        testSubscriber.assertError(BleCharacteristicNotFoundException)
        testSubscriber.assertError { it.charactersisticUUID == CHARACTERISTIC_UUID }
    }

    def "should read first found characteristic with matching UUID"() {
        given:
        def firstCharacteristicValue = [1, 2, 3] as byte[]
        def secondCharacteristicValue = [3, 4, 5] as byte[]
        def service = Mock BluetoothGattService
        shouldServiceContainCharacteristic(service, CHARACTERISTIC_UUID, firstCharacteristicValue)
        shouldServiceContainCharacteristic(service, OTHER_UUID, secondCharacteristicValue)
        shouldGattCallbackReturnServicesOnDiscovery([service])
        shouldGattContainServices([service])
        shouldGattCallbackReturnDataOnRead(
                [uuid: OTHER_UUID, value: secondCharacteristicValue],
                [uuid: CHARACTERISTIC_UUID, value: firstCharacteristicValue])

        when:
        objectUnderTest.readCharacteristic(CHARACTERISTIC_UUID).subscribe(testSubscriber)

        then:
        testSubscriber.assertValue(firstCharacteristicValue)
    }

    def "should emit BleCharacteristicNotFoundException if there are no services during write operation"() {
        given:
        def dataToWrite = [1, 2, 3] as byte[]
        shouldGattCallbackReturnServicesOnDiscovery([])
        shouldGattContainNoServices()

        when:
{'fraction_non_alphanumeric': 0.07029804727646455, 'fraction_numerical': 0.005686879068174032, 'mean_word_length': 3.673711175152097, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2503236', 'n_tokens_mistral': 3907, 'n_tokens_neox': 3829, 'n_words': 771}