Added repository tests and fixed some bugs
This commit is contained in:
parent
696cd6c0a6
commit
287de9deb5
@ -85,6 +85,11 @@ dependencies {
|
|||||||
|
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:2.8.9'
|
testCompile 'org.mockito:mockito-core:2.8.9'
|
||||||
|
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||||
|
testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
testCompile 'com.nhaarman:mockito-kotlin-kt1.1:1.5.0'
|
||||||
|
testCompile 'org.robolectric:robolectric:3.4.2'
|
||||||
|
testCompile "org.robolectric:shadows-multidex:3.4-rc2"
|
||||||
}
|
}
|
||||||
|
|
||||||
idea.module {
|
idea.module {
|
||||||
|
@ -113,7 +113,7 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
|
|||||||
where.append(" AND version = ").append(version)
|
where.append(" AND version = ").append(version)
|
||||||
}
|
}
|
||||||
if (types.isNotEmpty()) {
|
if (types.isNotEmpty()) {
|
||||||
where.append(" AND type IN (").append(join(*types)).append(")")
|
where.append(" AND type IN (").append(types.joinToString(separator = "', '", prefix = "'", postfix = "'", transform = { it.number.toString() })).append(")")
|
||||||
}
|
}
|
||||||
|
|
||||||
val db = sql.readableDatabase
|
val db = sql.readableDatabase
|
||||||
|
@ -148,8 +148,9 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
}
|
}
|
||||||
val result = LinkedList<UUID>()
|
val result = LinkedList<UUID>()
|
||||||
sql.readableDatabase.query(
|
sql.readableDatabase.query(
|
||||||
TABLE_NAME, projection,
|
true,
|
||||||
where, null, null, null, null
|
TABLE_NAME, projection, where,
|
||||||
|
null, null, null, null, null
|
||||||
).use { c ->
|
).use { c ->
|
||||||
while (c.moveToNext()) {
|
while (c.moveToNext()) {
|
||||||
val uuidBytes = c.getBlob(c.getColumnIndex(COLUMN_CONVERSATION))
|
val uuidBytes = c.getBlob(c.getColumnIndex(COLUMN_CONVERSATION))
|
||||||
@ -172,8 +173,7 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
// save new parents
|
// save new parents
|
||||||
var order = 0
|
var order = 0
|
||||||
for (parentIV in message.parents) {
|
for (parentIV in message.parents) {
|
||||||
val parent = getMessage(parentIV)
|
getMessage(parentIV)?.let { parent ->
|
||||||
if (parent != null) {
|
|
||||||
mergeConversations(db, parent.conversationId, message.conversationId)
|
mergeConversations(db, parent.conversationId, message.conversationId)
|
||||||
order++
|
order++
|
||||||
val values = ContentValues()
|
val values = ContentValues()
|
||||||
@ -198,9 +198,9 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
|
|||||||
private fun mergeConversations(db: SQLiteDatabase, source: UUID, target: UUID) {
|
private fun mergeConversations(db: SQLiteDatabase, source: UUID, target: UUID) {
|
||||||
val values = ContentValues()
|
val values = ContentValues()
|
||||||
values.put("conversation", UuidUtils.asBytes(target))
|
values.put("conversation", UuidUtils.asBytes(target))
|
||||||
val whereArgs = arrayOf(hex(UuidUtils.asBytes(source)))
|
val where = "conversation=X'${hex(UuidUtils.asBytes(source))}'"
|
||||||
db.update(TABLE_NAME, values, "conversation=?", whereArgs)
|
db.update(TABLE_NAME, values, where, null)
|
||||||
db.update(PARENTS_TABLE_NAME, values, "conversation=?", whereArgs)
|
db.update(PARENTS_TABLE_NAME, values, where, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findIds(where: String): List<Long> {
|
private fun findIds(where: String): List<Long> {
|
||||||
|
@ -99,6 +99,6 @@ class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME,
|
|||||||
private val DATABASE_VERSION = 7
|
private val DATABASE_VERSION = 7
|
||||||
val DATABASE_NAME = "jabit.db"
|
val DATABASE_NAME = "jabit.db"
|
||||||
|
|
||||||
internal fun join(vararg types: Enum<*>): String = types.joinToString(separator = "', '", prefix = "'", postfix = "'", transform = {it.name})
|
internal fun join(vararg types: Enum<*>): String = types.joinToString(separator = "', '", prefix = "'", postfix = "'", transform = { it.name })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.repository
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import ch.dissem.apps.abit.BuildConfig
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidAddressRepository
|
||||||
|
import ch.dissem.apps.abit.repository.SqlHelper
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.RuntimeEnvironment
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit")
|
||||||
|
class AndroidAddressRepositoryTest : TestBase() {
|
||||||
|
private val CONTACT_A = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt"
|
||||||
|
private val CONTACT_B = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj"
|
||||||
|
private val CONTACT_C = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke"
|
||||||
|
|
||||||
|
private lateinit var IDENTITY_A: String
|
||||||
|
private lateinit var IDENTITY_B: String
|
||||||
|
|
||||||
|
private lateinit var repo: AndroidAddressRepository
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
RuntimeEnvironment.application.deleteDatabase(SqlHelper.DATABASE_NAME)
|
||||||
|
val sqlHelper = SqlHelper(RuntimeEnvironment.application)
|
||||||
|
|
||||||
|
repo = AndroidAddressRepository(sqlHelper)
|
||||||
|
|
||||||
|
repo.save(BitmessageAddress(CONTACT_A))
|
||||||
|
repo.save(BitmessageAddress(CONTACT_B))
|
||||||
|
repo.save(BitmessageAddress(CONTACT_C))
|
||||||
|
|
||||||
|
val identityA = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK))
|
||||||
|
repo.save(identityA)
|
||||||
|
IDENTITY_A = identityA.address
|
||||||
|
val identityB = BitmessageAddress(PrivateKey(false, 1, 1000, 1000))
|
||||||
|
repo.save(identityB)
|
||||||
|
IDENTITY_B = identityB.address
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure contact can be found`() {
|
||||||
|
val address = BitmessageAddress(CONTACT_A)
|
||||||
|
assertEquals(4, address.version)
|
||||||
|
assertEquals(address, repo.findContact(address.tag!!))
|
||||||
|
assertNull(repo.findIdentity(address.tag!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure identity can be found`() {
|
||||||
|
val identity = BitmessageAddress(IDENTITY_A)
|
||||||
|
assertEquals(4, identity.version)
|
||||||
|
assertNull(repo.findContact(identity.tag!!))
|
||||||
|
|
||||||
|
val storedIdentity = repo.findIdentity(identity.tag!!)
|
||||||
|
assertEquals(identity, storedIdentity)
|
||||||
|
assertTrue(storedIdentity!!.has(DOES_ACK))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure identities are retrieved`() {
|
||||||
|
val identities = repo.getIdentities()
|
||||||
|
assertEquals(2, identities.size.toLong())
|
||||||
|
for (identity in identities) {
|
||||||
|
assertNotNull(identity.privateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure subscriptions are retrieved`() {
|
||||||
|
addSubscription("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
||||||
|
addSubscription("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
||||||
|
addSubscription("BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh")
|
||||||
|
val subscriptions = repo.getSubscriptions()
|
||||||
|
assertEquals(3, subscriptions.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure subscriptions are retrieved for given version`() {
|
||||||
|
addSubscription("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
||||||
|
addSubscription("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")
|
||||||
|
addSubscription("BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh")
|
||||||
|
|
||||||
|
var subscriptions = repo.getSubscriptions(5)
|
||||||
|
|
||||||
|
assertEquals(1, subscriptions.size.toLong())
|
||||||
|
|
||||||
|
subscriptions = repo.getSubscriptions(4)
|
||||||
|
assertEquals(2, subscriptions.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure contacts are retrieved`() {
|
||||||
|
val contacts = repo.getContacts()
|
||||||
|
assertEquals(3, contacts.size.toLong())
|
||||||
|
for (contact in contacts) {
|
||||||
|
assertNull(contact.privateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure new address is saved`() {
|
||||||
|
repo.save(BitmessageAddress(PrivateKey(false, 1, 1000, 1000)))
|
||||||
|
val identities = repo.getIdentities()
|
||||||
|
assertEquals(3, identities.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure existing address is updated`() {
|
||||||
|
var address = repo.getAddress(CONTACT_A)
|
||||||
|
address!!.alias = "Test-Alias"
|
||||||
|
repo.save(address)
|
||||||
|
address = repo.getAddress(address.address)
|
||||||
|
assertEquals("Test-Alias", address!!.alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure existing keys are not deleted`() {
|
||||||
|
val address = BitmessageAddress(IDENTITY_A)
|
||||||
|
address.alias = "Test"
|
||||||
|
repo.save(address)
|
||||||
|
val identityA = repo.getAddress(IDENTITY_A)
|
||||||
|
assertNotNull(identityA!!.pubkey)
|
||||||
|
assertNotNull(identityA.privateKey)
|
||||||
|
assertEquals("Test", identityA.alias)
|
||||||
|
assertFalse(identityA.isChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure new chan is saved and updated`() {
|
||||||
|
val chan = BitmessageAddress.chan(1, "test")
|
||||||
|
repo.save(chan)
|
||||||
|
var address = repo.getAddress(chan.address)
|
||||||
|
assertNotNull(address)
|
||||||
|
assertTrue(address!!.isChan)
|
||||||
|
|
||||||
|
address.alias = "Test"
|
||||||
|
repo.save(address)
|
||||||
|
|
||||||
|
address = repo.getAddress(chan.address)
|
||||||
|
assertNotNull(address)
|
||||||
|
assertTrue(address!!.isChan)
|
||||||
|
assertEquals("Test", address.alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure address is removed`() {
|
||||||
|
val address = repo.getAddress(IDENTITY_A)
|
||||||
|
repo.remove(address!!)
|
||||||
|
assertNull(repo.getAddress(IDENTITY_A))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure address can be retrieved`() {
|
||||||
|
val address = repo.getAddress(IDENTITY_A)
|
||||||
|
assertNotNull(address)
|
||||||
|
assertNotNull(address!!.privateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addSubscription(address: String) {
|
||||||
|
val subscription = BitmessageAddress(address)
|
||||||
|
subscription.isSubscribed = true
|
||||||
|
repo.save(subscription)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.repository
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import ch.dissem.apps.abit.BuildConfig
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidInventory
|
||||||
|
import ch.dissem.apps.abit.repository.SqlHelper
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.payload.GetPubkey
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectPayload
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectType.GET_PUBKEY
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectType.MSG
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.ports.Inventory
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.RuntimeEnvironment
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit")
|
||||||
|
class AndroidInventoryTest : TestBase() {
|
||||||
|
private lateinit var inventory: Inventory
|
||||||
|
|
||||||
|
private lateinit var inventoryVector1: InventoryVector
|
||||||
|
private lateinit var inventoryVector2: InventoryVector
|
||||||
|
private lateinit var inventoryVectorIgnore: InventoryVector
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
RuntimeEnvironment.application.deleteDatabase(SqlHelper.DATABASE_NAME)
|
||||||
|
val sqlHelper = SqlHelper(RuntimeEnvironment.application)
|
||||||
|
|
||||||
|
inventory = AndroidInventory(sqlHelper)
|
||||||
|
|
||||||
|
val object1 = getObjectMessage(1, 300, getPubkey)
|
||||||
|
inventoryVector1 = object1.inventoryVector
|
||||||
|
inventory.storeObject(object1)
|
||||||
|
|
||||||
|
val object2 = getObjectMessage(2, 300, getPubkey)
|
||||||
|
inventoryVector2 = object2.inventoryVector
|
||||||
|
inventory.storeObject(object2)
|
||||||
|
|
||||||
|
val ignore = getObjectMessage(1, -1 * DAY, getPubkey)
|
||||||
|
inventoryVectorIgnore = ignore.inventoryVector
|
||||||
|
inventory.storeObject(ignore)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure inventory can be retrieved`() {
|
||||||
|
var inventoryVectors = inventory.getInventory(1)
|
||||||
|
assertEquals(1, inventoryVectors.size.toLong())
|
||||||
|
|
||||||
|
inventoryVectors = inventory.getInventory(2)
|
||||||
|
assertEquals(1, inventoryVectors.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure the IVs of missing objects are returned`() {
|
||||||
|
val newIV = getObjectMessage(1, 200, getPubkey).inventoryVector
|
||||||
|
val offer = LinkedList<InventoryVector>()
|
||||||
|
offer.add(newIV)
|
||||||
|
offer.add(inventoryVector1)
|
||||||
|
val missing = inventory.getMissing(offer, 1, 2)
|
||||||
|
assertEquals(1, missing.size.toLong())
|
||||||
|
assertEquals(newIV, missing[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure single object can be retrieved`() {
|
||||||
|
val `object` = inventory.getObject(inventoryVectorIgnore)
|
||||||
|
assertNotNull(`object`)
|
||||||
|
assertEquals(1, `object`!!.stream)
|
||||||
|
assertEquals(inventoryVectorIgnore, `object`.inventoryVector)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure objects can be retrieved`() {
|
||||||
|
var objects = inventory.getObjects(1, 4)
|
||||||
|
assertEquals(2, objects.size.toLong())
|
||||||
|
|
||||||
|
objects = inventory.getObjects(1, 4, GET_PUBKEY)
|
||||||
|
assertEquals(2, objects.size.toLong())
|
||||||
|
|
||||||
|
objects = inventory.getObjects(1, 4, MSG)
|
||||||
|
assertEquals(0, objects.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure object can be stored`() {
|
||||||
|
val `object` = getObjectMessage(5, 0, getPubkey)
|
||||||
|
inventory.storeObject(`object`)
|
||||||
|
|
||||||
|
assertNotNull(inventory.getObject(`object`.inventoryVector))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure contained objects are recognized`() {
|
||||||
|
val `object` = getObjectMessage(5, 0, getPubkey)
|
||||||
|
|
||||||
|
assertFalse(inventory.contains(`object`))
|
||||||
|
|
||||||
|
inventory.storeObject(`object`)
|
||||||
|
|
||||||
|
assertTrue(inventory.contains(`object`))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure inventory is cleaned up`() {
|
||||||
|
assertNotNull(inventory.getObject(inventoryVectorIgnore))
|
||||||
|
inventory.cleanup()
|
||||||
|
assertNull(inventory.getObject(inventoryVectorIgnore))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload): ObjectMessage {
|
||||||
|
return ObjectMessage(
|
||||||
|
nonce = ByteArray(8),
|
||||||
|
expiresTime = now + TTL,
|
||||||
|
stream = stream,
|
||||||
|
payload = payload
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val getPubkey: GetPubkey = GetPubkey(BitmessageAddress("BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt"))
|
||||||
|
}
|
@ -0,0 +1,346 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.repository
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import ch.dissem.apps.abit.BuildConfig
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidAddressRepository
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidMessageRepository
|
||||||
|
import ch.dissem.apps.abit.repository.SqlHelper
|
||||||
|
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.Label
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.extended.Message
|
||||||
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import org.hamcrest.BaseMatcher
|
||||||
|
import org.hamcrest.CoreMatchers.`is`
|
||||||
|
import org.hamcrest.Description
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
import org.hamcrest.Matchers.*
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.RuntimeEnvironment
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit")
|
||||||
|
class AndroidMessageRepositoryTest : TestBase() {
|
||||||
|
private lateinit var contactA: BitmessageAddress
|
||||||
|
private lateinit var contactB: BitmessageAddress
|
||||||
|
private lateinit var identity: BitmessageAddress
|
||||||
|
|
||||||
|
private lateinit var repo: MessageRepository
|
||||||
|
|
||||||
|
private lateinit var inbox: Label
|
||||||
|
private lateinit var sent: Label
|
||||||
|
private lateinit var drafts: Label
|
||||||
|
private lateinit var unread: Label
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
RuntimeEnvironment.application.deleteDatabase(SqlHelper.DATABASE_NAME)
|
||||||
|
val sqlHelper = SqlHelper(RuntimeEnvironment.application)
|
||||||
|
|
||||||
|
val addressRepo = AndroidAddressRepository(sqlHelper)
|
||||||
|
repo = AndroidMessageRepository(sqlHelper, RuntimeEnvironment.application)
|
||||||
|
mockedInternalContext(
|
||||||
|
cryptography = SpongyCryptography(),
|
||||||
|
addressRepository = addressRepo,
|
||||||
|
messageRepository = repo,
|
||||||
|
port = 12345,
|
||||||
|
connectionTTL = 10,
|
||||||
|
connectionLimit = 10
|
||||||
|
)
|
||||||
|
val tmp = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK))
|
||||||
|
contactA = BitmessageAddress(tmp.address)
|
||||||
|
contactA.pubkey = tmp.pubkey
|
||||||
|
addressRepo.save(contactA)
|
||||||
|
contactB = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj")
|
||||||
|
addressRepo.save(contactB)
|
||||||
|
|
||||||
|
identity = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK))
|
||||||
|
addressRepo.save(identity)
|
||||||
|
|
||||||
|
inbox = repo.getLabels(Label.Type.INBOX)[0]
|
||||||
|
sent = repo.getLabels(Label.Type.SENT)[0]
|
||||||
|
drafts = repo.getLabels(Label.Type.DRAFT)[0]
|
||||||
|
unread = repo.getLabels(Label.Type.UNREAD)[0]
|
||||||
|
|
||||||
|
addMessage(contactA, identity, Plaintext.Status.RECEIVED, inbox, unread)
|
||||||
|
addMessage(identity, contactA, Plaintext.Status.DRAFT, drafts)
|
||||||
|
addMessage(identity, contactB, Plaintext.Status.DRAFT, unread)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure labels are retrieved`() {
|
||||||
|
val labels = repo.getLabels()
|
||||||
|
assertEquals(7, labels.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure labels can be retrieved by type`() {
|
||||||
|
val labels = repo.getLabels(Label.Type.INBOX)
|
||||||
|
assertEquals(1, labels.size.toLong())
|
||||||
|
assertEquals("Inbox", labels[0].toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure messages can be found by label`() {
|
||||||
|
val messages = repo.findMessages(inbox)
|
||||||
|
assertEquals(1, messages.size.toLong())
|
||||||
|
val m = messages[0]
|
||||||
|
assertEquals(contactA, m.from)
|
||||||
|
assertEquals(identity, m.to)
|
||||||
|
assertEquals(Plaintext.Status.RECEIVED, m.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure unread messages can be found for all labels`() {
|
||||||
|
val unread = repo.countUnread(null)
|
||||||
|
assertThat(unread, `is`(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure unread messages can be found by label`() {
|
||||||
|
val unread = repo.countUnread(inbox)
|
||||||
|
assertThat(unread, `is`(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure message can be retrieved by initial hash`() {
|
||||||
|
val initialHash = ByteArray(64)
|
||||||
|
val message = repo.findMessages(contactA)[0]
|
||||||
|
message.initialHash = initialHash
|
||||||
|
repo.save(message)
|
||||||
|
val other = repo.getMessage(initialHash)
|
||||||
|
assertThat<Plaintext>(other, `is`(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure ack message can be updated and retrieved`() {
|
||||||
|
val initialHash = ByteArray(64)
|
||||||
|
val message = repo.findMessages(contactA)[0]
|
||||||
|
message.initialHash = initialHash
|
||||||
|
val ackMessage = message.ackMessage
|
||||||
|
repo.save(message)
|
||||||
|
val other = repo.getMessage(initialHash)!!
|
||||||
|
assertThat<Plaintext>(other, `is`(message))
|
||||||
|
assertThat<ObjectMessage>(other.ackMessage, `is`<ObjectMessage>(ackMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure messages can be found by status`() {
|
||||||
|
val messages = repo.findMessages(Plaintext.Status.RECEIVED)
|
||||||
|
assertEquals(1, messages.size.toLong())
|
||||||
|
val m = messages[0]
|
||||||
|
assertEquals(contactA, m.from)
|
||||||
|
assertEquals(identity, m.to)
|
||||||
|
assertEquals(Plaintext.Status.RECEIVED, m.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure messages can be found by status and recipient`() {
|
||||||
|
val messages = repo.findMessages(Plaintext.Status.DRAFT, contactB)
|
||||||
|
assertEquals(1, messages.size.toLong())
|
||||||
|
val m = messages[0]
|
||||||
|
assertEquals(identity, m.from)
|
||||||
|
assertEquals(contactB, m.to)
|
||||||
|
assertEquals(Plaintext.Status.DRAFT, m.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure message can be saved`() {
|
||||||
|
val message = Plaintext.Builder(MSG)
|
||||||
|
.IV(randomInventoryVector())
|
||||||
|
.from(identity)
|
||||||
|
.to(contactA)
|
||||||
|
.message("Subject", "Message")
|
||||||
|
.status(Plaintext.Status.DOING_PROOF_OF_WORK)
|
||||||
|
.build()
|
||||||
|
repo.save(message)
|
||||||
|
|
||||||
|
assertNotNull(message.id)
|
||||||
|
|
||||||
|
message.addLabels(inbox)
|
||||||
|
repo.save(message)
|
||||||
|
|
||||||
|
val messages = repo.findMessages(Plaintext.Status.DOING_PROOF_OF_WORK)
|
||||||
|
|
||||||
|
assertEquals(1, messages.size.toLong())
|
||||||
|
assertNotNull(messages[0].inventoryVector)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure message can be updated`() {
|
||||||
|
var messages = repo.findMessages(Plaintext.Status.DRAFT, contactA)
|
||||||
|
val message = messages[0]
|
||||||
|
message.inventoryVector = randomInventoryVector()
|
||||||
|
repo.save(message)
|
||||||
|
|
||||||
|
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA)
|
||||||
|
assertEquals(1, messages.size.toLong())
|
||||||
|
assertNotNull(messages[0].inventoryVector)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure message is removed`() {
|
||||||
|
val toRemove = repo.findMessages(Plaintext.Status.DRAFT, contactB)[0]
|
||||||
|
var messages = repo.findMessages(Plaintext.Status.DRAFT)
|
||||||
|
assertEquals(2, messages.size.toLong())
|
||||||
|
repo.remove(toRemove)
|
||||||
|
messages = repo.findMessages(Plaintext.Status.DRAFT)
|
||||||
|
assertThat(messages, hasSize<Plaintext>(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure unacknowledged messages are found for resend`() {
|
||||||
|
val message = Plaintext.Builder(MSG)
|
||||||
|
.IV(randomInventoryVector())
|
||||||
|
.from(identity)
|
||||||
|
.to(contactA)
|
||||||
|
.message("Subject", "Message")
|
||||||
|
.sent(UnixTime.now)
|
||||||
|
.status(Plaintext.Status.SENT)
|
||||||
|
.ttl(2)
|
||||||
|
.build()
|
||||||
|
message.updateNextTry()
|
||||||
|
assertThat(message.retries, `is`(1))
|
||||||
|
assertThat<Long>(message.nextTry, greaterThan(UnixTime.now))
|
||||||
|
assertThat<Long>(message.nextTry, lessThanOrEqualTo(UnixTime.now + 2))
|
||||||
|
repo.save(message)
|
||||||
|
Thread.sleep(4100) // somewhat longer than 2*TTL
|
||||||
|
var messagesToResend = repo.findMessagesToResend()
|
||||||
|
assertThat(messagesToResend, hasSize<Plaintext>(1))
|
||||||
|
|
||||||
|
message.updateNextTry()
|
||||||
|
assertThat(message.retries, `is`(2))
|
||||||
|
assertThat<Long>(message.nextTry, greaterThan(UnixTime.now))
|
||||||
|
repo.save(message)
|
||||||
|
messagesToResend = repo.findMessagesToResend()
|
||||||
|
assertThat(messagesToResend, empty<Plaintext>())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure parents are saved`() {
|
||||||
|
val parent = storeConversation()
|
||||||
|
|
||||||
|
val responses = repo.findResponses(parent)
|
||||||
|
assertThat(responses, hasSize<Plaintext>(2))
|
||||||
|
assertThat(responses, hasItem(hasMessage("Re: new test", "Nice!")))
|
||||||
|
assertThat(responses, hasItem(hasMessage("Re: new test", "PS: it did work!")))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure conversation can be retrieved`() {
|
||||||
|
val root = storeConversation()
|
||||||
|
val conversations = repo.findConversations(inbox)
|
||||||
|
assertThat(conversations, hasSize<UUID>(2))
|
||||||
|
assertThat(conversations, hasItem(root.conversationId))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addMessage(from: BitmessageAddress, to: BitmessageAddress, status: Plaintext.Status, vararg labels: Label): Plaintext {
|
||||||
|
val content = Message.Builder()
|
||||||
|
.subject("Subject")
|
||||||
|
.body("Message")
|
||||||
|
.build()
|
||||||
|
return addMessage(from, to, content, status, *labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addMessage(from: BitmessageAddress, to: BitmessageAddress,
|
||||||
|
content: ExtendedEncoding, status: Plaintext.Status, vararg labels: Label): Plaintext {
|
||||||
|
val message = Plaintext.Builder(MSG)
|
||||||
|
.IV(randomInventoryVector())
|
||||||
|
.from(from)
|
||||||
|
.to(to)
|
||||||
|
.message(content)
|
||||||
|
.status(status)
|
||||||
|
.labels(Arrays.asList(*labels))
|
||||||
|
.build()
|
||||||
|
repo.save(message)
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeConversation(): Plaintext {
|
||||||
|
val older = addMessage(identity, contactA,
|
||||||
|
Message.Builder()
|
||||||
|
.subject("hey there")
|
||||||
|
.body("does it work?")
|
||||||
|
.build(),
|
||||||
|
Plaintext.Status.SENT, sent)
|
||||||
|
|
||||||
|
val root = addMessage(identity, contactA,
|
||||||
|
Message.Builder()
|
||||||
|
.subject("new test")
|
||||||
|
.body("There's a new test in town!")
|
||||||
|
.build(),
|
||||||
|
Plaintext.Status.SENT, sent)
|
||||||
|
|
||||||
|
addMessage(contactA, identity,
|
||||||
|
Message.Builder()
|
||||||
|
.subject("Re: new test")
|
||||||
|
.body("Nice!")
|
||||||
|
.addParent(root)
|
||||||
|
.build(),
|
||||||
|
Plaintext.Status.RECEIVED, inbox)
|
||||||
|
|
||||||
|
addMessage(contactA, identity,
|
||||||
|
Message.Builder()
|
||||||
|
.subject("Re: new test")
|
||||||
|
.body("PS: it did work!")
|
||||||
|
.addParent(root)
|
||||||
|
.addParent(older)
|
||||||
|
.build(),
|
||||||
|
Plaintext.Status.RECEIVED, inbox)
|
||||||
|
|
||||||
|
return repo.getMessage(root.id!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasMessage(subject: String?, body: String?): Matcher<Plaintext> {
|
||||||
|
return object : BaseMatcher<Plaintext>() {
|
||||||
|
override fun describeTo(description: Description) {
|
||||||
|
description.appendText("Subject: ").appendText(subject)
|
||||||
|
description.appendText(", ")
|
||||||
|
description.appendText("Body: ").appendText(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matches(item: Any): Boolean {
|
||||||
|
if (item is Plaintext) {
|
||||||
|
if (subject != null && subject != item.subject) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (body != null && body != item.text) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 Christian Basler
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.repository
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import ch.dissem.apps.abit.BuildConfig
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidNodeRegistry
|
||||||
|
import ch.dissem.apps.abit.repository.SqlHelper
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||||
|
import ch.dissem.bitmessage.ports.NodeRegistry
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
|
import org.hamcrest.Matchers.empty
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.RuntimeEnvironment
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please note that some tests fail if there is no internet connection,
|
||||||
|
* as the initial nodes' IP addresses are determined by DNS lookup.
|
||||||
|
*/
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), packageName = "ch.dissem.apps.abit")
|
||||||
|
class AndroidNodeRegistryTest : TestBase() {
|
||||||
|
private lateinit var registry: NodeRegistry
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
RuntimeEnvironment.application.deleteDatabase(SqlHelper.DATABASE_NAME)
|
||||||
|
val sqlHelper = SqlHelper(RuntimeEnvironment.application)
|
||||||
|
|
||||||
|
registry = AndroidNodeRegistry(sqlHelper)
|
||||||
|
|
||||||
|
registry.offerAddresses(Arrays.asList(
|
||||||
|
createAddress(1, 8444, 1, now),
|
||||||
|
createAddress(2, 8444, 1, now),
|
||||||
|
createAddress(3, 8444, 1, now),
|
||||||
|
createAddress(4, 8444, 2, now)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure getKnownNodes() without streams yields empty`() {
|
||||||
|
assertThat(registry.getKnownAddresses(10), empty<NetworkAddress>())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure predefined node is returned when database is empty`() {
|
||||||
|
RuntimeEnvironment.application.deleteDatabase(SqlHelper.DATABASE_NAME)
|
||||||
|
|
||||||
|
val sqlHelper = SqlHelper(RuntimeEnvironment.application)
|
||||||
|
registry = AndroidNodeRegistry(sqlHelper)
|
||||||
|
|
||||||
|
val knownAddresses = registry.getKnownAddresses(2, 1)
|
||||||
|
assertEquals(1, knownAddresses.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure known addresses are retrieved`() {
|
||||||
|
var knownAddresses = registry.getKnownAddresses(2, 1)
|
||||||
|
assertEquals(2, knownAddresses.size.toLong())
|
||||||
|
|
||||||
|
knownAddresses = registry.getKnownAddresses(1000, 1)
|
||||||
|
assertEquals(3, knownAddresses.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure offered addresses are added`() {
|
||||||
|
registry.offerAddresses(Arrays.asList(
|
||||||
|
createAddress(1, 8444, 1, now),
|
||||||
|
createAddress(10, 8444, 1, now),
|
||||||
|
createAddress(11, 8444, 1, now)
|
||||||
|
))
|
||||||
|
|
||||||
|
var knownAddresses = registry.getKnownAddresses(1000, 1)
|
||||||
|
assertEquals(5, knownAddresses.size.toLong())
|
||||||
|
|
||||||
|
registry.offerAddresses(listOf(createAddress(1, 8445, 1, now)))
|
||||||
|
|
||||||
|
knownAddresses = registry.getKnownAddresses(1000, 1)
|
||||||
|
assertEquals(6, knownAddresses.size.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAddress(lastByte: Int, port: Int, stream: Long, time: Long): NetworkAddress {
|
||||||
|
return NetworkAddress.Builder()
|
||||||
|
.ipv6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, lastByte)
|
||||||
|
.port(port)
|
||||||
|
.stream(stream)
|
||||||
|
.time(time)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.repository
|
||||||
|
|
||||||
|
import android.os.Build.VERSION_CODES.LOLLIPOP
|
||||||
|
import ch.dissem.apps.abit.BuildConfig
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidAddressRepository
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidMessageRepository
|
||||||
|
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository
|
||||||
|
import ch.dissem.apps.abit.repository.SqlHelper
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.Plaintext
|
||||||
|
import ch.dissem.bitmessage.entity.payload.GenericPayload
|
||||||
|
import ch.dissem.bitmessage.entity.payload.GetPubkey
|
||||||
|
import ch.dissem.bitmessage.entity.payload.ObjectPayload
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.ports.AddressRepository
|
||||||
|
import ch.dissem.bitmessage.ports.MessageRepository
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
||||||
|
import ch.dissem.bitmessage.utils.Singleton.cryptography
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime
|
||||||
|
import org.hamcrest.CoreMatchers.*
|
||||||
|
import org.junit.Assert.assertThat
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.RuntimeEnvironment
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(constants = BuildConfig::class, sdk = intArrayOf(LOLLIPOP), packageName = "ch.dissem.apps.abit")
|
||||||
|
class AndroidProofOfWorkRepositoryTest : TestBase() {
|
||||||
|
private lateinit var repo: ProofOfWorkRepository
|
||||||
|
private lateinit var addressRepo: AddressRepository
|
||||||
|
private lateinit var messageRepo: MessageRepository
|
||||||
|
|
||||||
|
private var initialHash1: ByteArray by Delegates.notNull<ByteArray>()
|
||||||
|
private var initialHash2: ByteArray by Delegates.notNull<ByteArray>()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
RuntimeEnvironment.application.deleteDatabase(SqlHelper.DATABASE_NAME)
|
||||||
|
val sqlHelper = SqlHelper(RuntimeEnvironment.application)
|
||||||
|
|
||||||
|
addressRepo = AndroidAddressRepository(sqlHelper)
|
||||||
|
messageRepo = AndroidMessageRepository(sqlHelper, RuntimeEnvironment.application)
|
||||||
|
repo = AndroidProofOfWorkRepository(sqlHelper)
|
||||||
|
mockedInternalContext(
|
||||||
|
addressRepository = addressRepo,
|
||||||
|
messageRepository = messageRepo,
|
||||||
|
proofOfWorkRepository = repo,
|
||||||
|
cryptography = cryptography()
|
||||||
|
)
|
||||||
|
|
||||||
|
repo.putObject(ObjectMessage.Builder()
|
||||||
|
.payload(GetPubkey(BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(),
|
||||||
|
1000, 1000)
|
||||||
|
initialHash1 = repo.getItems()[0]
|
||||||
|
|
||||||
|
val sender = loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")
|
||||||
|
val recipient = loadContact()
|
||||||
|
addressRepo.save(sender)
|
||||||
|
addressRepo.save(recipient)
|
||||||
|
val plaintext = Plaintext.Builder(Plaintext.Type.MSG)
|
||||||
|
.ackData(cryptography().randomBytes(32))
|
||||||
|
.from(sender)
|
||||||
|
.to(recipient)
|
||||||
|
.message("Subject", "Message")
|
||||||
|
.status(Plaintext.Status.DOING_PROOF_OF_WORK)
|
||||||
|
.build()
|
||||||
|
messageRepo.save(plaintext)
|
||||||
|
initialHash2 = cryptography().getInitialHash(plaintext.ackMessage!!)
|
||||||
|
repo.putObject(ProofOfWorkRepository.Item(
|
||||||
|
plaintext.ackMessage!!,
|
||||||
|
1000, 1000,
|
||||||
|
UnixTime.now + 10 * UnixTime.MINUTE,
|
||||||
|
plaintext
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure object is stored`() {
|
||||||
|
val sizeBefore = repo.getItems().size
|
||||||
|
repo.putObject(ObjectMessage.Builder()
|
||||||
|
.payload(GetPubkey(BitmessageAddress("BM-2D9U2hv3YBMHM1zERP32anKfVKohyPN9x2"))).build(),
|
||||||
|
1000, 1000)
|
||||||
|
assertThat(repo.getItems().size, `is`(sizeBefore + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure ack objects are stored`() {
|
||||||
|
val sizeBefore = repo.getItems().size
|
||||||
|
val sender = loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")
|
||||||
|
val recipient = loadContact()
|
||||||
|
addressRepo.save(sender)
|
||||||
|
addressRepo.save(recipient)
|
||||||
|
val plaintext = Plaintext.Builder(Plaintext.Type.MSG)
|
||||||
|
.ackData(cryptography().randomBytes(32))
|
||||||
|
.from(sender)
|
||||||
|
.to(recipient)
|
||||||
|
.message("Subject", "Message")
|
||||||
|
.status(Plaintext.Status.DOING_PROOF_OF_WORK)
|
||||||
|
.build()
|
||||||
|
messageRepo.save(plaintext)
|
||||||
|
repo.putObject(ProofOfWorkRepository.Item(
|
||||||
|
plaintext.ackMessage!!,
|
||||||
|
1000, 1000,
|
||||||
|
UnixTime.now + 10 * UnixTime.MINUTE,
|
||||||
|
plaintext
|
||||||
|
))
|
||||||
|
assertThat(repo.getItems().size, `is`(sizeBefore + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure item can be retrieved`() {
|
||||||
|
val item = repo.getItem(initialHash1)
|
||||||
|
assertThat(item, notNullValue())
|
||||||
|
assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GetPubkey::class.java))
|
||||||
|
assertThat(item.nonceTrialsPerByte, `is`(1000L))
|
||||||
|
assertThat(item.extraBytes, `is`(1000L))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure ack item can be retrieved`() {
|
||||||
|
val item = repo.getItem(initialHash2)
|
||||||
|
assertThat(item, notNullValue())
|
||||||
|
assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GenericPayload::class.java))
|
||||||
|
assertThat(item.nonceTrialsPerByte, `is`(1000L))
|
||||||
|
assertThat(item.extraBytes, `is`(1000L))
|
||||||
|
assertThat(item.expirationTime, not<Number>(0))
|
||||||
|
assertThat(item.message, notNullValue())
|
||||||
|
assertThat<PrivateKey>(item.message?.from?.privateKey, notNullValue())
|
||||||
|
assertThat<Pubkey>(item.message?.to?.pubkey, notNullValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = RuntimeException::class)
|
||||||
|
fun `ensure retrieving nonexisting item causes exception`() {
|
||||||
|
repo.getItem(ByteArray(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure item can be deleted`() {
|
||||||
|
repo.removeObject(initialHash1)
|
||||||
|
repo.removeObject(initialHash2)
|
||||||
|
assertTrue(repo.getItems().isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ensure deletion of nonexisting item is handled silently`() {
|
||||||
|
repo.removeObject(ByteArray(0))
|
||||||
|
}
|
||||||
|
}
|
127
app/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt
Normal file
127
app/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Christian Basler
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.bitmessage.repository
|
||||||
|
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.InternalContext
|
||||||
|
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
||||||
|
import ch.dissem.bitmessage.entity.payload.V4Pubkey
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
|
||||||
|
import ch.dissem.bitmessage.factory.Factory
|
||||||
|
import ch.dissem.bitmessage.ports.*
|
||||||
|
import com.nhaarman.mockito_kotlin.mock
|
||||||
|
import com.nhaarman.mockito_kotlin.spy
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
open class TestBase {
|
||||||
|
companion object {
|
||||||
|
@BeforeClass
|
||||||
|
@JvmStatic
|
||||||
|
fun init() {
|
||||||
|
mockedInternalContext(
|
||||||
|
cryptography = SpongyCryptography(),
|
||||||
|
proofOfWorkEngine = MultiThreadedPOWEngine()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mockedInternalContext(
|
||||||
|
cryptography: Cryptography = mock {},
|
||||||
|
inventory: Inventory = mock {},
|
||||||
|
nodeRegistry: NodeRegistry = mock {},
|
||||||
|
networkHandler: NetworkHandler = mock {},
|
||||||
|
addressRepository: AddressRepository = mock {},
|
||||||
|
messageRepository: MessageRepository = mock {},
|
||||||
|
proofOfWorkRepository: ProofOfWorkRepository = mock {},
|
||||||
|
proofOfWorkEngine: ProofOfWorkEngine = mock {},
|
||||||
|
customCommandHandler: CustomCommandHandler = mock {},
|
||||||
|
listener: BitmessageContext.Listener = mock {},
|
||||||
|
labeler: Labeler = mock {},
|
||||||
|
port: Int = 0,
|
||||||
|
connectionTTL: Long = 0,
|
||||||
|
connectionLimit: Int = 0
|
||||||
|
): InternalContext {
|
||||||
|
return spy(InternalContext(
|
||||||
|
cryptography,
|
||||||
|
inventory,
|
||||||
|
nodeRegistry,
|
||||||
|
networkHandler,
|
||||||
|
addressRepository,
|
||||||
|
messageRepository,
|
||||||
|
proofOfWorkRepository,
|
||||||
|
proofOfWorkEngine,
|
||||||
|
customCommandHandler,
|
||||||
|
listener,
|
||||||
|
labeler,
|
||||||
|
"/Jabit:TEST/",
|
||||||
|
port,
|
||||||
|
connectionTTL,
|
||||||
|
connectionLimit
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun randomInventoryVector(): InventoryVector {
|
||||||
|
val bytes = ByteArray(32)
|
||||||
|
RANDOM.nextBytes(bytes)
|
||||||
|
return InventoryVector(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getResource(resourceName: String) =
|
||||||
|
TestBase::class.java.classLoader.getResourceAsStream(resourceName)
|
||||||
|
|
||||||
|
fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage {
|
||||||
|
val data = getBytes(resourceName)
|
||||||
|
val `in` = ByteArrayInputStream(data)
|
||||||
|
return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBytes(resourceName: String): ByteArray {
|
||||||
|
val `in` = getResource(resourceName)
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var len = `in`.read(buffer)
|
||||||
|
while (len != -1) {
|
||||||
|
out.write(buffer, 0, len)
|
||||||
|
len = `in`.read(buffer)
|
||||||
|
}
|
||||||
|
return out.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadIdentity(address: String): BitmessageAddress {
|
||||||
|
val privateKey = PrivateKey.read(getResource(address + ".privkey"))
|
||||||
|
val identity = BitmessageAddress(privateKey)
|
||||||
|
Assert.assertEquals(address, identity.address)
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadContact(): BitmessageAddress {
|
||||||
|
val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")
|
||||||
|
val objectMessage = loadObjectMessage(3, "V4Pubkey.payload")
|
||||||
|
objectMessage.decrypt(address.publicDecryptionKey)
|
||||||
|
address.pubkey = objectMessage.payload as V4Pubkey
|
||||||
|
return address
|
||||||
|
}
|
||||||
|
|
||||||
|
private val RANDOM = Random()
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
app/src/test/resources/V4Pubkey.payload
Normal file
BIN
app/src/test/resources/V4Pubkey.payload
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
mock-maker-inline
|
Loading…
Reference in New Issue
Block a user