Implement new contact harvester for the engine

Replaces harvesting code in ImapDB code so as to be stand-alone and
reusable. Implement harvesting policy to only harvest from a set of
whitelisted special folder types (so junk and trash is not harvested)
and only harvest valid, non-spoofed, addresses.
This commit is contained in:
Michael Gratton 2019-06-10 08:43:40 +10:00 committed by Michael James Gratton
parent 96cbb83f83
commit 65ab37938f
7 changed files with 323 additions and 0 deletions

View file

@ -0,0 +1,26 @@
/*
* Copyright 2019 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
internal class Geary.ContactStoreMock : ContactStore, MockObject, GLib.Object {
protected Gee.Queue<ExpectedCall> expected {
get; set; default = new Gee.LinkedList<ExpectedCall>();
}
public async Contact? get_by_rfc822(Geary.RFC822.MailboxAddress address,
GLib.Cancellable? cancellable)
throws GLib.Error {
return object_call<Contact?>("get_by_rfc822", { address }, null);
}
public async void update_contacts(Gee.Collection<Contact> updated,
GLib.Cancellable? cancellable)
throws GLib.Error {
void_call("update_contacts", { updated, cancellable });
}
}

View file

@ -0,0 +1,186 @@
/*
* Copyright 2019 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class Geary.ContactHarvesterImplTest : TestCase {
private ContactStoreMock? store = null;
private Email? email = null;
private RFC822.MailboxAddress test_address = null;
private RFC822.MailboxAddress sender_address = null;
private Gee.Collection<RFC822.MailboxAddress> senders = null;
public ContactHarvesterImplTest() {
base("Geary.ContactHarvesterImplTest");
add_test("whitelisted_folder_type", whitelisted_folder_type);
add_test("blacklisted_folder_type", blacklisted_folder_type);
add_test("seen_priority", seen_priority);
add_test("sent_priority", sent_priority);
add_test("received_priority", received_priority);
}
public override void set_up() throws GLib.Error {
this.store = new ContactStoreMock();
this.email = new Email(
new ImapDB.EmailIdentifier.no_message_id(new Imap.UID(1))
);
// Ensure the minimum required email flags are set
this.email.set_originators(null, null, null);
this.email.set_receivers(null, null, null);
this.test_address = new RFC822.MailboxAddress(
"Test", "test@example.com"
);
this.sender_address = new RFC822.MailboxAddress(
"Sender", "sender@example.com"
);
this.senders = Collection.single(this.sender_address);
}
public override void tear_down() throws GLib.Error {
this.store = null;
this.email = null;
this.test_address = null;
this.sender_address = null;
this.senders = null;
}
public void whitelisted_folder_type() throws GLib.Error {
ContactHarvesterImpl whitelisted = new ContactHarvesterImpl(
this.store,
SpecialFolderType.INBOX,
this.senders
);
ExpectedCall call = this.store.expect_call("update_contacts");
this.email.set_receivers(
new RFC822.MailboxAddresses.single(this.test_address), null, null
);
whitelisted.harvest_from_email.begin(
Collection.single(this.email), null,
(obj, ret) => { async_complete(ret); }
);
whitelisted.harvest_from_email.end(async_result());
this.store.assert_expectations();
Gee.Collection<Contact> contacts = call.called_arg<Gee.Collection<Contact>>(0);
assert_int(1, contacts.size, "contacts length");
Contact? created = Collection.get_first<Contact>(contacts) as Contact;
assert_non_null(created, "contacts contents");
assert_string("Test", created.real_name);
assert_string("test@example.com", created.email);
assert_string("test@example.com", created.normalized_email);
}
public void blacklisted_folder_type() throws GLib.Error {
ContactHarvesterImpl whitelisted = new ContactHarvesterImpl(
this.store,
SpecialFolderType.SPAM,
this.senders
);
this.email.set_receivers(
new RFC822.MailboxAddresses.single(this.test_address), null, null
);
whitelisted.harvest_from_email.begin(
Collection.single(this.email), null,
(obj, ret) => { async_complete(ret); }
);
whitelisted.harvest_from_email.end(async_result());
this.store.assert_expectations();
}
public void seen_priority() throws GLib.Error {
ContactHarvesterImpl whitelisted = new ContactHarvesterImpl(
this.store,
SpecialFolderType.INBOX,
this.senders
);
ExpectedCall call = this.store.expect_call("update_contacts");
this.email.set_receivers(
new RFC822.MailboxAddresses.single(this.test_address), null, null
);
whitelisted.harvest_from_email.begin(
Collection.single(this.email), null,
(obj, ret) => { async_complete(ret); }
);
whitelisted.harvest_from_email.end(async_result());
this.store.assert_expectations();
Gee.Collection<Contact> contacts = call.called_arg<Gee.Collection<Contact>>(0);
Contact? created = Collection.get_first<Contact>(contacts) as Contact;
assert_int(
Contact.Importance.SEEN,
created.highest_importance,
"call contact importance"
);
}
public void sent_priority() throws GLib.Error {
ContactHarvesterImpl whitelisted = new ContactHarvesterImpl(
this.store,
SpecialFolderType.SENT,
this.senders
);
ExpectedCall call = this.store.expect_call("update_contacts");
this.email.set_receivers(
new RFC822.MailboxAddresses.single(this.test_address), null, null
);
whitelisted.harvest_from_email.begin(
Collection.single(this.email), null,
(obj, ret) => { async_complete(ret); }
);
whitelisted.harvest_from_email.end(async_result());
this.store.assert_expectations();
Gee.Collection<Contact> contacts = call.called_arg<Gee.Collection<Contact>>(0);
Contact? created = Collection.get_first<Contact>(contacts) as Contact;
assert_int(
Contact.Importance.SENT_TO,
created.highest_importance,
"call contact importance"
);
}
public void received_priority() throws GLib.Error {
ContactHarvesterImpl whitelisted = new ContactHarvesterImpl(
this.store,
SpecialFolderType.SENT,
this.senders
);
ExpectedCall call = this.store.expect_call("update_contacts");
this.email.set_receivers(
new RFC822.MailboxAddresses.single(this.sender_address), null, null
);
whitelisted.harvest_from_email.begin(
Collection.single(this.email), null,
(obj, ret) => { async_complete(ret); }
);
whitelisted.harvest_from_email.end(async_result());
this.store.assert_expectations();
Gee.Collection<Contact> contacts = call.called_arg<Gee.Collection<Contact>>(0);
Contact? created = Collection.get_first<Contact>(contacts) as Contact;
assert_int(
Contact.Importance.RECEIVED_FROM,
created.highest_importance,
"call contact importance"
);
}
}

View file

@ -14,6 +14,7 @@ geary_test_engine_sources = [
# geary-engine_internal.vapi, which leads to duplicate symbols when
# linking
'engine/api/geary-account-mock.vala',
'engine/api/geary-contact-store-mock.vala',
'engine/api/geary-credentials-mediator-mock.vala',
'engine/api/geary-email-identifier-mock.vala',
'engine/api/geary-email-properties-mock.vala',
@ -29,6 +30,7 @@ geary_test_engine_sources = [
'engine/app/app-conversation-monitor-test.vala',
'engine/app/app-conversation-set-test.vala',
'engine/common/common-contact-store-impl-test.vala',
'engine/common/common-contact-harvester-test.vala',
'engine/db/db-database-test.vala',
'engine/db/db-versioned-database-test.vala',
'engine/imap/command/imap-create-command-test.vala',

View file

@ -24,6 +24,7 @@ int main(string[] args) {
engine.add_suite(new Geary.AccountInformationTest().get_suite());
engine.add_suite(new Geary.AttachmentTest().get_suite());
engine.add_suite(new Geary.ContactHarvesterImplTest().get_suite());
engine.add_suite(new Geary.EngineTest().get_suite());
engine.add_suite(new Geary.FolderPathTest().get_suite());
engine.add_suite(new Geary.IdleManagerTest().get_suite());