kopia lustrzana https://github.com/ryukoposting/Signal-Android
SMS Exporter unit testing.
rodzic
372f939a67
commit
777a91abc7
|
@ -15,4 +15,12 @@ dependencies {
|
|||
implementation libs.rxjava3.rxjava
|
||||
implementation libs.rxjava3.rxandroid
|
||||
implementation libs.rxjava3.rxkotlin
|
||||
|
||||
testImplementation testLibs.junit.junit
|
||||
testImplementation testLibs.mockito.core
|
||||
testImplementation testLibs.mockito.android
|
||||
testImplementation testLibs.mockito.kotlin
|
||||
testImplementation testLibs.robolectric.robolectric
|
||||
testImplementation testLibs.androidx.test.core
|
||||
testImplementation testLibs.androidx.test.core.ktx
|
||||
}
|
|
@ -17,6 +17,10 @@ internal object ExportMmsMessagesUseCase {
|
|||
|
||||
private val TAG = Log.tag(ExportMmsMessagesUseCase::class.java)
|
||||
|
||||
internal fun getTransactionId(mms: ExportableMessage.Mms): String {
|
||||
return "signal:T${mms.id}"
|
||||
}
|
||||
|
||||
fun execute(
|
||||
context: Context,
|
||||
getOrCreateThreadOutput: GetOrCreateMmsThreadIdsUseCase.Output,
|
||||
|
@ -24,7 +28,7 @@ internal object ExportMmsMessagesUseCase {
|
|||
): Try<Output> {
|
||||
try {
|
||||
val (mms, threadId) = getOrCreateThreadOutput
|
||||
val transactionId = "signal:T${mms.id}"
|
||||
val transactionId = getTransactionId(mms)
|
||||
|
||||
if (checkForExistence) {
|
||||
Log.d(TAG, "Checking if the message is already in the database.")
|
||||
|
@ -47,7 +51,8 @@ internal object ExportMmsMessagesUseCase {
|
|||
Telephony.Mms.MESSAGE_CLASS to "personal",
|
||||
Telephony.Mms.PRIORITY to PduHeaders.PRIORITY_NORMAL,
|
||||
Telephony.Mms.TRANSACTION_ID to transactionId,
|
||||
Telephony.Mms.RESPONSE_STATUS to PduHeaders.RESPONSE_STATUS_OK
|
||||
Telephony.Mms.RESPONSE_STATUS to PduHeaders.RESPONSE_STATUS_OK,
|
||||
Telephony.Mms.SEEN to 1
|
||||
)
|
||||
|
||||
val uri = context.contentResolver.insert(Telephony.Mms.CONTENT_URI, mmsContentValues)
|
||||
|
|
|
@ -17,10 +17,14 @@ internal object ExportMmsPartsUseCase {
|
|||
|
||||
private val TAG = Log.tag(ExportMmsPartsUseCase::class.java)
|
||||
|
||||
internal fun getContentId(part: ExportableMessage.Mms.Part): String {
|
||||
return "<signal:${part.contentId}>"
|
||||
}
|
||||
|
||||
fun execute(context: Context, part: ExportableMessage.Mms.Part, output: ExportMmsMessagesUseCase.Output, checkForExistence: Boolean): Try<Output> {
|
||||
try {
|
||||
val (message, messageId) = output
|
||||
val contentId = "<signal:${part.contentId}>"
|
||||
val contentId = getContentId(part)
|
||||
val mmsPartUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(messageId.toString()).appendPath("part").build()
|
||||
|
||||
if (checkForExistence) {
|
||||
|
|
|
@ -43,7 +43,7 @@ internal object GetOrCreateMmsThreadIdsUseCase {
|
|||
error("Expected non-empty recipient count.")
|
||||
}
|
||||
|
||||
return HashSet(recipients.map { it.toString() })
|
||||
return HashSet(recipients.map { it })
|
||||
}
|
||||
|
||||
data class Output(val mms: ExportableMessage.Mms, val threadId: Long)
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package org.signal.smsexporter
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.net.Uri
|
||||
import android.provider.Telephony
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
|
||||
/**
|
||||
* Provides a content provider which reads and writes to an in-memory database.
|
||||
*/
|
||||
class InMemoryContentProvider : ContentProvider() {
|
||||
|
||||
private val database: InMemoryDatabase = InMemoryDatabase()
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
|
||||
val tableName = if (p0.pathSegments.isNotEmpty()) p0.lastPathSegment else p0.authority
|
||||
return database.readableDatabase.query(tableName, p1, p2, p3, p4, null, null)
|
||||
}
|
||||
|
||||
override fun getType(p0: Uri): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
|
||||
val tableName = if (p0.pathSegments.isNotEmpty()) p0.lastPathSegment else p0.authority
|
||||
val id = database.writableDatabase.insert(tableName, null, p1)
|
||||
return if (id == -1L) {
|
||||
null
|
||||
} else {
|
||||
p0.buildUpon().appendPath("$id").build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
|
||||
return -1
|
||||
}
|
||||
|
||||
private class InMemoryDatabase : SQLiteOpenHelper(ApplicationProvider.getApplicationContext(), null, null, 1) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE sms (
|
||||
${Telephony.Sms._ID} INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
${Telephony.Sms.ADDRESS} TEXT,
|
||||
${Telephony.Sms.DATE_SENT} INTEGER,
|
||||
${Telephony.Sms.DATE} INTEGER,
|
||||
${Telephony.Sms.BODY} TEXT,
|
||||
${Telephony.Sms.READ} INTEGER,
|
||||
${Telephony.Sms.TYPE} INTEGER
|
||||
);
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE mms (
|
||||
${Telephony.Mms._ID} INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
${Telephony.Mms.THREAD_ID} INTEGER,
|
||||
${Telephony.Mms.DATE} INTEGER,
|
||||
${Telephony.Mms.DATE_SENT} INTEGER,
|
||||
${Telephony.Mms.MESSAGE_BOX} INTEGER,
|
||||
${Telephony.Mms.READ} INTEGER,
|
||||
${Telephony.Mms.CONTENT_TYPE} TEXT,
|
||||
${Telephony.Mms.MESSAGE_TYPE} INTEGER,
|
||||
${Telephony.Mms.MMS_VERSION} INTEGER,
|
||||
${Telephony.Mms.MESSAGE_CLASS} TEXT,
|
||||
${Telephony.Mms.PRIORITY} INTEGER,
|
||||
${Telephony.Mms.TRANSACTION_ID} TEXT,
|
||||
${Telephony.Mms.RESPONSE_STATUS} INTEGER,
|
||||
${Telephony.Mms.SEEN} INTEGER
|
||||
);
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE part (
|
||||
${Telephony.Mms.Part._ID} INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
${Telephony.Mms.Part.MSG_ID} INTEGER,
|
||||
${Telephony.Mms.Part.CONTENT_TYPE} TEXT,
|
||||
${Telephony.Mms.Part.CONTENT_ID} INTEGER,
|
||||
${Telephony.Mms.Part.TEXT} TEXT
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE addr (
|
||||
${Telephony.Mms.Addr._ID} INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
${Telephony.Mms.Addr.ADDRESS} TEXT,
|
||||
${Telephony.Mms.Addr.CHARSET} INTEGER,
|
||||
${Telephony.Mms.Addr.TYPE} INTEGER
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, p1: Int, p2: Int) = Unit
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.signal.smsexporter
|
||||
|
||||
import android.provider.Telephony
|
||||
import org.robolectric.shadows.ShadowContentResolver
|
||||
import java.util.UUID
|
||||
|
||||
object TestUtils {
|
||||
fun generateSmsMessage(
|
||||
id: String = UUID.randomUUID().toString(),
|
||||
address: String = "+15555060177",
|
||||
dateReceived: Long = 2,
|
||||
dateSent: Long = 1,
|
||||
isRead: Boolean = false,
|
||||
isOutgoing: Boolean = false,
|
||||
body: String = "Hello, $id"
|
||||
): ExportableMessage.Sms {
|
||||
return ExportableMessage.Sms(id, address, dateReceived, dateSent, isRead, isOutgoing, body)
|
||||
}
|
||||
|
||||
fun generateMmsMessage(
|
||||
id: String = UUID.randomUUID().toString(),
|
||||
addresses: Set<String> = setOf("+15555060177"),
|
||||
dateReceived: Long = 2,
|
||||
dateSent: Long = 1,
|
||||
isRead: Boolean = false,
|
||||
isOutgoing: Boolean = false,
|
||||
parts: List<ExportableMessage.Mms.Part> = listOf(ExportableMessage.Mms.Part.Text("Hello, $id")),
|
||||
sender: CharSequence = "+15555060177"
|
||||
): ExportableMessage.Mms {
|
||||
return ExportableMessage.Mms(id, addresses, dateReceived, dateSent, isRead, isOutgoing, parts, sender)
|
||||
}
|
||||
|
||||
fun setUpSmsContentProviderAndResolver() {
|
||||
ShadowContentResolver.registerProviderInternal(
|
||||
Telephony.Sms.CONTENT_URI.authority,
|
||||
InMemoryContentProvider()
|
||||
)
|
||||
}
|
||||
|
||||
fun setUpMmsContentProviderAndResolver() {
|
||||
ShadowContentResolver.registerProviderInternal(
|
||||
Telephony.Mms.CONTENT_URI.authority,
|
||||
InMemoryContentProvider()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package org.signal.smsexporter.internal.mms
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.Telephony
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.smsexporter.ExportableMessage
|
||||
import org.signal.smsexporter.TestUtils
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ExportMmsMessagesUseCaseTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
TestUtils.setUpMmsContentProviderAndResolver()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an MMS message, when I execute, then I expect an MMS record to be created`() {
|
||||
// GIVEN
|
||||
val mmsMessage = TestUtils.generateMmsMessage()
|
||||
val threadUseCaseOutput = GetOrCreateMmsThreadIdsUseCase.Output(mmsMessage, 1)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsMessagesUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
threadUseCaseOutput,
|
||||
false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedMessage(mmsMessage)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an MMS message that already exists, when I execute and check for existence, then I expect no new MMS record to be created`() {
|
||||
// GIVEN
|
||||
val mmsMessage = TestUtils.generateMmsMessage()
|
||||
val threadUseCaseOutput = GetOrCreateMmsThreadIdsUseCase.Output(mmsMessage, 1)
|
||||
ExportMmsMessagesUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
threadUseCaseOutput,
|
||||
false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsMessagesUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
threadUseCaseOutput,
|
||||
true
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedMessage(mmsMessage)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an MMS message that already exists, when I execute and do not check for existence, then I expect a duplicate MMS record to be created`() {
|
||||
// GIVEN
|
||||
val mmsMessage = TestUtils.generateMmsMessage()
|
||||
val threadUseCaseOutput = GetOrCreateMmsThreadIdsUseCase.Output(mmsMessage, 1)
|
||||
ExportMmsMessagesUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
threadUseCaseOutput,
|
||||
false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsMessagesUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
threadUseCaseOutput,
|
||||
false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedMessage(mmsMessage, expectedRowCount = 2)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateExportedMessage(
|
||||
mms: ExportableMessage.Mms,
|
||||
expectedRowCount: Int = 1,
|
||||
threadId: Long = 1L
|
||||
) {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
val baseUri: Uri = Telephony.Mms.CONTENT_URI
|
||||
val transactionId = ExportMmsMessagesUseCase.getTransactionId(mms)
|
||||
|
||||
context.contentResolver.query(
|
||||
baseUri,
|
||||
null,
|
||||
"${Telephony.Mms.TRANSACTION_ID} = ?",
|
||||
arrayOf(transactionId),
|
||||
null,
|
||||
null
|
||||
)?.use {
|
||||
it.moveToFirst()
|
||||
assertEquals(expectedRowCount, it.count)
|
||||
assertEquals(threadId, CursorUtil.requireLong(it, Telephony.Mms.THREAD_ID))
|
||||
assertEquals(mms.dateReceived, CursorUtil.requireLong(it, Telephony.Mms.DATE))
|
||||
assertEquals(mms.dateSent, CursorUtil.requireLong(it, Telephony.Mms.DATE_SENT))
|
||||
assertEquals(if (mms.isOutgoing) Telephony.Mms.MESSAGE_BOX_SENT else Telephony.Mms.MESSAGE_BOX_INBOX, CursorUtil.requireInt(it, Telephony.Mms.MESSAGE_BOX))
|
||||
assertEquals(mms.isRead, CursorUtil.requireBoolean(it, Telephony.Mms.READ))
|
||||
assertEquals(transactionId, CursorUtil.requireString(it, Telephony.Mms.TRANSACTION_ID))
|
||||
} ?: org.junit.Assert.fail("Content Resolver returned a null cursor")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package org.signal.smsexporter.internal.mms
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.Telephony
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.smsexporter.ExportableMessage
|
||||
import org.signal.smsexporter.TestUtils
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ExportMmsPartsUseCaseTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
TestUtils.setUpMmsContentProviderAndResolver()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a message with a part, when I export part, then I expect a valid part row`() {
|
||||
// GIVEN
|
||||
val message = TestUtils.generateMmsMessage()
|
||||
val output = ExportMmsMessagesUseCase.Output(message, 1)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsPartsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
message.parts.first(),
|
||||
output,
|
||||
false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedPart(message.parts.first(), output.messageId)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an already exported part, when I export part with check, then I expect a single part row`() {
|
||||
// GIVEN
|
||||
val message = TestUtils.generateMmsMessage()
|
||||
val output = ExportMmsMessagesUseCase.Output(message, 1)
|
||||
ExportMmsPartsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
message.parts.first(),
|
||||
output,
|
||||
false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsPartsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
message.parts.first(),
|
||||
output,
|
||||
true
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedPart(message.parts.first(), output.messageId)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an already exported part, when I export part without check, then I expect a duplicated part row`() {
|
||||
// GIVEN
|
||||
val message = TestUtils.generateMmsMessage()
|
||||
val output = ExportMmsMessagesUseCase.Output(message, 1)
|
||||
ExportMmsPartsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
message.parts.first(),
|
||||
output,
|
||||
false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsPartsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
message.parts.first(),
|
||||
output,
|
||||
false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedPart(message.parts.first(), output.messageId, expectedRowCount = 2)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateExportedPart(
|
||||
part: ExportableMessage.Mms.Part,
|
||||
messageId: Long,
|
||||
expectedRowCount: Int = 1
|
||||
) {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
val baseUri: Uri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath("part").build()
|
||||
val contentId = ExportMmsPartsUseCase.getContentId(part)
|
||||
|
||||
context.contentResolver.query(
|
||||
baseUri,
|
||||
null,
|
||||
"${Telephony.Mms.Part.CONTENT_ID} = ?",
|
||||
arrayOf(contentId),
|
||||
null,
|
||||
null
|
||||
)?.use {
|
||||
it.moveToFirst()
|
||||
assertEquals(expectedRowCount, it.count)
|
||||
assertEquals(part.contentType, CursorUtil.requireString(it, Telephony.Mms.Part.CONTENT_TYPE))
|
||||
assertEquals(contentId, CursorUtil.requireString(it, Telephony.Mms.Part.CONTENT_ID))
|
||||
assertEquals(messageId, CursorUtil.requireLong(it, Telephony.Mms.Part.MSG_ID))
|
||||
assertEquals(if (part is ExportableMessage.Mms.Part.Text) part.text else null, CursorUtil.requireString(it, Telephony.Mms.Part.TEXT))
|
||||
} ?: org.junit.Assert.fail("Content Resolver returned a null cursor")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package org.signal.smsexporter.internal.mms
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.Telephony
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.google.android.mms.pdu_alt.PduHeaders
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.smsexporter.TestUtils
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ExportMmsRecipientsUseCaseTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
TestUtils.setUpMmsContentProviderAndResolver()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When I export recipient, then I expect a valid exported recipient`() {
|
||||
// GIVEN
|
||||
val address = "+15065550177"
|
||||
val sender = "+15065550123"
|
||||
val messageId = 1L
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsRecipientsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
messageId,
|
||||
address,
|
||||
sender,
|
||||
false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedRecipient(address, sender, messageId)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given recipient already exported, When I export recipient with check, then I expect a single exported recipient`() {
|
||||
// GIVEN
|
||||
val address = "+15065550177"
|
||||
val sender = "+15065550123"
|
||||
val messageId = 1L
|
||||
ExportMmsRecipientsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
messageId,
|
||||
address,
|
||||
sender,
|
||||
false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsRecipientsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
messageId,
|
||||
address,
|
||||
sender,
|
||||
true
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedRecipient(address, sender, messageId)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given recipient already exported, When I export recipient with check, then I expect a duplicate exported recipient`() {
|
||||
// GIVEN
|
||||
val address = "+15065550177"
|
||||
val sender = "+15065550123"
|
||||
val messageId = 1L
|
||||
ExportMmsRecipientsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
messageId,
|
||||
address,
|
||||
sender,
|
||||
false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportMmsRecipientsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
messageId,
|
||||
address,
|
||||
sender,
|
||||
false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedRecipient(address, sender, messageId, expectedRowCount = 2)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateExportedRecipient(address: String, sender: String, messageId: Long, expectedRowCount: Int = 1) {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
val baseUri: Uri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(messageId.toString()).appendPath("addr").build()
|
||||
|
||||
context.contentResolver.query(
|
||||
baseUri,
|
||||
null,
|
||||
"${Telephony.Mms.Addr.ADDRESS} = ?",
|
||||
arrayOf(address),
|
||||
null,
|
||||
null
|
||||
)?.use {
|
||||
it.moveToFirst()
|
||||
assertEquals(expectedRowCount, it.count)
|
||||
assertEquals(address, CursorUtil.requireString(it, Telephony.Mms.Addr.ADDRESS))
|
||||
assertEquals(if (address == sender) PduHeaders.FROM else PduHeaders.TO, CursorUtil.requireInt(it, Telephony.Mms.Addr.TYPE))
|
||||
} ?: fail("Content Resolver returned a null cursor")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.signal.smsexporter.internal.mms
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.signal.smsexporter.TestUtils
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class GetOrCreateMmsThreadIdsUseCaseTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
TestUtils.setUpMmsContentProviderAndResolver()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a message, when I execute, then I update the cache with the thread id`() {
|
||||
// GIVEN
|
||||
val mms = TestUtils.generateMmsMessage()
|
||||
val threadCache = mutableMapOf<Set<String>, Long>()
|
||||
|
||||
// WHEN
|
||||
val result = GetOrCreateMmsThreadIdsUseCase.execute(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
mms,
|
||||
threadCache
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
assertEquals(threadCache[mms.addresses], it.threadId)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package org.signal.smsexporter.internal.sms
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.provider.Telephony
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.signal.core.util.CursorUtil
|
||||
import org.signal.smsexporter.ExportableMessage
|
||||
import org.signal.smsexporter.TestUtils
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ExportSmsMessagesUseCaseTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
TestUtils.setUpSmsContentProviderAndResolver()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an SMS message, when I execute, then I expect a record to be inserted into the SMS database`() {
|
||||
// GIVEN
|
||||
val exportableSmsMessage = TestUtils.generateSmsMessage()
|
||||
|
||||
// WHEN
|
||||
val result = ExportSmsMessagesUseCase.execute(
|
||||
context = ApplicationProvider.getApplicationContext(),
|
||||
sms = exportableSmsMessage,
|
||||
checkForExistence = false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedMessage(exportableSmsMessage)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an SMS message that already exists, when I execute and check for existence, then I expect only a single record to be inserted into the SMS database`() {
|
||||
// GIVEN
|
||||
val exportableSmsMessage = TestUtils.generateSmsMessage()
|
||||
ExportSmsMessagesUseCase.execute(
|
||||
context = ApplicationProvider.getApplicationContext(),
|
||||
sms = exportableSmsMessage,
|
||||
checkForExistence = false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportSmsMessagesUseCase.execute(
|
||||
context = ApplicationProvider.getApplicationContext(),
|
||||
sms = exportableSmsMessage,
|
||||
checkForExistence = true
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedMessage(exportableSmsMessage)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an SMS message that already exists, when I execute and do not check for existence, then I expect only a duplicate record to be inserted into the SMS database`() {
|
||||
// GIVEN
|
||||
val exportableSmsMessage = TestUtils.generateSmsMessage()
|
||||
ExportSmsMessagesUseCase.execute(
|
||||
context = ApplicationProvider.getApplicationContext(),
|
||||
sms = exportableSmsMessage,
|
||||
checkForExistence = false
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = ExportSmsMessagesUseCase.execute(
|
||||
context = ApplicationProvider.getApplicationContext(),
|
||||
sms = exportableSmsMessage,
|
||||
checkForExistence = false
|
||||
)
|
||||
|
||||
// THEN
|
||||
result.either(
|
||||
onSuccess = {
|
||||
validateExportedMessage(exportableSmsMessage, expectedRowCount = 2)
|
||||
},
|
||||
onFailure = {
|
||||
throw it
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun validateExportedMessage(sms: ExportableMessage.Sms, expectedRowCount: Int = 1) {
|
||||
// 1. Grab the SMS record from the content resolver
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
val baseUri: Uri = Telephony.Sms.CONTENT_URI
|
||||
|
||||
context.contentResolver.query(
|
||||
baseUri,
|
||||
null,
|
||||
"${Telephony.Sms.ADDRESS} = ? AND ${Telephony.Sms.DATE_SENT} = ?",
|
||||
arrayOf(sms.address, sms.dateSent.toString()),
|
||||
null,
|
||||
null
|
||||
)?.use {
|
||||
it.moveToFirst()
|
||||
assertEquals(expectedRowCount, it.count)
|
||||
assertEquals(sms.address, CursorUtil.requireString(it, Telephony.Sms.ADDRESS))
|
||||
assertEquals(sms.dateSent, CursorUtil.requireLong(it, Telephony.Sms.DATE_SENT))
|
||||
assertEquals(sms.dateReceived, CursorUtil.requireLong(it, Telephony.Sms.DATE))
|
||||
assertEquals(sms.isRead, CursorUtil.requireBoolean(it, Telephony.Sms.READ))
|
||||
assertEquals(sms.body, CursorUtil.requireString(it, Telephony.Sms.BODY))
|
||||
assertEquals(if (sms.isOutgoing) Telephony.Sms.MESSAGE_TYPE_SENT else Telephony.Sms.MESSAGE_TYPE_INBOX, CursorUtil.requireInt(it, Telephony.Sms.TYPE))
|
||||
} ?: Assert.fail("Content Resolver returned a null cursor")
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue