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.rxjava
|
||||||
implementation libs.rxjava3.rxandroid
|
implementation libs.rxjava3.rxandroid
|
||||||
implementation libs.rxjava3.rxkotlin
|
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)
|
private val TAG = Log.tag(ExportMmsMessagesUseCase::class.java)
|
||||||
|
|
||||||
|
internal fun getTransactionId(mms: ExportableMessage.Mms): String {
|
||||||
|
return "signal:T${mms.id}"
|
||||||
|
}
|
||||||
|
|
||||||
fun execute(
|
fun execute(
|
||||||
context: Context,
|
context: Context,
|
||||||
getOrCreateThreadOutput: GetOrCreateMmsThreadIdsUseCase.Output,
|
getOrCreateThreadOutput: GetOrCreateMmsThreadIdsUseCase.Output,
|
||||||
|
@ -24,7 +28,7 @@ internal object ExportMmsMessagesUseCase {
|
||||||
): Try<Output> {
|
): Try<Output> {
|
||||||
try {
|
try {
|
||||||
val (mms, threadId) = getOrCreateThreadOutput
|
val (mms, threadId) = getOrCreateThreadOutput
|
||||||
val transactionId = "signal:T${mms.id}"
|
val transactionId = getTransactionId(mms)
|
||||||
|
|
||||||
if (checkForExistence) {
|
if (checkForExistence) {
|
||||||
Log.d(TAG, "Checking if the message is already in the database.")
|
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.MESSAGE_CLASS to "personal",
|
||||||
Telephony.Mms.PRIORITY to PduHeaders.PRIORITY_NORMAL,
|
Telephony.Mms.PRIORITY to PduHeaders.PRIORITY_NORMAL,
|
||||||
Telephony.Mms.TRANSACTION_ID to transactionId,
|
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)
|
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)
|
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> {
|
fun execute(context: Context, part: ExportableMessage.Mms.Part, output: ExportMmsMessagesUseCase.Output, checkForExistence: Boolean): Try<Output> {
|
||||||
try {
|
try {
|
||||||
val (message, messageId) = output
|
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()
|
val mmsPartUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(messageId.toString()).appendPath("part").build()
|
||||||
|
|
||||||
if (checkForExistence) {
|
if (checkForExistence) {
|
||||||
|
|
|
@ -43,7 +43,7 @@ internal object GetOrCreateMmsThreadIdsUseCase {
|
||||||
error("Expected non-empty recipient count.")
|
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)
|
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