def test_new(inbox, emptyemail, serviceadmin): emptyemail.SetProps([SPropValue(PR_SUBJECT, b'test')]) emptyemail.SaveChanges(0) entryid = emptyemail.GetProps([PR_ENTRYID], 0)[0].Value table = inbox.GetContentsTable(0) table.SetColumns([PR_EC_IMAP_ID], 0) table.Restrict(SPropertyRestriction(RELOP_EQ, PR_ENTRYID, SPropValue(PR_ENTRYID, entryid)), 0) rows = table.QueryRows(1, 0) assert len(rows) == 1 imap_id_0 = rows[0][0] assert imap_id_0.ulPropTag == PR_EC_IMAP_ID # Retry without cache for #7483 (Might purge the wrong cache in cluster tests) serviceadmin.PurgeCache(PURGE_CACHE_CELL) table = inbox.GetContentsTable(0) table.SetColumns([PR_EC_IMAP_ID], 0) table.Restrict(SPropertyRestriction(RELOP_EQ, PR_ENTRYID, SPropValue(PR_ENTRYID, entryid)), 0) rows = table.QueryRows(1, 0) assert len(rows) == 1 imap_id_1 = rows[0][0] assert imap_id_1.ulPropTag == PR_EC_IMAP_ID assert imap_id_0 == imap_id_1
def test_move(store, inbox, folder, emptyemail, serviceadmin): emptyemail.SetProps([SPropValue(PR_SUBJECT, b'test')]) emptyemail.SaveChanges(KEEP_OPEN_READWRITE) entryid = emptyemail.GetProps([PR_ENTRYID], 0)[0].Value emptyemail.SetProps([SPropValue(0x66010102, entryid)]) emptyemail.SaveChanges(0) emptyemail = store.OpenEntry(entryid, None, 0) uid = emptyemail.GetProps([PR_EC_IMAP_ID], 0)[0].Value inbox.CopyMessages([entryid], IID_IMAPIFolder, folder, 0, None, MESSAGE_MOVE) table = folder.GetContentsTable(0) table.SetColumns([PR_EC_IMAP_ID], 0) table.Restrict(SPropertyRestriction(RELOP_EQ, 0x66010102, SPropValue(0x66010102, entryid)), 0) rows = table.QueryRows(1, 0) assert len(rows) == 1 imap_id_0 = rows[0][0] assert imap_id_0.ulPropTag == PR_EC_IMAP_ID # PR_EC_IMAP_ID must be higher after move assert imap_id_0.Value > uid # Retry without cache for #7483 (Might purge the wrong cache in cluster tests) serviceadmin.PurgeCache(PURGE_CACHE_CELL) table = folder.GetContentsTable(0) table.SetColumns([PR_EC_IMAP_ID], 0) table.Restrict(SPropertyRestriction(RELOP_EQ, 0x66010102, SPropValue(0x66010102, entryid)), 0) rows = table.QueryRows(1, 0) assert len(rows) == 1 imap_id_1 = rows[0][0] assert imap_id_1.ulPropTag == PR_EC_IMAP_ID # PR_EC_IMAP_ID must be higher after move assert imap_id_1.Value > uid
def gab_shared_contacts(providersession): user = os.getenv('KOPANO_TEST_USER2') password = os.getenv('KOPANO_TEST_PASSWORD2') socket = os.getenv('KOPANO_SOCKET') session = OpenECSession(user, password, socket, providers=[b'ZARAFA6', b'ZCONTACTS']) store = GetDefaultStore(session) seid = store.GetProps([PR_ENTRYID], 0)[0] usereid = store.GetProps([PR_MAILBOX_OWNER_ENTRYID], 0)[0].Value root = store.OpenEntry(None, None, 0) ceid = root.GetProps([PR_IPM_CONTACT_ENTRYID], 0)[0].Value sharedfolder = session.OpenEntry(ceid, None, 0) shared_eid = sharedfolder.GetProps([PR_ENTRYID], 0)[0] add_folder_to_provider(providersession, seid.Value, shared_eid.Value, 'Shared Contacts') acltab = sharedfolder.OpenProperty(PR_ACL_TABLE, IID_IExchangeModifyTable, 0, MAPI_MODIFY) rowlist = [ ROWENTRY(ROW_ADD, [ SPropValue(PR_MEMBER_ENTRYID, usereid), SPropValue(PR_MEMBER_RIGHTS, ecRightsFolderVisible | ecRightsReadAny) ]) ] acltab.ModifyTable(ROWLIST_REPLACE, rowlist) yield sharedfolder # Reset ACL's acltab.ModifyTable(ROWLIST_REPLACE, [])
def gab_distlist(ab, contacts): '''distlist with two GAB members''' gabdir = ab.GetDefaultDir() gab = ab.OpenEntry(gabdir, None, 0) table = gab.GetContentsTable(MAPI_UNICODE) table.Restrict( SPropertyRestriction(RELOP_EQ, PR_OBJECT_TYPE, SPropValue(PR_OBJECT_TYPE, MAPI_MAILUSER)), 0) table.SetColumns([PR_ENTRYID, PR_ACCOUNT_W, PR_DISPLAY_NAME_W], 0) rows = table.QueryRows(2, 0) wrap = WRAPPED_ENTRYID_PREFIX + WRAPPED_EID_TYPE_PERSONAL_DISTLIST distlist = contacts.CreateMessage(None, 0) distlist.SetProps([ SPropValue(PR_DISPLAY_NAME, b'My GAB Group'), SPropValue(PR_MESSAGE_CLASS, b'IPM.DistList'), SPropValue(0x81041102, [ ab.CreateOneOff(rows[0][2].Value, 'KOPANO', rows[0][1].Value, MAPI_UNICODE), ab.CreateOneOff(rows[1][2].Value, 'KOPANO', rows[1][1].Value, MAPI_UNICODE) ]), SPropValue(0x81051102, [wrap + rows[0][0].Value, wrap + rows[1][0].Value]), ]) distlist.SaveChanges(0)
def test_ab_contact_searchpath(ab, gab_contact_folder): searchpath = ab.GetSearchPath(MAPI_UNICODE) assert len(searchpath) == 2 assert PpropFindProp(searchpath[0], PR_DISPLAY_NAME_W) == SPropValue( 0x3001001F, 'Global Address Book') assert PpropFindProp(searchpath[1], PR_DISPLAY_NAME_W) == SPropValue( 0x3001001F, 'User Folder')
def test_restrict_anr_partialcase(gab): contents = gab.GetContentsTable(0) contents.SetColumns([PR_DISPLAY_NAME], 0) contents.Restrict(SPropertyRestriction(RELOP_EQ, PR_ANR_A, SPropValue(PR_ANR_W, 'EVERY')), 0) rows = contents.QueryRows(-1, 0) assert len(rows) == 1 assert rows[0] == [SPropValue(PR_DISPLAY_NAME, b'Everyone')]
def test_props(gabtable): username = os.getenv('KOPANO_TEST_USER').encode() set_restriction(gabtable) gabtable.SetColumns([PR_ACCOUNT, PR_EMAIL_ADDRESS], 0) rows = gabtable.QueryRows(1, 0) assert rows[0][0] == SPropValue(PR_ACCOUNT, username) assert rows[0][1] == SPropValue(PR_EMAIL_ADDRESS, username)
def test_restrict_anr_ascii(gab): account = os.getenv('KOPANO_TEST_USER3').encode() fullname = os.getenv('KOPANO_TEST_FULLNAME3') contents = gab.GetContentsTable(0) contents.SetColumns([PR_DISPLAY_NAME_W], 0) contents.Restrict(SPropertyRestriction(RELOP_EQ, PR_ANR_A, SPropValue(PR_ANR_A, account)), 0) rows = contents.QueryRows(-1, 0) assert len(rows) == 1 assert rows[0] == [SPropValue(PR_DISPLAY_NAME_W, fullname)]
def test_resolvenames_multi(ab, gab_contact_folder, multi_contact): with pytest.raises(MAPIError) as excinfo: ab.ResolveName(0, MAPI_UNICODE, None, [[SPropValue(PR_DISPLAY_NAME, b'kontakt')]]) assert 'MAPI_E_AMBIGUOUS_RECIP' in str(excinfo) names = ab.ResolveName( 0, MAPI_UNICODE, None, [[SPropValue(PR_DISPLAY_NAME, b'kontakt@somewhere')]]) assert len(names) == 1
def ConvertBMP2PNG(self, message, attnum): attname = u'Unknown' attach = message.OpenAttach(attnum, IID_IAttachment, 0) attprops = attach.GetProps([ PR_ATTACH_LONG_FILENAME_W, PR_DISPLAY_NAME_W, PR_ATTACH_FILENAME_W ], 0) stream = attach.OpenProperty(PR_ATTACH_DATA_BIN, IID_IStream, 0, MAPI_MODIFY) datain = io.BytesIO(stream.Read(0xFFFFFF)) dataout = io.BytesIO() img = Image.open(datain) img.save(dataout, 'PNG') stream.SetSize(0) stream.Seek(0, 0) stream.Write(dataout.getvalue()) stream.Commit(0) props = [ SPropValue(PR_ATTACH_MIME_TAG_W, u'image/png'), SPropValue(PR_ATTACH_EXTENSION_W, u'.png') ] if attprops[0].ulPropTag == PR_ATTACH_LONG_FILENAME_W and attprops[ 0].Value[-4:].upper() == u'.BMP': props.append( SPropValue(PR_ATTACH_LONG_FILENAME_W, attprops[0].Value[0:-3] + u'png')) attname = attprops[0].Value if attprops[1].ulPropTag == PR_DISPLAY_NAME_W and attprops[1].Value[ -4:].upper() == u'.BMP': props.append( SPropValue(PR_DISPLAY_NAME_W, attprops[1].Value[0:-3] + u'png')) if len(attname) == 0: attname = attprops[1].Value if attprops[2].ulPropTag == PR_ATTACH_FILENAME_W and attprops[2].Value[ -4:].upper() == u'.BMP': props.append( SPropValue(PR_ATTACH_FILENAME_W, attprops[2].Value[0:-3] + u'png')) if len(attname) == 0: attname = attprops[2].Value attach.SetProps(props) attach.SaveChanges(0) self.logger.logDebug("*--- [%d] Attachment '%s' converted to png" % (attnum, attname)) return 0
def test_math(item): item.subject = 'test' restriction = Restriction( SPropertyRestriction(RELOP_EQ, PR_SUBJECT_W, SPropValue(PR_SUBJECT_W, 'test'))) assert item.match(restriction) restriction = Restriction( SPropertyRestriction(RELOP_EQ, PR_SUBJECT_W, SPropValue(PR_SUBJECT_W, 'boo'))) assert not item.match(restriction)
def local_distlist(ab, contacts, single_contact): wrap = WRAPPED_ENTRYID_PREFIX + WRAPPED_EID_TYPE_LOCAL_DISTLIST eid = single_contact.GetProps([PR_ENTRYID], 0)[0] distlist = contacts.CreateMessage(None, 0) distlist.SetProps([ SPropValue(PR_DISPLAY_NAME, b'My GAB Group'), SPropValue(PR_MESSAGE_CLASS, b'IPM.DistList'), SPropValue(0x81041102, [ ab.CreateOneOff('dummy', 'KOPANO', 'dummy@localhost', MAPI_UNICODE) ]), SPropValue(0x81051102, [wrap + eid.Value]), ]) distlist.SaveChanges(0)
def test_resolvename(addressbook): assert addressbook.ResolveName(0, 0, None, [[SPropValue(PR_DISPLAY_NAME, b'user10')]]) with pytest.raises(MAPIError) as excinfo: addressbook.ResolveName(0, 0, None, [[SPropValue(PR_DISPLAY_NAME, b'user')]]) assert 'MAPI_E_AMBIGUOUS_RECIP' in str(excinfo) with pytest.raises(MAPIError) as excinfo: addressbook.ResolveName(0, 0, None, [[SPropValue(PR_DISPLAY_NAME, b'notfound')]]) assert 'MAPI_E_NOT_FOUND' in str(excinfo)
def PreRuleProcess(self, session, addrbook, store, rulestable): props = store.GetProps([PR_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID], 0) storeid = props[0].Value folderid = props[1].Value rowlist = [ ROWENTRY(ROW_ADD, [ SPropValue(PR_RULE_LEVEL, 0), SPropValue(PR_RULE_NAME, "dagenttest"), SPropValue(PR_RULE_PROVIDER, "RuleOrganizer"), SPropValue(PR_RULE_STATE, ST_ENABLED), SPropValue(PR_RULE_SEQUENCE, 1), SPropValue( PR_RULE_ACTIONS, ACTIONS(EDK_RULES_VERSION, [ ACTION(ACTTYPE.OP_MOVE, 0x00000000, None, None, 0x00000000, actMoveCopy(storeid, folderid)) ])), SPropValue( PR_RULE_CONDITION, SContentRestriction(FL_SUBSTRING | FL_IGNORECASE, PR_SUBJECT, SPropValue(PR_SUBJECT, 'rulestest'))) ]) ] rulestable.ModifyTable(0, rowlist) return MP_CONTINUE
def ipmnote(inbox): message = inbox.CreateMessage(None, 0) message.SetProps([ SPropValue(PR_SUBJECT, b'test mail'), SPropValue(PR_BODY, b'test body'), SPropValue(PR_MESSAGE_CLASS, b'IPM.Note'), ]) message.SaveChanges(0) yield message eid = message.GetProps([PR_ENTRYID], 0)[0] inbox.DeleteMessages([eid.Value], 0, None, DELETE_HARD_DELETE)
def test_reply_rule(lmtpclient, inbox, outbox, rules, sink, reply_template, create_test_email): '''Reply to a mail matching subject to KOPANO_TEST_USER with a template message in the inbox assoicated messages''' subject = 'reply rule' sender = '*****@*****.**' receiver = os.getenv('KOPANO_TEST_EMAIL') condition = SContentRestriction(FL_SUBSTRING | FL_IGNORECASE, PR_SUBJECT, SPropValue(PR_SUBJECT, subject.encode())) ruleaction = ACTIONS(EDK_RULES_VERSION, [ ACTION(ACTTYPE.OP_REPLY, 0x00000000, None, None, 0x00000000, actReply(reply_template, b'\0' * 16)) ]) add_rule(rules, condition, ruleaction) msg = create_test_email(sender, receiver, subject, '') lmtpclient.sendmail(sender, receiver, msg.as_string()) sink.WaitForNotification(60) table = inbox.GetContentsTable(0) rowcount = table.GetRowCount(0) assert rowcount == 1 table = outbox.GetContentsTable(0) rowcount = table.GetRowCount(0) assert rowcount == 1
def create_forward_rule(rules, subject, gab_user, flavor=0x00000000): condition = SContentRestriction(FL_SUBSTRING | FL_IGNORECASE, PR_SUBJECT, SPropValue(PR_SUBJECT, subject.encode())) ruleaction = ACTIONS(EDK_RULES_VERSION, [ ACTION(ACTTYPE.OP_FORWARD, flavor, None, None, 0x00000000, actFwdDelegate(gab_user)) ]) add_rule(rules, condition, ruleaction)
def create_copymove_rule(rules, action, subject, storeid, wasteid): condition = SContentRestriction(FL_SUBSTRING | FL_IGNORECASE, PR_SUBJECT, SPropValue(PR_SUBJECT, subject.encode())) ruleaction = ACTIONS(EDK_RULES_VERSION, [ ACTION(action, 0x00000000, None, None, 0x00000000, actMoveCopy(storeid, wasteid)) ]) add_rule(rules, condition, ruleaction)
def PostConverting(self, session, addrbook, store, folder, message): body = message.GetProps([PR_BODY], 0)[0].Value lines = [line.strip() for line in body.strip().splitlines()] state = STATE_TEXT body2 = [] attachments = [] uulines = [] for i, line in enumerate(lines): if state == STATE_TEXT: split = line.split(' ') if len(split) >= 3 and split[0] == 'begin': state = STATE_UU uulines = [line] else: body2.append(line) elif state == STATE_UU: uulines.append(line) if line == 'end' and lines[i - 1] == '`' or line == 'end': attachments.append(uulines) body2.extend(['', SNIP_MSG, '']) state = STATE_TEXT if state != STATE_TEXT: body2.extend(uulines) if attachments: self.logger.logDebug(LOG_MSG % len(attachments)) for uulines in attachments: (_, attach) = message.CreateAttach(None, 0) fname = uulines[0].split(' ', 2)[2] self.logger.logDebug('filename: %s' % fname) attach.SetProps([ SPropValue(PR_DISPLAY_NAME, fname), SPropValue(PR_ATTACH_METHOD, 1) ]) attach.SetProps([ SPropValue(PR_ATTACH_FILENAME, fname), SPropValue(PR_ATTACH_METHOD, 1) ]) stream = attach.OpenProperty(PR_ATTACH_DATA_BIN, IID_IStream, 0, MAPI_MODIFY | MAPI_CREATE) stream.Write('\n'.join(uulines) + '\n') attach.SaveChanges(0) message.SetProps([SPropValue(PR_BODY, '\r\n'.join(body2))]) message.SaveChanges(0) return plugintemplates.MP_CONTINUE,
def message(root): message = root.CreateMessage(None, 0) message.SetProps([SPropValue(PR_SUBJECT, b'Test')]) message.SaveChanges(KEEP_OPEN_READWRITE) yield message eid = message.GetProps([PR_ENTRYID], 0)[0] root.DeleteMessages([eid.Value], 0, None, DELETE_HARD_DELETE)
def assert_folder_type(root, folderid, foldertype): proptags = [PR_FOLDER_TYPE] folder = root.OpenEntry(folderid, None, 0) assert folder.GetProps(proptags, 0)[0].Value == foldertype table = root.GetHierarchyTable(CONVENIENT_DEPTH) table.Restrict(SPropertyRestriction(RELOP_EQ, PR_ENTRYID, SPropValue(PR_ENTRYID, folderid)), 0) table.SetColumns(proptags, 0) rows = table.QueryRows(10, 0) assert rows[0][0].Value == foldertype
def test_props_update(root, folder): oldprops = folder.GetProps([PR_LAST_MODIFICATION_TIME, PR_CREATION_TIME, PR_ENTRYID], 0) folderid = oldprops[2].Value folder.SetProps([SPropValue(0x6601001e, b'test')]) folder = root.OpenEntry(folderid, None, 0) props = folder.GetProps([PR_LAST_MODIFICATION_TIME, PR_CREATION_TIME], 0) assert props[0].ulPropTag == PR_LAST_MODIFICATION_TIME assert props[0].Value >= oldprops[0].Value assert props[1].ulPropTag == PR_CREATION_TIME assert props[1].Value == oldprops[1].Value
def add_rule(rules, condition, ruleaction): rules.ModifyTable(0, [ ROWENTRY(ROW_ADD, [ SPropValue(PR_RULE_LEVEL, 0), SPropValue(PR_RULE_NAME, b'test_rule'), SPropValue(PR_RULE_PROVIDER, b'RuleOrganizer'), SPropValue(PR_RULE_STATE, ST_ENABLED), SPropValue(PR_RULE_SEQUENCE, 1), SPropValue(PR_RULE_CONDITION, condition), SPropValue(PR_RULE_ACTIONS, ruleaction) ]) ])
def test_ab_depth(abroot, gab_contact_folder): hierarchy = abroot.GetHierarchyTable(MAPI_UNICODE | CONVENIENT_DEPTH) hierarchy.Restrict( SPropertyRestriction(RELOP_EQ, PR_AB_PROVIDER_ID, SPropValue(PR_AB_PROVIDER_ID, CONTACTS_GUID)), 0) rows = hierarchy.QueryRows(-1, 0) assert len(rows) == 2 assert PpropFindProp(rows[0], PR_DISPLAY_NAME_W).Value == 'Kopano Contacts Folders' assert PpropFindProp(rows[0], PR_DEPTH).Value == 0 assert PpropFindProp(rows[1], PR_DISPLAY_NAME_W).Value == 'User Folder' assert PpropFindProp(rows[1], PR_DEPTH).Value == 1
def test_delegate_permissions_owner(publicfolder, session3, kind, rights, folder_access, message_access): print(kind) access = publicfolder.GetProps([PR_ACCESS], 0)[0].Value defaultAccess = MAPI_ACCESS_MODIFY | MAPI_ACCESS_READ | MAPI_ACCESS_DELETE | MAPI_ACCESS_CREATE_HIERARCHY | MAPI_ACCESS_CREATE_CONTENTS assert defaultAccess == access store = GetDefaultStore(session3) userid = session3.QueryIdentity() folderid = publicfolder.GetProps([PR_ENTRYID], 0)[0].Value acls = publicfolder.OpenProperty(PR_ACL_TABLE, IID_IExchangeModifyTable, 0, MAPI_MODIFY) rowlist = [ ROWENTRY(ROW_ADD, [ SPropValue(PR_MEMBER_ENTRYID, userid), SPropValue(PR_MEMBER_RIGHTS, rights) ]) ] acls.ModifyTable(ROWLIST_REPLACE, rowlist) message = publicfolder.CreateMessage(None, 0) message.SetProps([SPropValue(PR_SUBJECT, b'acl test')]) message.SaveChanges(0) eid = message.GetProps([PR_ENTRYID], 0)[0].Value access = message.GetProps([PR_ACCESS], 0)[0].Value assert access == MAPI_ACCESS_MODIFY | MAPI_ACCESS_READ | MAPI_ACCESS_DELETE # as user3, open public and get folder access public = GetPublicStore(session3) folder = public.OpenEntry(folderid, None, MAPI_BEST_ACCESS) access = folder.GetProps([PR_ACCESS], 0)[0].Value assert access == folder_access message = folder.OpenEntry(eid, None, 0) access = message.GetProps([PR_ACCESS], 0)[0].Value assert access == message_access
def rule(inbox): target = inbox.folder('target', create=True) restriction = kopano.Restriction( SPropertyRestriction(RELOP_EQ, PR_SUBJECT_W, SPropValue(PR_SUBJECT_W, 'test')) ) rule = inbox.create_rule('test', restriction) rule.create_action('move', target) # reload rule rule = next(inbox.rules()) yield rule inbox.delete(target) # Clear the rules table inbox.mapiobj.DeleteProps([PR_RULES_TABLE])
def upgrade_findroot(store): findroot = store.root.get_folder('FINDER_ROOT') if not findroot: print('creating FINDER_ROOT for store %s' % store.guid) # create findroot findroot = store.root.folder('FINDER_ROOT', create=True) # set PR_FINDER_ENTRYID # XXX pyko: store.findroot = .. entryid = HrGetOneProp(findroot.mapiobj, PR_ENTRYID) store.mapiobj.SetProps([SPropValue(PR_FINDER_ENTRYID, entryid.Value)]) store.mapiobj.SaveChanges(0) # add findroot ACL findroot.permission(kopano.Group('Everyone'), create=True).rights = FINDROOT_RIGHTS
def multi_contact(ab, single_contact): single_contact.SetProps([ SPropValue(0x8140001E, b'my second name here'), SPropValue(0x8142001E, b'SMTP'), SPropValue(0x8143001E, b'*****@*****.**'), SPropValue(0x8144001E, b'Kontakt Original Display 2'), SPropValue( 0x81450102, ab.CreateOneOff(b'Kontakt Original Display 2', b'SMTP', b'*****@*****.**', 0)), SPropValue(0x80D81003, [0, 1]), SPropValue(0x80D90003, 3) ]) single_contact.SaveChanges(0)
def fax_contact(ab, single_contact): # fax contact, what outlook 2007 sets when editing a business fax field single_contact.SetProps([ SPropValue(0x8172001E, b'FAX'), SPropValue(0x8173001E, b'Kontakt Name@+31 (010) 1234567'), SPropValue(0x8174001E, b'Business Fax Original Display'), SPropValue( 0x81750102, ab.CreateOneOff(b'Business Fax Original Display', b'FAX', b'Kontakt Name@+31 (010) 1234567', 0)), SPropValue(0x80D81003, [0, 3]), SPropValue(0x80D90003, 7) # address:32808 / address:32809 ]) single_contact.SaveChanges(0)
def test_forward_external_rule(lmtpclient, inbox, outbox, rules, sink, reply_template, create_test_email): '''Forward mail matching subject to [email protected], should be disallowed with an reject in inbox''' subject = 'forward external rule' sender = '*****@*****.**' external = '*****@*****.**' receiver = os.getenv('KOPANO_TEST_EMAIL') external = [ SPropValue(PR_ADDRTYPE, b'SMTP'), SPropValue(PR_EMAIL_ADDRESS, external.encode()), SPropValue(PR_SMTP_ADDRESS, external.encode()), SPropValue(PR_DISPLAY_NAME, 'external contractor'.encode()), SPropValue(PR_OBJECT_TYPE, 6), SPropValue(PR_RECIPIENT_TYPE, MAPI_TO), SPropValue(PR_SEARCH_KEY, b'SMTP:' + external.encode()) ] create_forward_rule(rules, subject, [external]) msg = create_test_email(sender, receiver, subject, '') lmtpclient.sendmail(sender, receiver, msg.as_string()) sink.WaitForNotification(60) table = inbox.GetContentsTable(0) rowcount = table.GetRowCount(0) assert rowcount == 2 rows = table.QueryRows(2, 0) assert subject.encode() == PpropFindProp(rows[0], PR_SUBJECT).Value assert b'rule not forwarded (administratively blocked)' in PpropFindProp( rows[1], PR_SUBJECT).Value table = outbox.GetContentsTable(0) rowcount = table.GetRowCount(0) assert not rowcount