Add Espresso for testing

This commit is contained in:
Mohamed Zenadi 2017-07-30 19:37:29 +01:00 committed by Mohamed Zenadi
parent d347e8349e
commit b145dfcf7f
10 changed files with 213 additions and 83 deletions

View File

@ -12,6 +12,8 @@ android {
targetSdkVersion 25 targetSdkVersion 25
versionCode 88 versionCode 88
versionName "1.2.0.68" versionName "1.2.0.68"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
@ -55,21 +57,35 @@ android {
} }
dependencies { dependencies {
compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:appcompat-v7:25.4.0'
compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.android.support:recyclerview-v7:25.4.0'
compile 'com.android.support:cardview-v7:25.3.1' compile 'com.android.support:cardview-v7:25.4.0'
compile 'com.android.support:design:25.3.1' compile 'com.android.support:design:25.4.0'
compile 'com.android.support:support-annotations:25.4.0'
compile 'org.sufficientlysecure:openpgp-api:11.0' compile 'org.sufficientlysecure:openpgp-api:11.0'
compile 'com.nononsenseapps:filepicker:2.4.2' compile 'com.nononsenseapps:filepicker:2.4.2'
compile('org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r') { compile('org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r') {
exclude group: 'org.apache.httpcomponents', module: 'httpclient' exclude group: 'org.apache.httpcomponents', module: 'httpclient'
} }
compile 'com.jcraft:jsch:0.1.53' compile 'com.jcraft:jsch:0.1.54'
compile 'org.apache.commons:commons-io:1.3.2' compile 'org.apache.commons:commons-io:1.3.2'
compile 'com.jayway.android.robotium:robotium-solo:5.3.1' compile 'com.jayway.android.robotium:robotium-solo:5.3.1'
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support.constraint:constraint-layout:1.0.2'
// Testing-only dependencies
androidTestCompile 'junit:junit:4.12'
androidTestCompile 'org.mockito:mockito-core:2.8.47'
androidTestCompile 'com.android.support.test:runner:1.0.0'
androidTestCompile 'com.android.support.test:rules:1.0.0'
androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.0'
androidTestCompile 'com.android.support.test.espresso:espresso-intents:3.0.0'
} }
repositories { repositories {
mavenCentral() mavenCentral()
// temp. solution until we use use gradle 4.0
maven { url 'https://maven.google.com' }
} }

View File

@ -0,0 +1,97 @@
package com.zeapo.pwdstore
import android.content.Context
import android.content.Intent
import android.support.test.InstrumentationRegistry
import android.support.test.filters.LargeTest
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import android.util.Log
import com.zeapo.pwdstore.crypto.PgpActivity
import kotlinx.android.synthetic.main.decrypt_layout.*
import org.apache.commons.io.IOUtils
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@RunWith(AndroidJUnit4::class)
@LargeTest
class WelcomeActivityTest {
@Rule @JvmField
var mActivityRule: ActivityTestRule<PgpActivity> = ActivityTestRule(PgpActivity::class.java, true, false)
@Test
fun pathShouldDecompose() {
val path = "/data/my.app.com/files/store/cat1/name.gpg"
val repoPath = "/data/my.app.com/files/store"
assertEquals("/cat1/name.gpg", PgpActivity.getRelativePath(path, repoPath))
assertEquals("/cat1/", PgpActivity.getParentPath(path, repoPath))
assertEquals("name", PgpActivity.getName(path, repoPath))
assertEquals("name", PgpActivity.getName(path, "$repoPath/"))
}
@Test
fun activityShouldShowName() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val name = "name"
val parentPath = "/cat1/"
val repoPath = "${context.filesDir}/store/"
val path = "$repoPath/cat1/name.gpg"
val intent = Intent(context, PgpActivity::class.java)
intent.putExtra("OPERATION", "DECRYPT")
intent.putExtra("FILE_PATH", path)
intent.putExtra("REPO_PATH", repoPath)
copyAssets(context, "store", context.filesDir.absolutePath)
val activity: PgpActivity = mActivityRule.launchActivity(intent)
val categoryView = activity.crypto_password_category_decrypt
assertNotNull(categoryView)
assertEquals(parentPath, categoryView.text)
val nameView = activity.crypto_password_file
assertNotNull(nameView)
assertEquals(name, nameView.text)
}
companion object {
fun copyAssets(context: Context, source: String, destination: String) {
val assetManager = context.assets
val files: Array<String>? = assetManager.list(source)
files?.map { filename ->
val destPath = "$destination/$filename"
val sourcePath = "$source/$filename"
if (assetManager.list(filename).isNotEmpty()) {
File(destPath).mkdir()
copyAssets(context, "$source/$filename", destPath)
} else {
try {
val input = assetManager.open(sourcePath)
val outFile = File(destination, filename)
val output = FileOutputStream(outFile)
IOUtils.copy(input, output)
input.close()
output.flush()
output.close()
} catch (e: IOException) {
Log.e("tag", "Failed to copy asset file: " + filename, e)
}
}
}
}
}
}

View File

@ -37,24 +37,17 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
} }
var passwordEntry: PasswordEntry? = null var passwordEntry: PasswordEntry? = null
var api : OpenPgpApi? = null
val name: String by lazy { intent.getStringExtra("NAME") }
val repoPath: String by lazy { intent.getStringExtra("REPO_PATH") }
val path: String by lazy { intent.getStringExtra("FILE_PATH") }
val parentPath: String by lazy {
// when encrypting we pass "PARENT_PATH" as we do not have a file
if (operation == "ENCRYPT") intent.getStringExtra("PARENT_PATH")
else File(path).parentFile.absolutePath
}
val cat: String by lazy { parentPath.replace(repoPath, "") }
val operation: String by lazy { intent.getStringExtra("OPERATION") } val operation: String by lazy { intent.getStringExtra("OPERATION") }
val repoPath: String by lazy { intent.getStringExtra("REPO_PATH") }
val settings: SharedPreferences by lazy { val path: String by lazy { intent.getStringExtra("FILE_PATH") }
PreferenceManager.getDefaultSharedPreferences(this) val name: String by lazy { getName(path, repoPath) }
} val relativeParentPath: String by lazy { getParentPath(path, repoPath) }
val keyIDs: MutableSet<String> by lazy {
settings.getStringSet("openpgp_key_ids_set", emptySet()) val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
} val keyIDs: MutableSet<String> by lazy { settings.getStringSet("openpgp_key_ids_set", emptySet()) }
var mServiceConnection: OpenPgpServiceConnection? = null var mServiceConnection: OpenPgpServiceConnection? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -78,13 +71,13 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
when (operation) { when (operation) {
"DECRYPT", "EDIT" -> { "DECRYPT", "EDIT" -> {
setContentView(R.layout.decrypt_layout) setContentView(R.layout.decrypt_layout)
crypto_password_category_decrypt.text = "$cat/" crypto_password_category_decrypt.text = relativeParentPath
crypto_password_file.text = name crypto_password_file.text = name
} }
"ENCRYPT" -> { "ENCRYPT" -> {
setContentView(R.layout.encrypt_layout) setContentView(R.layout.encrypt_layout)
title = getString(R.string.new_password_title) title = getString(R.string.new_password_title)
crypto_password_category.text = "$cat/" crypto_password_category.text = relativeParentPath
} }
} }
} }
@ -178,6 +171,10 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
Log.e(TAG, "onError getMessage:" + error.message) Log.e(TAG, "onError getMessage:" + error.message)
} }
fun initOpenPgpApi() {
api = api ?: OpenPgpApi(this, mServiceConnection?.service)
}
private fun decryptAndVerify(receivedIntent: Intent? = null): Unit { private fun decryptAndVerify(receivedIntent: Intent? = null): Unit {
val data = receivedIntent ?: Intent() val data = receivedIntent ?: Intent()
data.action = ACTION_DECRYPT_VERIFY data.action = ACTION_DECRYPT_VERIFY
@ -185,64 +182,62 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val iStream = FileUtils.openInputStream(File(path)) val iStream = FileUtils.openInputStream(File(path))
val oStream = ByteArrayOutputStream() val oStream = ByteArrayOutputStream()
val api = OpenPgpApi(this, mServiceConnection?.service) api?.executeApiAsync(data, iStream, oStream, { result: Intent? ->
api.executeApiAsync(data, iStream, oStream, { result: Intent? ->
when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) { when (result?.getIntExtra(RESULT_CODE, RESULT_CODE_ERROR)) {
RESULT_CODE_SUCCESS -> { RESULT_CODE_SUCCESS -> {
try { try {
val showPassword = settings.getBoolean("show_password", true) // val showPassword = settings.getBoolean("show_password", true)
val showExtraContent = settings.getBoolean("show_extra_content", true) // val showExtraContent = settings.getBoolean("show_extra_content", true)
//
crypto_container_decrypt.visibility = View.VISIBLE // crypto_container_decrypt.visibility = View.VISIBLE
//
val monoTypeface = Typeface.createFromAsset(assets, "fonts/sourcecodepro.ttf") // val monoTypeface = Typeface.createFromAsset(assets, "fonts/sourcecodepro.ttf")
val entry = PasswordEntry(oStream) // val entry = PasswordEntry(oStream)
//
passwordEntry = entry // passwordEntry = entry
//
if (operation == "EDIT") { // if (operation == "EDIT") {
editPassword() // editPassword()
return@executeApiAsync // return@executeApiAsync
} // }
//
crypto_password_show.typeface = monoTypeface // crypto_password_show.typeface = monoTypeface
crypto_password_show.text = entry.password // crypto_password_show.text = entry.password
//
crypto_password_toggle_show.visibility = if (showPassword) View.GONE else View.VISIBLE // crypto_password_toggle_show.visibility = if (showPassword) View.GONE else View.VISIBLE
crypto_password_show.transformationMethod = if (showPassword) { // crypto_password_show.transformationMethod = if (showPassword) {
null // null
} else { // } else {
HoldToShowPasswordTransformation( // HoldToShowPasswordTransformation(
crypto_password_toggle_show, // crypto_password_toggle_show,
Runnable { crypto_password_show.text = entry.password } // Runnable { crypto_password_show.text = entry.password }
) // )
} // }
//
if (entry.hasExtraContent()) { // if (entry.hasExtraContent()) {
crypto_extra_show_layout.visibility = if (showExtraContent) View.VISIBLE else View.GONE // crypto_extra_show_layout.visibility = if (showExtraContent) View.VISIBLE else View.GONE
//
crypto_extra_show.typeface = monoTypeface // crypto_extra_show.typeface = monoTypeface
crypto_extra_show.text = entry.extraContent // crypto_extra_show.text = entry.extraContent
//
if (entry.hasUsername()) { // if (entry.hasUsername()) {
crypto_username_show.visibility = View.VISIBLE // crypto_username_show.visibility = View.VISIBLE
crypto_username_show_label.visibility = View.VISIBLE // crypto_username_show_label.visibility = View.VISIBLE
crypto_copy_username.visibility = View.VISIBLE // crypto_copy_username.visibility = View.VISIBLE
//
crypto_copy_username.setOnClickListener { copyUsernameToClipBoard(entry.username) } // crypto_copy_username.setOnClickListener { copyUsernameToClipBoard(entry.username) }
crypto_username_show.typeface = monoTypeface // crypto_username_show.typeface = monoTypeface
crypto_username_show.text = entry.username // crypto_username_show.text = entry.username
} else { // } else {
crypto_username_show.visibility = View.GONE // crypto_username_show.visibility = View.GONE
crypto_username_show_label.visibility = View.GONE // crypto_username_show_label.visibility = View.GONE
crypto_copy_username.visibility = View.GONE // crypto_copy_username.visibility = View.GONE
} // }
} // }
//
if (settings.getBoolean("copy_on_decrypt", true)) { // if (settings.getBoolean("copy_on_decrypt", true)) {
copyPasswordToClipBoard() // copyPasswordToClipBoard()
} // }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "An Exception occurred", e) Log.e(TAG, "An Exception occurred", e)
} }
@ -284,10 +279,9 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val iStream = ByteArrayInputStream("$pass\n$extra".toByteArray(Charset.forName("UTF-8"))) val iStream = ByteArrayInputStream("$pass\n$extra".toByteArray(Charset.forName("UTF-8")))
val oStream = ByteArrayOutputStream() val oStream = ByteArrayOutputStream()
val api = OpenPgpApi(this, mServiceConnection?.service) val path = "$repoPath/$relativeParentPath/$name.gpg"
val path = "$parentPath/$name.gpg"
api.executeApiAsync(data, iStream, oStream, { result: Intent? -> api?.executeApiAsync(data, iStream, oStream, { result: Intent? ->
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
OpenPgpApi.RESULT_CODE_SUCCESS -> { OpenPgpApi.RESULT_CODE_SUCCESS -> {
try { try {
@ -332,7 +326,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
crypto_extra_edit.setText(passwordEntry?.extraContent) crypto_extra_edit.setText(passwordEntry?.extraContent)
crypto_extra_edit.typeface = monoTypeface crypto_extra_edit.typeface = monoTypeface
crypto_password_category.text = "$cat/" crypto_password_category.text = relativeParentPath
crypto_password_file_edit.setText(name) crypto_password_file_edit.setText(name)
crypto_password_file_edit.isEnabled = false crypto_password_file_edit.isEnabled = false
@ -351,8 +345,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
fun getKeyIds(receivedIntent: Intent? = null) { fun getKeyIds(receivedIntent: Intent? = null) {
val data = receivedIntent ?: Intent() val data = receivedIntent ?: Intent()
data.action = OpenPgpApi.ACTION_GET_KEY_IDS data.action = OpenPgpApi.ACTION_GET_KEY_IDS
val api = OpenPgpApi(this, mServiceConnection?.service) api?.executeApiAsync(data, null, null, { result: Intent? ->
api.executeApiAsync(data, null, null, { result: Intent? ->
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
OpenPgpApi.RESULT_CODE_SUCCESS -> { OpenPgpApi.RESULT_CODE_SUCCESS -> {
try { try {
@ -382,6 +375,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
* The action to take when the PGP service is bound * The action to take when the PGP service is bound
*/ */
override fun onBound(service: IOpenPgpService2?) { override fun onBound(service: IOpenPgpService2?) {
initOpenPgpApi()
when (operation) { when (operation) {
"EDIT", "DECRYPT" -> decryptAndVerify() "EDIT", "DECRYPT" -> decryptAndVerify()
"GET_KEY_ID" -> getKeyIds() "GET_KEY_ID" -> getKeyIds()
@ -572,6 +566,29 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
private var delayTask: DelayShow? = null private var delayTask: DelayShow? = null
/**
* Gets the relative path to the repository
*/
fun getRelativePath(fullPath: String, repositoryPath: String): String =
fullPath.replace(repositoryPath, "").replace("//", "/")
/**
* Gets the Parent path, relative to the repository
*/
fun getParentPath(fullPath: String, repositoryPath: String) : String {
val relativePath = getRelativePath(fullPath, repositoryPath)
val index = relativePath.lastIndexOf("/")
return "/${relativePath.substring(startIndex = 0, endIndex = index + 1)}/".replace("//", "/")
}
/**
* Gets the name of the password (excluding .gpg)
*/
fun getName(fullPath: String, repositoryPath: String) : String {
val relativePath = getRelativePath(fullPath, repositoryPath)
val index = relativePath.lastIndexOf("/")
return relativePath.substring(index + 1).replace("\\.gpg$".toRegex(), "")
}
} }
} }