Integration testing of a custom JIRA CreateOrCommentHandler email processor

I'm working on a customised email handler (based on Atlassian's original CreateOrCommentHandler class), that extends some of the available parameters to enable our team to better handle their Jira loads.

I won't go into too much detail about the handler itself, but I would like to document the test harness that allows me to perform some integration/regression testing against it.

The test environment includes:

  • an instance of JIRA (with some manual modifications)
  • an IMAP/SMTP server (Apache James).

JIRA Environment

An email message handler is not a normal plugin module type for JIRA (see the list here), so there is no entry in the atlassian-plugin.xml required for it. The plugin infrastructure in this case is merely a vehicle to upload the classes into Jira.

IMAP Service

We need to manually enable access to the custom handler by editing the imapservice.xml file (under WEB-INF/classes/services/com/atlassian/jira/service/services/imap/imapservice.xml) to include our class:

...
<property>
    <key>handler</key>
    <name>admin.service.common.handler</name>
    <type>select</type>
    <values>
        <value>
            <key>com.example.CreateOrCommentHandler</key>
            <value>Custom CreateOrCommentHandler</value>
        </value>
        <value>
            <key>com.example.CreateIssueHandler</key>
            <value>Custom CreateIssueHandler</value>
        </value>
        ...
        (Additional handlers go here!)
        ...
    </values>
</property>
...

Once this is done (and JIRA is restarted), we can add services that use this handler by adding a regular IMAPService, and selecting our custom handler.

JIRA Configuration

We also need to set JIRA up with some dummy data - e.g. projects, users - and export this configuration as a testing baseline (using the administration tool).

IMAP/SMTP Server

Can't really test an email handling plugin without an email!

Rather than mocking out a whole bunch of complex classes (the message itself and JIRA's internals), using a lightweight mail server and simulating a real environment would be much simpler.

I found Apache James, and was able to quickly get it up and running as a standalone server (To use the default ports however - SMTP: 25, IMAP: 143 -, you need to start the server as root). In the default configuration, James will create a user account for the receiver(s) when receiving an email, if they don't already exist. So we are free to send emails to and check the accounts of which ever user we want, without a setup overhead!

Email Client

A utility class will help us to send emails and clear user's inboxes (this class based on Claude Duguay's IBM article).

The parent class javax.mail.Authenticator helps us with the javax.mail.Session connection.

public class JamesMailClient extends Authenticator {

    public static final int SHOW_MESSAGES = 1;

    public static final int CLEAR_MESSAGES = 2;

    public static final int SHOW_AND_CLEAR = SHOW_MESSAGES + CLEAR_MESSAGES;


    private Session _session;

    private PasswordAuthentication _passwordAuthentication;

    private String _userAddress;

    private String _user;

    private String _pass;

    private String _host;


    public JamesMailClient(String user, final String pass, String host, boolean debug) {
        _user = user;
        _host = host;
        _userAddress = _user + '@' + _host;

        _pass = pass;
        _passwordAuthentication = new PasswordAuthentication(user, _pass);

        Properties props = new Properties();
        props.put("mail.user", user);
        props.put("mail.host", host);
        props.put("mail.debug", debug ? "true" : "false");
        props.put("mail.store.protocol", "imap");
        props.put("mail.transport.protocol", "smtp");

        _session = Session.getInstance(props, this);
    }

    public void checkInbox(int mode) throws MessagingException, IOException {..}

    public void sendMessage(String recipientTo, String recipientCC, String messageSubject, String messageBody) throws MessagingException {..}

    public boolean waitForIncomingEmail(long timeout, int emailCount) throws MessagingException, InterruptedException {..}
}

Test class structure

The test classes extend Atlassian's com.atlassian.jira.functest.framework.FuncTestCase, which provides us with access to convenience functions to restore data from XML and navigate JIRA easily.

The test harness is handled by overridding setUpTest() and tearDownTest() (Note that to be a 'good neighbour', I restored a 'clean' version of the config when we're done):

@Override                                                                        ˚
protected void setUpTest() {
    super.setUpTest();

    mailUserAccount = new JamesMailClient("user", "user", "localhost", false);
    _mailBugsAccount = new JamesMailClient("bugs", "bugs", "localhost", false);

    try {
        _mailUserAccount.checkInbox(JamesMailClient.CLEARMESSAGES);
        mailBugsAccount.checkInbox(JamesMailClient.CLEARMESSAGES);
    } catch (MessagingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    //Import some test data.
    administration.restoreData("jira-test-base.xml");
}

@Override
protected void tearDownTest() {
    super.tearDownTest();

    administration.restoreData("jira-clean-install.xml");
    try {
        mailBugsAccount.checkInbox(JamesMailClient.CLEARMESSAGES);
        mailUserAccount.checkInbox(JamesMailClient.CLEARMESSAGES);
    } catch (Exception e) {
        e.printStackTrace();
    }
}