diff --git a/build.gradle b/build.gradle index c167b6a7..c377e766 100644 --- a/build.gradle +++ b/build.gradle @@ -1,95 +1,94 @@ apply plugin: 'com.android.application' buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:3.3.1' } } android { compileSdkVersion 28 defaultConfig { minSdkVersion 14 targetSdkVersion 28 - //multiDexEnabled true - //testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner" } dexOptions { javaMaxHeapSize "2g" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['resources'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } - androidTest { + test { java.srcDirs = ['tests'] } } packagingOptions { pickFirst "META-INF/DEPENDENCIES" pickFirst "META-INF/LICENSE" pickFirst "META-INF/NOTICE" pickFirst "META-INF/BCKEY.SF" pickFirst "META-INF/BCKEY.DSA" pickFirst "META-INF/INDEX.LIST" } lintOptions { abortOnError false checkReleaseBuilds false } buildTypes { debug { minifyEnabled false useProguard false } release { //keep on 'release' for faster builds, set to 'all' when testing to make sure proguard is not deleting important stuff minifyEnabled true useProguard true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { repositories { jcenter() google() } implementation 'androidx.media:media:1.0.1' implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.preference:preference:1.0.0' implementation 'com.google.android.material:material:1.0.0' implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps implementation 'org.apache.sshd:sshd-core:0.14.0' implementation 'org.apache.mina:mina-core:2.0.19' //For some reason, makes sshd-core:0.14.0 work without NIO, which isn't available until Android 8+ implementation 'com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0' //For SSL certificate generation implementation 'com.jakewharton:butterknife:10.0.0' annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0' implementation 'org.atteo.classindex:classindex:3.6' annotationProcessor 'org.atteo.classindex:classindex:3.6' // Testing - androidTestImplementation 'org.mockito:mockito-core:1.10.19' - androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.1'// Because mockito has some problems with dex environment - androidTestImplementation 'org.skyscreamer:jsonassert:1.3.0' testImplementation 'junit:junit:4.12' - + testImplementation 'org.powermock:powermock-core:2.0.0' + testImplementation 'org.powermock:powermock-module-junit4:2.0.0' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.0' + testImplementation 'org.mockito:mockito-core:2.23.0' + testImplementation 'org.skyscreamer:jsonassert:1.3.0' } diff --git a/tests/org/kde/kdeconnect/DeviceTest.java b/tests/org/kde/kdeconnect/DeviceTest.java index fd3d1f20..61477bce 100644 --- a/tests/org/kde/kdeconnect/DeviceTest.java +++ b/tests/org/kde/kdeconnect/DeviceTest.java @@ -1,280 +1,281 @@ /* * Copyright 2015 Vineet Garg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.kde.kdeconnect; +import android.app.NotificationManager; import android.content.Context; import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.util.Base64; import android.util.Log; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Backends.LanBackend.LanLink; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler; +import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; +import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.mockito.Mockito; -import org.spongycastle.asn1.x500.X500NameBuilder; -import org.spongycastle.asn1.x500.style.BCStyle; -import org.spongycastle.cert.X509v3CertificateBuilder; -import org.spongycastle.cert.jcajce.JcaX509CertificateConverter; -import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder; -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.spongycastle.operator.ContentSigner; -import org.spongycastle.operator.jcajce.JcaContentSignerBuilder; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import java.lang.reflect.Method; -import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.security.cert.X509Certificate; -import java.util.Date; -class DeviceTest extends AndroidTestCase { +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Matchers.eq; - // Creating a paired device before each test case - @Override - protected void setUp() throws Exception { - super.setUp(); +@RunWith(PowerMockRunner.class) +@PrepareForTest({Base64.class, Log.class, PreferenceManager.class}) +public class DeviceTest { + + Context context; - // Dexmaker has problems guessing cache directory, setting manually - System.setProperty("dexmaker.dexcache", getContext().getCacheDir().getPath()); + // Creating a paired device before each test case + @Before + public void setUp() { // Save new test device in settings - Context context = getContext(); String deviceId = "testDevice"; String name = "Test Device"; KeyPair keyPair; try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); keyPair = keyGen.genKeyPair(); } catch (Exception e) { e.printStackTrace(); Log.e("KDE/initializeRsaKeys", "Exception"); return; } - SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); + this.context = Mockito.mock(Context.class); + + PowerMockito.mockStatic(Base64.class); + PowerMockito.when(Base64.encodeToString(any(), anyInt())).thenAnswer(invocation -> java.util.Base64.getMimeEncoder().encodeToString((byte[]) invocation.getArguments()[0])); + PowerMockito.when(Base64.decode(anyString(), anyInt())).thenAnswer(invocation -> java.util.Base64.getMimeDecoder().decode((String) invocation.getArguments()[0])); + + PowerMockito.mockStatic(Log.class); //Store device information needed to create a Device object in a future - SharedPreferences.Editor editor = settings.edit(); + MockSharedPreference deviceSettings = new MockSharedPreference(); + SharedPreferences.Editor editor = deviceSettings.edit(); editor.putString("deviceName", name); editor.putString("deviceType", Device.DeviceType.Phone.toString()); editor.putString("publicKey", Base64.encodeToString(keyPair.getPublic().getEncoded(), 0).trim() + "\n"); editor.apply(); + Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings); - SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - preferences.edit().putBoolean(deviceId, true).apply(); - } - - - // Removing paired device info after each test case - @Override - protected void tearDown() throws Exception { - super.tearDown(); + //Store the device as trusted + MockSharedPreference trustedSettings = new MockSharedPreference(); + trustedSettings.edit().putBoolean(deviceId, true).apply(); + Mockito.when(context.getSharedPreferences(eq("trusted_devices"), eq(Context.MODE_PRIVATE))).thenReturn(trustedSettings); - // Remove saved test device - Context context = getContext(); - - String deviceId = "testDevice"; + //Store an untrusted device + MockSharedPreference untrustedSettings = new MockSharedPreference(); + Mockito.when(context.getSharedPreferences(eq("unpairedTestDevice"), eq(Context.MODE_PRIVATE))).thenReturn(untrustedSettings); - SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); - - //Store device information needed to create a Device object in a future - SharedPreferences.Editor editor = settings.edit(); - editor.clear(); - editor.apply(); + //Default shared prefs, including our own private key + PowerMockito.mockStatic(PreferenceManager.class); + MockSharedPreference defaultSettings = new MockSharedPreference(); + PowerMockito.when(PreferenceManager.getDefaultSharedPreferences(any())).thenReturn(defaultSettings); + RsaHelper.initialiseRsaKeys(context); - SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - preferences.edit().remove(deviceId).apply(); - } + Mockito.when(context.getSystemService(eq(Context.NOTIFICATION_SERVICE))).thenReturn(Mockito.mock(NotificationManager.class)); +} // Basic paired device testing + @Test public void testDevice() { - Device device = new Device(getContext(), "testDevice"); + Device device = new Device(context, "testDevice"); assertEquals(device.getDeviceId(), "testDevice"); assertEquals(device.getDeviceType(), Device.DeviceType.Phone); assertEquals(device.getName(), "Test Device"); assertTrue(device.isPaired()); } // Testing pairing done using reflection since it is private // Created an unpaired device inside this test + @Test public void testPairingDone() { NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY); fakeNetworkPacket.set("deviceId", "unpairedTestDevice"); fakeNetworkPacket.set("deviceName", "Unpaired Test Device"); fakeNetworkPacket.set("protocolVersion", NetworkPacket.ProtocolVersion); fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString()); LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class); Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider"); LanLink link = Mockito.mock(LanLink.class); Mockito.when(link.getLinkProvider()).thenReturn(linkProvider); - Mockito.when(link.getPairingHandler(Mockito.any(Device.class), Mockito.any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class)); - Device device = new Device(getContext(), fakeNetworkPacket, link); + Mockito.when(link.getPairingHandler(any(Device.class), any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class)); + Device device = new Device(context, fakeNetworkPacket, link); KeyPair keyPair; try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); keyPair = keyGen.genKeyPair(); } catch (Exception e) { e.printStackTrace(); Log.e("KDE/initializeRsaKeys", "Exception"); return; } device.publicKey = keyPair.getPublic(); assertNotNull(device); assertEquals(device.getDeviceId(), "unpairedTestDevice"); assertEquals(device.getName(), "Unpaired Test Device"); assertEquals(device.getDeviceType(), Device.DeviceType.Phone); assertNotNull(device.publicKey); assertNull(device.certificate); Method method; try { method = Device.class.getDeclaredMethod("pairingDone"); method.setAccessible(true); method.invoke(device); } catch (Exception e) { e.printStackTrace(); } assertTrue(device.isPaired()); - SharedPreferences preferences = getContext().getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); + SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); assertTrue(preferences.getBoolean(device.getDeviceId(), false)); - SharedPreferences settings = getContext().getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE); + SharedPreferences settings = context.getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE); assertEquals(settings.getString("deviceName", "Unknown device"), "Unpaired Test Device"); assertEquals(settings.getString("deviceType", "tablet"), "phone"); // Cleanup for unpaired test device preferences.edit().remove(device.getDeviceId()).apply(); settings.edit().clear().apply(); } + @Test public void testPairingDoneWithCertificate() throws Exception { KeyPair keyPair = null; try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); keyPair = keyGen.genKeyPair(); } catch (Exception e) { e.printStackTrace(); Log.e("KDE/initializeRsaKeys", "Exception"); } - X509Certificate certificate = null; - try { - - BouncyCastleProvider BC = new BouncyCastleProvider(); - - X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); - nameBuilder.addRDN(BCStyle.CN, "testDevice"); - nameBuilder.addRDN(BCStyle.OU, "KDE Connect"); - nameBuilder.addRDN(BCStyle.O, "KDE"); - Date notBefore = new Date(System.currentTimeMillis()); - Date notAfter = new Date(System.currentTimeMillis() + System.currentTimeMillis()); - X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( - nameBuilder.build(), - BigInteger.ONE, - notBefore, - notAfter, - nameBuilder.build(), - keyPair.getPublic() - ); - ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(keyPair.getPrivate()); - certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateBuilder.build(contentSigner)); - - } catch (Exception e) { - e.printStackTrace(); - Log.e("KDE/initialiseCert", "Exception"); - } - NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY); fakeNetworkPacket.set("deviceId", "unpairedTestDevice"); fakeNetworkPacket.set("deviceName", "Unpaired Test Device"); fakeNetworkPacket.set("protocolVersion", NetworkPacket.ProtocolVersion); fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString()); - fakeNetworkPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0)); + fakeNetworkPacket.set("certificate", + "MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" + + "NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" + + "bmVjdDAeFw0xNTA2MDMxMzE0MzhaFw0yNTA2MDMxMzE0MzhaMFUxLzAtBgNVBAMMJl9kYTE3OWE5\n" + + "MV9mMDY0XzQ3OGVfYmU4Y18xOTM1ZDc1NDRkNTRfMQwwCgYDVQQKDANLREUxFDASBgNVBAsMC0tk\n" + + "ZSBjb25uZWN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzH9GxS1lctpwYdSGAoPH\n" + + "ws+MnVaL0PVDCuzrpxzXc+bChR87xofhQIesLPLZEcmUJ1MlEJ6jx4W+gVhvY2tUN7SoiKKbnq8s\n" + + "WjI5ovs5yML3C1zPbOSJAdK613FcdkK+UGd/9dQk54gIozinC58iyTAChVVpB3pAF38EPxwKkuo2\n" + + "qTzwk24d6PRxz1skkzwEphUQQzGboyHsAlJHN1MzM2/yFGB4l8iUua2d3ETyfy/xFEh/SwtGtXE5\n" + + "KLz4cpb0fxjeYQZVruBKxzE07kgDO3zOhmP3LJ/KSPHWYImd1DWmpY9iDvoXr6+V7FAnRloaEIyg\n" + + "7WwdlSCpo3TXVuIjLwIDAQABozIwMDAdBgNVHQ4EFgQUwmbHo8YbiR463GRKSLL3eIKyvDkwDwYD\n" + + "VR0TAQH/BAUwAwIBADANBgkqhkiG9w0BAQUFAAOCAQEAydijH3rbnvpBDB/30w2PCGMT7O0N/XYM\n" + + "wBtUidqa4NFumJrNrccx5Ehp4UP66BfP61HW8h2U/EekYfOsZyyWd4KnsDD6ycR8h/WvpK3BC2cn\n" + + "I299wbqCEZmk5ZFFaEIDHdLAdgMCuxJkAzy9mMrWEa05Soxi2/ZXdrU9nXo5dzuPGYlirVPDHl7r\n" + + "/urBxD6HVX3ObQJRJ7r/nAWyUVdX3/biJaDRsydftOpGU6Gi5c1JK4MWIz8Bsjh6mEjCsVatbPPl\n" + + "yygGiJbDZfAvN2XoaVEBii2GDDCWfaFwPVPYlNTvjkUkMP8YThlMsiJ8Q4693XoLOL94GpNlCfUg\n" + + "7n+KOQ=="); LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class); Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider"); LanLink link = Mockito.mock(LanLink.class); - Mockito.when(link.getPairingHandler(Mockito.any(Device.class), Mockito.any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class)); + Mockito.when(link.getPairingHandler(any(Device.class), any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class)); Mockito.when(link.getLinkProvider()).thenReturn(linkProvider); - Device device = new Device(getContext(), fakeNetworkPacket, link); + Device device = new Device(context, fakeNetworkPacket, link); device.publicKey = keyPair.getPublic(); assertNotNull(device); assertEquals(device.getDeviceId(), "unpairedTestDevice"); assertEquals(device.getName(), "Unpaired Test Device"); assertEquals(device.getDeviceType(), Device.DeviceType.Phone); assertNotNull(device.publicKey); assertNotNull(device.certificate); Method method; try { method = Device.class.getDeclaredMethod("pairingDone"); method.setAccessible(true); method.invoke(device); } catch (Exception e) { e.printStackTrace(); } assertTrue(device.isPaired()); - SharedPreferences preferences = getContext().getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); + SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); assertTrue(preferences.getBoolean(device.getDeviceId(), false)); - SharedPreferences settings = getContext().getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE); + SharedPreferences settings = context.getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE); assertEquals(settings.getString("deviceName", "Unknown device"), "Unpaired Test Device"); assertEquals(settings.getString("deviceType", "tablet"), "phone"); // Cleanup for unpaired test device preferences.edit().remove(device.getDeviceId()).apply(); settings.edit().clear().apply(); } + @Test public void testUnpair() { - Device device = new Device(getContext(), "testDevice"); + Device device = new Device(context, "testDevice"); device.unpair(); assertFalse(device.isPaired()); - SharedPreferences preferences = getContext().getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); + SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); assertFalse(preferences.getBoolean(device.getDeviceId(), false)); } } diff --git a/tests/org/kde/kdeconnect/LanLinkProviderTest.java b/tests/org/kde/kdeconnect/LanLinkProviderTest.java index 7be34180..7c3a3758 100644 --- a/tests/org/kde/kdeconnect/LanLinkProviderTest.java +++ b/tests/org/kde/kdeconnect/LanLinkProviderTest.java @@ -1,69 +1,83 @@ /* * Copyright 2015 Vineet Garg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.kde.kdeconnect; +import android.util.Log; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.kde.kdeconnect.Backends.LanBackend.LanLink; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; +import org.kde.kdeconnect.Helpers.DeviceHelper; import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.Socket; import java.util.HashMap; -class LanLinkProviderTest extends AndroidTestCase { - - private LanLinkProvider linkProvider; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; - @Override - protected void setUp() throws Exception { - super.setUp(); +@RunWith(PowerMockRunner.class) +@PrepareForTest({DeviceHelper.class, Log.class}) +public class LanLinkProviderTest { - System.setProperty("dexmaker.dexcache", getContext().getCacheDir().getPath()); + @Before + public void setUp() { + PowerMockito.mockStatic(DeviceHelper.class); + PowerMockito.when(DeviceHelper.getDeviceId(any())).thenReturn("123"); - linkProvider = new LanLinkProvider(getContext()); + PowerMockito.mockStatic(Log.class); } + @Test public void testIdentityPacketReceived() throws Exception { + LanLinkProvider linkProvider = new LanLinkProvider(null); + NetworkPacket networkPacket = Mockito.mock(NetworkPacket.class); Mockito.when(networkPacket.getType()).thenReturn("kdeconnect.identity"); Mockito.when(networkPacket.getString("deviceId")).thenReturn("testDevice"); Mockito.when(networkPacket.getString("deviceName")).thenReturn("Test Device"); Mockito.when(networkPacket.getInt("protocolVersion")).thenReturn(5); Mockito.when(networkPacket.getString("deviceType")).thenReturn("phone"); String serialized = "{\"type\":\"kdeconnect.identity\",\"id\":12345,\"body\":{\"deviceName\":\"Test Device\",\"deviceType\":\"phone\",\"deviceId\":\"testDevice\",\"protocolVersion\":5}}"; Mockito.when(networkPacket.serialize()).thenReturn(serialized); Socket channel = Mockito.mock(Socket.class); Method method = LanLinkProvider.class.getDeclaredMethod("identityPacketReceived", NetworkPacket.class, Socket.class, LanLink.ConnectionStarted.class); method.setAccessible(true); method.invoke(linkProvider, networkPacket, channel, LanLink.ConnectionStarted.Locally); HashMap visibleComputers; Field field = LanLinkProvider.class.getDeclaredField("visibleComputers"); field.setAccessible(true); visibleComputers = (HashMap) field.get(linkProvider); assertNotNull(visibleComputers.get("testDevice")); } } diff --git a/tests/org/kde/kdeconnect/LanLinkTest.java b/tests/org/kde/kdeconnect/LanLinkTest.java index 636def9c..0ac5bd83 100644 --- a/tests/org/kde/kdeconnect/LanLinkTest.java +++ b/tests/org/kde/kdeconnect/LanLinkTest.java @@ -1,232 +1,245 @@ /* * Copyright 2015 Vineet Garg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.kde.kdeconnect; +import android.content.Context; import android.util.Log; import org.json.JSONException; import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.kde.kdeconnect.Backends.LanBackend.LanLink; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; +import org.kde.kdeconnect.Helpers.DeviceHelper; import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; -class LanLinkTest extends AndroidTestCase { +import static org.junit.Assert.assertEquals; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({Log.class}) +public class LanLinkTest { private LanLink badLanLink; private LanLink goodLanLink; private OutputStream badOutputStream; private OutputStream goodOutputStream; private Device.SendPacketStatusCallback callback; - @Override - protected void setUp() throws Exception { - super.setUp(); - - System.setProperty("dexmaker.dexcache", getContext().getCacheDir().getPath()); + @Before + public void setUp() throws Exception { + PowerMockito.mockStatic(Log.class); LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class); Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider"); callback = Mockito.mock(Device.SendPacketStatusCallback.class); goodOutputStream = Mockito.mock(OutputStream.class); badOutputStream = Mockito.mock(OutputStream.class); Mockito.doThrow(new IOException("AAA")).when(badOutputStream).write(Mockito.any(byte[].class)); Socket socketMock = Mockito.mock(Socket.class); Mockito.when(socketMock.getRemoteSocketAddress()).thenReturn(new InetSocketAddress(5000)); Mockito.when(socketMock.getOutputStream()).thenReturn(goodOutputStream); Socket socketBadMock = Mockito.mock(Socket.class); Mockito.when(socketBadMock.getRemoteSocketAddress()).thenReturn(new InetSocketAddress(5000)); Mockito.when(socketBadMock.getOutputStream()).thenReturn(badOutputStream); - goodLanLink = new LanLink(getContext(), "testDevice", linkProvider, socketMock, LanLink.ConnectionStarted.Remotely); - badLanLink = new LanLink(getContext(), "testDevice", linkProvider, socketBadMock, LanLink.ConnectionStarted.Remotely); + Context context = Mockito.mock(Context.class); + goodLanLink = new LanLink(context, "testDevice", linkProvider, socketMock, LanLink.ConnectionStarted.Remotely); + badLanLink = new LanLink(context, "testDevice", linkProvider, socketBadMock, LanLink.ConnectionStarted.Remotely); } + @Test public void testSendPacketSuccess() throws JSONException { NetworkPacket testPacket = Mockito.mock(NetworkPacket.class); Mockito.when(testPacket.getType()).thenReturn("kdeconnect.test"); Mockito.when(testPacket.getBoolean("isTesting")).thenReturn(true); Mockito.when(testPacket.getString("testName")).thenReturn("testSendPacketSuccess"); Mockito.when(testPacket.serialize()).thenReturn("{\"id\":123,\"type\":\"kdeconnect.test\",\"body\":{\"isTesting\":true,\"testName\":\"testSendPacketSuccess\"}}"); goodLanLink.sendPacket(testPacket, callback); Mockito.verify(callback).onSuccess(); } + @Test public void testSendPacketFail() throws JSONException { NetworkPacket testPacket = Mockito.mock(NetworkPacket.class); Mockito.when(testPacket.getType()).thenReturn("kdeconnect.test"); Mockito.when(testPacket.getBoolean("isTesting")).thenReturn(true); Mockito.when(testPacket.getString("testName")).thenReturn("testSendPacketFail"); Mockito.when(testPacket.serialize()).thenReturn("{\"id\":123,\"type\":\"kdeconnect.test\",\"body\":{\"isTesting\":true,\"testName\":\"testSendPacketFail\"}}"); badLanLink.sendPacket(testPacket, callback); - Mockito.verify(callback).onFailure(Mockito.any(RuntimeException.class)); + Mockito.verify(callback).onFailure(Mockito.any(IOException.class)); } - + @Test public void testSendPayload() throws Exception { class Downloader extends Thread { NetworkPacket np; final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); void setNetworkPacket(NetworkPacket networkPacket) { this.np = networkPacket; } ByteArrayOutputStream getOutputStream() { return outputStream; } @Override public void run() { try { Socket socket = null; try { socket = new Socket(); int tcpPort = np.getPayloadTransferInfo().getInt("port"); InetSocketAddress address = new InetSocketAddress(5000); socket.connect(new InetSocketAddress(address.getAddress(), tcpPort)); - np.setPayload(socket.getInputStream(), np.getPayloadSize()); + np.setPayload(new NetworkPacket.Payload(socket.getInputStream(), np.getPayloadSize())); } catch (Exception e) { socket.close(); e.printStackTrace(); Log.e("KDE/LanLinkTest", "Exception connecting to remote socket"); throw e; } - final InputStream input = np.getPayload(); + final InputStream input = np.getPayload().getInputStream(); final long fileLength = np.getPayloadSize(); byte data[] = new byte[1024]; long progress = 0, prevProgressPercentage = 0; int count; while ((count = input.read(data)) >= 0) { progress += count; outputStream.write(data, 0, count); if (fileLength > 0) { if (progress >= fileLength) break; long progressPercentage = (progress * 100 / fileLength); if (progressPercentage != prevProgressPercentage) { prevProgressPercentage = progressPercentage; } } } outputStream.close(); input.close(); } catch (Exception e) { Log.e("Downloader Test", "Exception"); e.printStackTrace(); } } } final Downloader downloader = new Downloader(); // Using byte array for payload, try to use input stream as used in real device String dataString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + " Cras vel erat et ante fringilla tristique. Sed consequat ligula at interdum " + "rhoncus. Integer semper enim felis, id sodales tellus aliquet eget." + " Sed fringilla ac metus eget dictum. Aliquam euismod non sem sit" + " amet dapibus. Interdum et malesuada fames ac ante ipsum primis " + "in faucibus. Nam et ligula placerat, varius justo eu, convallis " + "lorem. Nam consequat consequat tortor et gravida. Praesent " + "ultricies tortor eget ex elementum gravida. Suspendisse aliquet " + "erat a orci feugiat dignissim."; // reallyLongString contains dataString 16 times String reallyLongString = dataString + dataString; reallyLongString = reallyLongString + reallyLongString; reallyLongString = reallyLongString + reallyLongString; reallyLongString = reallyLongString + reallyLongString; final byte[] data = reallyLongString.getBytes(); final JSONObject sharePacketJson = new JSONObject("{\"id\":123,\"body\":{\"filename\":\"data.txt\"},\"payloadTransferInfo\":{},\"payloadSize\":8720,\"type\":\"kdeconnect.share\"}"); // Mocking share package final NetworkPacket sharePacket = Mockito.mock(NetworkPacket.class); Mockito.when(sharePacket.getType()).thenReturn("kdeconnect.share"); Mockito.when(sharePacket.hasPayload()).thenReturn(true); Mockito.when(sharePacket.hasPayloadTransferInfo()).thenReturn(true); Mockito.doAnswer(invocationOnMock -> sharePacketJson.toString()).when(sharePacket).serialize(); - Mockito.when(sharePacket.getPayload()).thenReturn(new ByteArrayInputStream(data)); + Mockito.when(sharePacket.getPayload()).thenReturn(new NetworkPacket.Payload(new ByteArrayInputStream(data), -1)); Mockito.when(sharePacket.getPayloadSize()).thenReturn((long) data.length); Mockito.doAnswer(invocationOnMock -> sharePacketJson.getJSONObject("payloadTransferInfo")).when(sharePacket).getPayloadTransferInfo(); Mockito.doAnswer(invocationOnMock -> { JSONObject object = (JSONObject) invocationOnMock.getArguments()[0]; sharePacketJson.put("payloadTransferInfo", object); return null; }).when(sharePacket).setPayloadTransferInfo(Mockito.any(JSONObject.class)); Mockito.doAnswer(invocationOnMock -> { Log.e("LanLinkTest", "Write to stream"); String stringNetworkPacket = new String((byte[]) invocationOnMock.getArguments()[0]); final NetworkPacket np = NetworkPacket.unserialize(stringNetworkPacket); downloader.setNetworkPacket(np); downloader.start(); return stringNetworkPacket.length(); }).when(goodOutputStream).write(Mockito.any(byte[].class)); goodLanLink.sendPacket(sharePacket, callback); try { // Wait 1 secs for downloader to finish (if some error, it will continue and assert will fail) downloader.join(1000); } catch (Exception e) { e.printStackTrace(); throw e; } assertEquals(new String(data), new String(downloader.getOutputStream().toByteArray())); Mockito.verify(callback).onSuccess(); } } diff --git a/tests/org/kde/kdeconnect/MockSharedPreference.java b/tests/org/kde/kdeconnect/MockSharedPreference.java new file mode 100644 index 00000000..e8c0010c --- /dev/null +++ b/tests/org/kde/kdeconnect/MockSharedPreference.java @@ -0,0 +1,159 @@ +package org.kde.kdeconnect; + +import android.content.SharedPreferences; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import androidx.annotation.Nullable; + + +/** + * Mock implementation of shared preference, which just saves data in memory using map. + * + * It DOES NOT support transactions, changes are immediate + * + * From https://gist.github.com/amardeshbd/354173d00b988574ee5019c4ba0c8a0b + */ +public class MockSharedPreference implements SharedPreferences { + + private final HashMap preferenceMap; + private final MockSharedPreferenceEditor preferenceEditor; + + public MockSharedPreference() { + preferenceMap = new HashMap<>(); + preferenceEditor = new MockSharedPreferenceEditor(preferenceMap); + } + + @Override + public Map getAll() { + return preferenceMap; + } + + @Nullable + @Override + public String getString(final String s, @Nullable final String def) { + if (!preferenceMap.containsKey(s)) return def; + return (String) preferenceMap.get(s); + } + + @Nullable + @Override + public Set getStringSet(final String s, @Nullable final Set def) { + if (!preferenceMap.containsKey(s)) return def; + return (Set) preferenceMap.get(s); + } + + @Override + public int getInt(final String s, final int def) { + if (!preferenceMap.containsKey(s)) return def; + return (int) preferenceMap.get(s); + } + + @Override + public long getLong(final String s, final long def) { + if (!preferenceMap.containsKey(s)) return def; + return (long) preferenceMap.get(s); + } + + @Override + public float getFloat(final String s, final float def) { + if (!preferenceMap.containsKey(s)) return def; + return (float) preferenceMap.get(s); + } + + @Override + public boolean getBoolean(final String s, final boolean def) { + if (!preferenceMap.containsKey(s)) return def; + return (boolean) preferenceMap.get(s); + } + + @Override + public boolean contains(final String s) { + return preferenceMap.containsKey(s); + } + + @Override + public Editor edit() { + return preferenceEditor; + } + + @Override + public void registerOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { + + } + + @Override + public void unregisterOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { + + } + + public static class MockSharedPreferenceEditor implements Editor { + + private final HashMap preferenceMap; + + public MockSharedPreferenceEditor(final HashMap preferenceMap) { + this.preferenceMap = preferenceMap; + } + + @Override + public Editor putString(final String s, @Nullable final String s1) { + preferenceMap.put(s, s1); + return this; + } + + @Override + public Editor putStringSet(final String s, @Nullable final Set set) { + preferenceMap.put(s, set); + return this; + } + + @Override + public Editor putInt(final String s, final int i) { + preferenceMap.put(s, i); + return this; + } + + @Override + public Editor putLong(final String s, final long l) { + preferenceMap.put(s, l); + return this; + } + + @Override + public Editor putFloat(final String s, final float v) { + preferenceMap.put(s, v); + return this; + } + + @Override + public Editor putBoolean(final String s, final boolean b) { + preferenceMap.put(s, b); + return this; + } + + @Override + public Editor remove(final String s) { + preferenceMap.remove(s); + return this; + } + + @Override + public Editor clear() { + preferenceMap.clear(); + return this; + } + + @Override + public boolean commit() { + return true; + } + + @Override + public void apply() { + // Nothing to do, everything is saved in memory. + } + } + +} diff --git a/tests/org/kde/kdeconnect/NetworkPacketTest.java b/tests/org/kde/kdeconnect/NetworkPacketTest.java index 6a84aa6f..affe5bff 100644 --- a/tests/org/kde/kdeconnect/NetworkPacketTest.java +++ b/tests/org/kde/kdeconnect/NetworkPacketTest.java @@ -1,127 +1,98 @@ /* * Copyright 2015 Vineet Garg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.kde.kdeconnect; +import android.content.Context; import android.util.Log; import org.json.JSONException; -import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; -import org.skyscreamer.jsonassert.JSONAssert; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; - -class NetworkPacketTest extends AndroidTestCase { +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.kde.kdeconnect.Helpers.DeviceHelper; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({DeviceHelper.class, Log.class}) +public class NetworkPacketTest { + + @Before + public void setUp() { + PowerMockito.mockStatic(DeviceHelper.class); + PowerMockito.when(DeviceHelper.getDeviceId(any())).thenReturn("123"); + PowerMockito.when(DeviceHelper.getDeviceType(any())).thenReturn(Device.DeviceType.Phone); + + PowerMockito.mockStatic(Log.class); + } + @Test public void testNetworkPacket() throws JSONException { NetworkPacket np = new NetworkPacket("com.test"); np.set("hello", "hola"); assertEquals(np.getString("hello", "bye"), "hola"); np.set("hello", ""); assertEquals(np.getString("hello", "bye"), ""); assertEquals(np.getString("hi", "bye"), "bye"); np.set("foo", "bar"); String serialized = np.serialize(); NetworkPacket np2 = NetworkPacket.unserialize(serialized); assertEquals(np.getLong("id"), np2.getLong("id")); assertEquals(np.getString("type"), np2.getString("type")); assertEquals(np.getJSONArray("body"), np2.getJSONArray("body")); String json = "{\"id\":123,\"type\":\"test\",\"body\":{\"testing\":true}}"; np2 = NetworkPacket.unserialize(json); assertEquals(np2.getId(), 123); assertTrue(np2.getBoolean("testing")); assertFalse(np2.getBoolean("not_testing")); assertTrue(np2.getBoolean("not_testing", true)); } + @Test public void testIdentity() { - NetworkPacket np = NetworkPacket.createIdentityPacket(getContext()); + Context context = Mockito.mock(Context.class); + MockSharedPreference settings = new MockSharedPreference(); + Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(settings); - assertEquals(np.getInt("protocolVersion"), NetworkPacket.ProtocolVersion); + NetworkPacket np = NetworkPacket.createIdentityPacket(context); - } + assertEquals(np.getInt("protocolVersion"), NetworkPacket.ProtocolVersion); - public void testEncryption() throws JSONException { - NetworkPacket original = new NetworkPacket("com.test"); - original.set("hello", "hola"); - - NetworkPacket copy = NetworkPacket.unserialize(original.serialize()); - - NetworkPacket decrypted = new NetworkPacket(""); - - KeyPair keyPair; - try { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); - keyGen.initialize(2048); - keyPair = keyGen.genKeyPair(); - } catch (Exception e) { - e.printStackTrace(); - Log.e("KDE/initializeRsaKeys", "Exception"); - return; - } - - PrivateKey privateKey = keyPair.getPrivate(); - assertNotNull(privateKey); - - PublicKey publicKey = keyPair.getPublic(); - assertNotNull(publicKey); - - // Encrypt and decrypt np - assertEquals(original.getType(), "com.test"); - try { - NetworkPacket encrypted = RsaHelper.encrypt(original, publicKey); - assertEquals(encrypted.getType(), NetworkPacket.PACKET_TYPE_ENCRYPTED); - - decrypted = RsaHelper.decrypt(encrypted, privateKey); - assertEquals(decrypted.getType(), "com.test"); - - } catch (Exception e) { - e.printStackTrace(); - } - - // np should be equal to np2 - assertEquals(decrypted.getLong("id"), copy.getLong("id")); - assertEquals(decrypted.getType(), copy.getType()); - assertEquals(decrypted.getJSONArray("body"), copy.getJSONArray("body")); - - String json = "{\"body\":{\"nowPlaying\":\"A really long song name - A really long artist name\",\"player\":\"A really long player name\",\"the_meaning_of_life_the_universe_and_everything\":\"42\"},\"id\":945945945,\"type\":\"kdeconnect.a_really_really_long_package_type\"}\n"; - NetworkPacket longJsonNp = NetworkPacket.unserialize(json); - try { - NetworkPacket encrypted = RsaHelper.encrypt(longJsonNp, publicKey); - decrypted = RsaHelper.decrypt(encrypted, privateKey); - String decryptedJson = decrypted.serialize(); - JSONAssert.assertEquals(json, decryptedJson, true); - } catch (Exception e) { - e.printStackTrace(); - } } }