Merge branch 'GH-1275_threaded_message_list_display_error'

This commit is contained in:
cketti 2016-04-15 19:47:27 +02:00
commit 6ab90e5805
3 changed files with 383 additions and 14 deletions

View file

@ -0,0 +1,347 @@
package com.fsck.k9.provider;
import android.database.Cursor;
import android.database.SQLException;
import android.net.Uri;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.ProviderTestCase2;
import com.fsck.k9.Account;
import com.fsck.k9.Preferences;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeMessage;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Collections;
import java.util.GregorianCalendar;
@RunWith(AndroidJUnit4.class)
public class EmailProviderTest extends ProviderTestCase2<EmailProvider> {
private MimeMessage message;
private MimeMessage laterMessage;
private MimeMessage reply;
private MimeMessage replyAtSameTime;
public EmailProviderTest() {
super(EmailProvider.class, EmailProvider.AUTHORITY);
}
private void buildMessages() throws MessagingException {
message = new MimeMessage();
message.setSubject("Test Subject");
message.setSentDate(new GregorianCalendar(2016, 1, 2).getTime(), false);
message.setMessageId("<uid001@email.com>");
laterMessage = new MimeMessage();
laterMessage.setSubject("Test Subject2");
laterMessage.setSentDate(new GregorianCalendar(2016, 1, 3).getTime(), false);
reply = new MimeMessage();
reply.setSubject("Re: Test Subject");
reply.setSentDate(new GregorianCalendar(2016, 1, 3).getTime(), false);
reply.setMessageId("<uid002@email.com>");
reply.setInReplyTo("<uid001@email.com>");
replyAtSameTime = new MimeMessage();
replyAtSameTime.setSubject("Re: Test Subject");
replyAtSameTime.setSentDate(new GregorianCalendar(2016, 1, 2).getTime(), false);
replyAtSameTime.setMessageId("<uid002@email.com>");
replyAtSameTime.setInReplyTo("<uid001@email.com>");
}
@Before
@Override
public void setUp() throws Exception {
setContext(InstrumentationRegistry.getTargetContext());
super.setUp();
buildMessages();
}
@Test
public void onCreate_shouldReturnTrue() {
assertNotNull(this.getProvider());
boolean returnValue = this.getProvider().onCreate();
assertEquals(true, returnValue);
}
@Test(expected = IllegalArgumentException.class)
public void query_withInvalidURI_throwsIllegalArgumentException() {
this.getProvider().query(
Uri.parse("content://com.google.www"),
new String[]{},
"",
new String[]{},
"");
}
@Test(expected = IllegalArgumentException.class)
public void query_forMessagesWithInvalidAccount_throwsIllegalArgumentException() {
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY+"/account/1/messages"),
new String[]{},
"",
new String[]{},
"");
assertNotNull(cursor);
}
@Test(expected = IllegalArgumentException.class)
public void query_forMessagesWithAccountAndWithoutRequiredFields_throwsIllegalArgumentException() {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages"),
new String[]{},
"",
new String[]{},
"");
assertNotNull(cursor);
assertTrue(cursor.isAfterLast());
}
@Test(expected = SQLException.class) //Handle this better?
public void query_forMessagesWithAccountAndRequiredFieldsWithNoOrderBy_throwsSQLiteException() {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT
},
"",
new String[]{},
"");
assertNotNull(cursor);
assertTrue(cursor.isAfterLast());
}
@Test
public void query_forMessagesWithEmptyAccountAndRequiredFieldsAndOrderBy_providesEmptyResult() {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
EmailProvider.MessageColumns.SUBJECT
},
"",
new String[]{},
EmailProvider.MessageColumns.DATE);
assertNotNull(cursor);
assertFalse(cursor.moveToFirst());
}
@Test
public void query_forMessagesWithAccountAndRequiredFieldsAndOrderBy_providesResult()
throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
account.getLocalStore().getFolder("Inbox")
.appendMessages(Collections.singletonList(message));
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
EmailProvider.MessageColumns.SUBJECT},
"",
new String[]{},
EmailProvider.MessageColumns.DATE);
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(message.getSubject(), cursor.getString(3));
}
@Test
public void query_forMessagesWithAccountAndRequiredFieldsAndOrderBy_sortsCorrectly()
throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
account.getLocalStore().getFolder("Inbox")
.appendMessages(Arrays.asList(message, laterMessage));
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
EmailProvider.MessageColumns.SUBJECT
},
"",
new String[]{},
EmailProvider.MessageColumns.DATE+" DESC");
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(laterMessage.getSubject(), cursor.getString(3));
cursor.moveToNext();
assertEquals(message.getSubject(), cursor.getString(3));
}
@Test
public void query_forThreadedMessages_sortsCorrectly()
throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
account.getLocalStore().getFolder("Inbox")
.appendMessages(Arrays.asList(message, laterMessage));
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages/threaded"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
EmailProvider.MessageColumns.SUBJECT,
EmailProvider.MessageColumns.DATE
},
"",
new String[]{},
EmailProvider.MessageColumns.DATE+" DESC");
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(laterMessage.getSubject(), cursor.getString(3));
cursor.moveToNext();
assertEquals(message.getSubject(), cursor.getString(3));
}
@Test
public void query_forThreadedMessages_showsThreadOfEmailOnce()
throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
account.getLocalStore().getFolder("Inbox")
.appendMessages(Collections.singletonList(message));
account.getLocalStore().getFolder("Inbox")
.appendMessages(Collections.singletonList(reply));
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages/threaded"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
EmailProvider.MessageColumns.SUBJECT,
EmailProvider.MessageColumns.DATE,
EmailProvider.SpecialColumns.THREAD_COUNT
},
"",
new String[]{},
EmailProvider.MessageColumns.DATE+" DESC");
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(2, cursor.getInt(5));
assertFalse(cursor.moveToNext());
}
@Test
public void query_forThreadedMessages_showsThreadOfEmailWithSameSendTimeOnce()
throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
account.getLocalStore().getFolder("Inbox")
.appendMessages(Collections.singletonList(message));
account.getLocalStore().getFolder("Inbox")
.appendMessages(Collections.singletonList(reply));
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages/threaded"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
EmailProvider.MessageColumns.SUBJECT,
EmailProvider.MessageColumns.DATE,
EmailProvider.SpecialColumns.THREAD_COUNT
},
"",
new String[]{},
EmailProvider.MessageColumns.DATE+" DESC");
assertNotNull(cursor);
assertTrue(cursor.moveToFirst());
assertEquals(2, cursor.getInt(5));
assertFalse(cursor.moveToNext());
}
@Test
public void query_forAThreadOfMessages_returnsMessage()
throws MessagingException {
Account account = Preferences.getPreferences(getContext()).newAccount();
account.getUuid();
Message message = new MimeMessage();
message.setSubject("Test Subject");
message.setSentDate(new GregorianCalendar(2016, 1, 2).getTime(), false);
account.getLocalStore().getFolder("Inbox")
.appendMessages(Collections.singletonList(message));
//Now get the thread id we just put in.
Cursor cursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/messages"),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
},
"",
new String[]{},
EmailProvider.MessageColumns.DATE);
assertNotNull(cursor);
cursor.moveToFirst();
String threadId = cursor.getString(2);
//Now check the message is listed under that thread
Cursor threadCursor = this.getProvider().query(
Uri.parse("content://"+EmailProvider.AUTHORITY
+"/account/"+account.getUuid()+"/thread/"+threadId),
new String[]{
EmailProvider.MessageColumns.ID,
EmailProvider.MessageColumns.FOLDER_ID,
EmailProvider.ThreadColumns.ROOT,
EmailProvider.MessageColumns.SUBJECT,
EmailProvider.MessageColumns.DATE
},
"",
new String[]{},
EmailProvider.MessageColumns.DATE);
assertNotNull(threadCursor);
assertTrue(threadCursor.moveToFirst());
assertEquals(message.getSubject(), threadCursor.getString(3));
}
}

View file

@ -79,7 +79,7 @@ public class Preferences {
loadAccounts();
}
return Collections.unmodifiableList(accountsInOrder);
return Collections.unmodifiableList(new ArrayList<Account>(accountsInOrder));
}
/**

View file

@ -403,19 +403,23 @@ public class EmailProvider extends ContentProvider {
query.append(") a ");
query.append("LEFT JOIN " + THREADS_TABLE + " t " +
query.append("JOIN " + THREADS_TABLE + " t " +
"ON (t." + ThreadColumns.ROOT + " = a.thread_root) " +
"LEFT JOIN " + MESSAGES_TABLE + " m " +
"ON (m." + MessageColumns.ID + " = t." + ThreadColumns.MESSAGE_ID +
"JOIN " + MESSAGES_TABLE + " m " +
"ON (m." + MessageColumns.ID + " = t." + ThreadColumns.MESSAGE_ID + " AND " +
"m." + InternalMessageColumns.EMPTY + "=0 AND " +
"m." + InternalMessageColumns.DELETED + "=0 AND " +
"m." + MessageColumns.DATE + " = a." + MessageColumns.DATE +
") ");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append("LEFT JOIN " + FOLDERS_TABLE + " f " +
query.append("JOIN " + FOLDERS_TABLE + " f " +
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID +
") ");
}
query.append("WHERE m." + MessageColumns.DATE + " = a." + MessageColumns.DATE);
query.append(" GROUP BY " + ThreadColumns.ROOT);
if (!TextUtils.isEmpty(sortOrder)) {
query.append(" ORDER BY ");
query.append(SqlQueryBuilder.addPrefixToSelection(
@ -456,27 +460,45 @@ public class EmailProvider extends ContentProvider {
query.append(
" FROM " + MESSAGES_TABLE + " m " +
"LEFT JOIN " + THREADS_TABLE + " t " +
"JOIN " + THREADS_TABLE + " t " +
"ON (t." + ThreadColumns.MESSAGE_ID + " = m." + MessageColumns.ID + ")");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append("LEFT JOIN " + FOLDERS_TABLE + " f " +
query.append("JOIN " + FOLDERS_TABLE + " f " +
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID +
")");
}
query.append(" WHERE (" +
InternalMessageColumns.DELETED + " = 0 AND " +
InternalMessageColumns.EMPTY + " = 0" +
")");
query.append(" WHERE (t." + ThreadColumns.ROOT + " IN (" +
"SELECT DISTINCT t." + ThreadColumns.ROOT + " " +
"FROM " + MESSAGES_TABLE + " mf " +
"JOIN " + THREADS_TABLE + " tf " +
"ON (tf." + ThreadColumns.MESSAGE_ID + " = mf." + MessageColumns.ID + ") " +
"JOIN " + THREADS_TABLE + " t " +
"ON (tf." + ThreadColumns.ROOT + " = t." + ThreadColumns.ROOT + ") " +
"JOIN " + MESSAGES_TABLE + " m " +
"ON (m." + MessageColumns.ID + " = t." + ThreadColumns.MESSAGE_ID + ") " +
"WHERE " +
"mf." + InternalMessageColumns.EMPTY + " = 0 AND " +
"mf." + InternalMessageColumns.DELETED + " = 0 AND " +
"m." + InternalMessageColumns.EMPTY + " = 0 AND " +
"m." + InternalMessageColumns.DELETED + " = 0");
if (!TextUtils.isEmpty(selection)) {
//TODO: Create more generic solution
String prefixedSelection = selection.replace("folder_id", "mf.folder_id");
query.append(" AND (");
query.append(selection);
query.append(prefixedSelection);
query.append(")");
}
query.append(
") AND " +
InternalMessageColumns.DELETED + " = 0 AND " +
InternalMessageColumns.EMPTY + " = 0)");
query.append(" GROUP BY t." + ThreadColumns.ROOT);
}
@ -631,7 +653,7 @@ public class EmailProvider extends ContentProvider {
}
/**
* This class is needed to make {@link CursorAdapter} work with our database schema.
* This class is needed to make {@link android.support.v4.widget.CursorAdapter} work with our database schema.
*
* <p>
* {@code CursorAdapter} requires a column named {@code "_id"} containing a stable id. We use