Added repository tests and fixed some bugs

This commit is contained in:
Christian Basler 2017-09-04 20:35:41 +02:00
parent 696cd6c0a6
commit 287de9deb5
15 changed files with 1109 additions and 9 deletions

View File

@ -85,6 +85,11 @@ dependencies {
testCompile 'junit:junit:4.12'
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 {

View File

@ -113,7 +113,7 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
where.append(" AND version = ").append(version)
}
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

View File

@ -148,8 +148,9 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
}
val result = LinkedList<UUID>()
sql.readableDatabase.query(
TABLE_NAME, projection,
where, null, null, null, null
true,
TABLE_NAME, projection, where,
null, null, null, null, null
).use { c ->
while (c.moveToNext()) {
val uuidBytes = c.getBlob(c.getColumnIndex(COLUMN_CONVERSATION))
@ -172,8 +173,7 @@ class AndroidMessageRepository(private val sql: SqlHelper, private val context:
// save new parents
var order = 0
for (parentIV in message.parents) {
val parent = getMessage(parentIV)
if (parent != null) {
getMessage(parentIV)?.let { parent ->
mergeConversations(db, parent.conversationId, message.conversationId)
order++
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) {
val values = ContentValues()
values.put("conversation", UuidUtils.asBytes(target))
val whereArgs = arrayOf(hex(UuidUtils.asBytes(source)))
db.update(TABLE_NAME, values, "conversation=?", whereArgs)
db.update(PARENTS_TABLE_NAME, values, "conversation=?", whereArgs)
val where = "conversation=X'${hex(UuidUtils.asBytes(source))}'"
db.update(TABLE_NAME, values, where, null)
db.update(PARENTS_TABLE_NAME, values, where, null)
}
private fun findIds(where: String): List<Long> {

View File

@ -99,6 +99,6 @@ class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME,
private val DATABASE_VERSION = 7
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 })
}
}

View File

@ -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)
}
}

View File

@ -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"))
}

View File

@ -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
}
}
}
}
}

View File

@ -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()
}
}

View File

@ -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))
}
}

View 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.

View File

@ -0,0 +1 @@
mock-maker-inline