diff --git a/contact_attach.test b/contact_attach.test
index 5e84f52..b87dd0a 100644
--- a/contact_attach.test
+++ b/contact_attach.test
@@ -196,3 +196,378 @@ class ContactAttachSettingsFormTestCase extends DrupalWebTestCase {
     $this->postInvalidSettings($role);
   }
 }
+
+/**
+ * Tests the Contact Attach functionality for site-wide and user contact forms.
+ */
+class ContactAttachContactFormsTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Contact Attach contact forms',
+      'description' => 'Tests attachment functionality on the site-wide and user contact forms.',
+      'group' => 'Contact Attach',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('contact_attach');
+
+    // Grant all users access to contact forms.
+    user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array(
+      'access site-wide contact form',
+      'access user contact forms'
+    ));
+    user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array(
+      'access site-wide contact form',
+      'access user contact forms'
+    ));
+
+    // Create a default role for site administrators.
+    $admin_role = new stdClass();
+    $admin_role->name = 'administrator';
+    $admin_role->weight = 2;
+    user_role_save($admin_role);
+    user_role_grant_permissions($admin_role->rid, array_keys(module_invoke_all('permission')));
+    // Set this as the administrator role.
+    variable_set('user_admin_role', $admin_role->rid);
+
+    // Enable contact forms for users by default.
+    variable_set('contact_default_status', TRUE);
+    // Avoid that the flood control causes the tests to fail.
+    variable_set('contact_threshold_limit', 50);
+
+    // Create an admin user for its contact form to be used in the tests.
+    $this->admin_user = $this->drupalCreateUser();
+    // Create a user that only has the authenticated user role.
+    $this->auth_user = $this->drupalCreateUser();
+    // drupalCreateUser() does not remove the anonymous role from the new user.
+    unset($this->auth_user->roles[DRUPAL_ANONYMOUS_RID]);
+    // Create a user with its own role that will later be assigned a 2nd role.
+    $this->specific_role_user = $this->drupalCreateUser(array(
+      'access site-wide contact form',
+      'access user contact forms',
+    ));
+
+    // Figure out rid of the role created when specific_role_user was created.
+    $specific_user_roles = $this->specific_role_user->roles;
+    unset($specific_user_roles[DRUPAL_AUTHENTICATED_RID]);
+    reset($specific_user_roles);
+    $this->created_role_rid = key($specific_user_roles);
+    // Create a second role for the specific role user.
+    $this->created_role_2_rid = $this->drupalCreateRole(array(
+      'access site-wide contact form',
+      'access user contact forms',
+    ));
+
+    // Assign the "administrator" role to the admin user.
+    db_insert('users_roles')
+      ->fields(array('uid' => $this->admin_user->uid, 'rid' => $admin_role->rid))
+      ->execute();
+    // Assign the specific role user the second created role.
+    db_insert('users_roles')
+      ->fields(array(
+        'uid' => $this->specific_role_user->uid,
+        'rid' => $this->created_role_2_rid,
+      ))
+      ->execute();
+    $this->specific_role_user->roles[$this->created_role_2_rid] = (string) $this->created_role_2_rid;
+  }
+
+  /**
+   * Tests the attachment functionality on the site-wide and user contact forms.
+   */
+  function testContactAttachContactForms() {
+    $this->contact_forms = array(
+      'contact' => 'send attachments with site-wide contact form',
+      'user/' . $this->admin_user->uid . '/contact' => 'send attachments with user contact forms',
+    );
+    $this->contact_forms_short = array(
+      'site' => 'contact',
+      'user' => 'user/' . $this->admin_user->uid . '/contact',
+    );
+    $this->message = array(
+      'name' => 'Kalle Klovn',
+      'mail' => 'kalle.klovn@example.com',
+      'subject' => 'Test message',
+      'message' => "This is a test message with 1 attachment.",
+    );
+
+    // Check the existence of attachment fields on the site-wide and user
+    // contact forms with and without permissions granted.
+    $this->checkExistenceOfAttachmentFields(DRUPAL_ANONYMOUS_RID);
+    $this->checkExistenceOfAttachmentFields(DRUPAL_AUTHENTICATED_RID, $this->auth_user);
+
+    // Verify that the user can not send messages with attachments when the user
+    // has permission to attach files, but no settings have been set.
+    $this->submitAttachmentWhenNoSettingsSet();
+    $this->submitAttachmentWhenNoSettingsSet($this->auth_user);
+
+    // Verify that the correct number of attachments appear on the contact forms
+    // after this setting has been set.
+    $this->attachment_numbers = array();
+    $this->checkNumberOfAttachmentFields(DRUPAL_ANONYMOUS_RID, '2');
+    $this->checkNumberOfAttachmentFields(DRUPAL_AUTHENTICATED_RID, '5', $this->auth_user);
+    // Ensure that the number of attachments for the specific role overrides the
+    // number of attachments defined for the authenticated user role.
+    $this->checkNumberOfAttachmentFields($this->created_role_rid, '4', $this->specific_role_user);
+    // Ensure that the number of attachments for the user's second role is
+    // accounted for, but that the number of attachments of the first created
+    // role is used, as it's higher.
+    $this->checkNumberOfAttachmentFields($this->created_role_2_rid, '3', $this->specific_role_user);
+
+    // Verify that the correct allowed extensions and maximum file size is taken
+    // into account after these settings have been set.
+    $this->extensions = array();
+    $this->uploadsizes = array();
+    $this->submitWithAttachments(DRUPAL_ANONYMOUS_RID, 'html', '0.00107421875');
+    $this->submitWithAttachments(DRUPAL_AUTHENTICATED_RID, 'sql', '0.00131835938', $this->auth_user);
+    // Ensure that the maximum allowed file size for the specific role overrides
+    // the maximum allowed file size defined for the authenticated user role.
+    $this->submitWithAttachments($this->created_role_rid, 'patch', '0.00126953125', $this->specific_role_user);
+    // Ensure that the settings for the user's second role is accounted for, but
+    // that the maximum upload size of the first created role is used, as it's
+    // higher.
+    $this->submitWithAttachments($this->created_role_2_rid, 'tgv', '0.001171875', $this->specific_role_user);
+  }
+
+  /**
+   * Checks existence of attachment fields on site-wide and user contact forms.
+   *
+   * @param int $rid
+   *   The role ID of the role to grant permissions.
+   * @param object $user
+   *   (optional) A fully-loaded $user object of the user to log in. Defaults to
+   *   NULL.
+   */
+  function checkExistenceOfAttachmentFields($rid, $user = NULL) {
+    if ($user) {
+      $this->drupalLoginWithUserGlobalRolesUpdate($user);
+    }
+    foreach ($this->contact_forms as $contact_form => $permission) {
+      $this->drupalGet($contact_form);
+      // Ensure that there are no attachment fields when the user's roles do not
+      // have the necessary permissions to attach files.
+      $this->assertNoRaw('files[contact_attach_1]', 'Attachment field does NOT appear on contact form.');
+      user_role_grant_permissions($rid, array($permission));
+      $this->drupalGet($contact_form);
+      // Ensure that there is one attachment field when the user's roles have
+      // permission to attach files, but the settings for the roles have not
+      // been set.
+      $this->assertRaw('files[contact_attach_1]', '1 attachment field appears on contact form.');
+      $this->assertNoRaw('files[contact_attach_2]', 'More than 1 attachment field does NOT appear on contact form.');
+    }
+    if ($user) {
+      $this->drupalLogoutWithUserGlobalRolesUpdate();
+    }
+  }
+
+  /**
+   * Submits contact forms with attachments when no settings are set.
+   *
+   * @param object $user
+   *   (optional) A fully-loaded $user object of the user to log in. Defaults to
+   *   NULL.
+   */
+  function submitAttachmentWhenNoSettingsSet($user = NULL) {
+    if ($user) {
+      $this->drupalLoginWithUserGlobalRolesUpdate($user);
+    }
+    foreach ($this->contact_forms as $contact_form => $permission) {
+      // Ensure that the user can not send messages with attachments when no
+      // settings have been set for the user's roles.
+      $file = current($this->drupalGetTestFiles('image', 1831));
+      $this->message['files[contact_attach_1]'] = drupal_realpath($file->uri);
+      $this->drupalPost($contact_form, $this->message, t('Send message'));
+      $this->assertNoText(t('Your message has been sent.'));
+      $this->assertUniqueText(t('The specified file @filename could not be uploaded.', array('@filename' => $file->filename)));
+      $this->assertUniqueText(t('Only files with the following extensions are allowed: .'));
+      $this->assertUniqueText(t('The file is @filesize exceeding the maximum file size of 1 KB.', array('@filesize' => format_size(filesize($file->uri)))));
+
+      // Ensure that a 1 byte file can be attached, but will still be refused
+      // because no allowed extensions have been set for the user's roles.
+      $tinyfile_name = 'tinyfile.txt';
+      file_put_contents('public://' . $tinyfile_name, '1');
+      $this->message['files[contact_attach_1]'] = drupal_realpath('public://' . $tinyfile_name);
+      $this->drupalPost($contact_form, $this->message, t('Send message'));
+      $this->assertNoText(t('Your message has been sent.'));
+      $this->assertUniqueText(t('The specified file @filename could not be uploaded. Only files with the following extensions are allowed: .', array('@filename' => $tinyfile_name)));
+    }
+    if ($user) {
+      $this->drupalLogoutWithUserGlobalRolesUpdate();
+    }
+  }
+
+  /**
+   * Checks the number of attachment fields after setting the variable.
+   *
+   * @param int $rid
+   *   The role ID of the role to be checked.
+   * @param string $contact_attach_number
+   *   The number of attachments to be allowed for the role defined in $rid.
+   * @param object $user
+   *   (optional) A fully-loaded $user object of the user to log in. Defaults to
+   *   NULL.
+   */
+  function checkNumberOfAttachmentFields($rid, $contact_attach_number, $user = NULL) {
+    if ($user) {
+      $this->drupalLoginWithUserGlobalRolesUpdate($user);
+    }
+    foreach ($this->contact_forms_short as $contact_form_short => $contact_form_path) {
+      if ($contact_form_short === 'site') {
+        $contact_form_long = 'site-wide contact form';
+      }
+      elseif ($contact_form_short === 'user') {
+        $contact_form_long = 'user contact forms';
+      }
+      $this->attachment_numbers[$rid] = $contact_attach_number;
+      variable_set('contact_attach_number_' . $contact_form_short, $this->attachment_numbers);
+      $roles = _contact_attach_get_valid_roles($contact_form_long, $this->attachment_numbers);
+      $attachments_allowed = _contact_attach_return_max_attachments($roles, $this->attachment_numbers);
+
+      $this->drupalGet($contact_form_path);
+      $this->assertRaw('files[contact_attach_' . $attachments_allowed . ']', $attachments_allowed . ' attachment fields appear on contact form.');
+      $this->assertNoRaw('files[contact_attach_' . ($attachments_allowed + 1) . ']', 'More than ' . $attachments_allowed . ' attachment fields do NOT appear on contact form.');
+    }
+    if ($user) {
+      $this->drupalLogoutWithUserGlobalRolesUpdate();
+    }
+  }
+
+  /**
+   * Submits contact forms with attachments after setting the settings.
+   *
+   * @param int $rid
+   *   The role ID of the role.
+   * @param string $extensions
+   *   The extensions that will be set as allowed for the role.
+   * @param string $uploadsize
+   *   The maximum allowed file size that will be set for the role.
+   * @param object $user
+   *   (optional) A fully-loaded $user object of the user to log in. Defaults to
+   *   NULL.
+   */
+  function submitWithAttachments($rid, $extensions, $uploadsize, $user = NULL) {
+    if ($user) {
+      $this->drupalLoginWithUserGlobalRolesUpdate($user);
+    }
+    foreach ($this->contact_forms_short as $contact_form_short => $contact_form_path) {
+      if ($contact_form_short === 'site') {
+        $contact_form_long = 'site-wide contact form';
+        $mail_id = 'contact_page_mail';
+        $mail_subject_start = '[Website feedback] ';
+      }
+      elseif ($contact_form_short === 'user') {
+        $contact_form_long = 'user contact forms';
+        $mail_id = 'contact_user_mail';
+        $mail_subject_start = '[Drupal] ';
+      }
+      $this->extensions[$rid] = $extensions;
+      $this->uploadsizes[$rid] = $uploadsize;
+      variable_set('contact_attach_extensions_' . $contact_form_short, $this->extensions);
+      variable_set('contact_attach_uploadsize_' . $contact_form_short, $this->uploadsizes);
+      $contact_attach_numbers = variable_get('contact_attach_number_' . $contact_form_short, array());
+      $roles = _contact_attach_get_valid_roles($contact_form_long, $contact_attach_numbers);
+      $allowed_extensions = _contact_attach_return_allowed_extensions($roles, $contact_form_short);
+      $file_size_limit = _contact_attach_return_max_file_size($roles, $contact_form_short);
+
+      $this->message['subject'] = 'Test message for role ' . $rid;
+
+      // Ensure that the user can not send messages with attachments when the
+      // file depasses the maximum file size and the file does not have an
+      // allowed extension.
+      $filename = 'testfile1.txt';
+      file_put_contents('public://' . $filename, $this->randomString(1433));
+      $this->message['files[contact_attach_1]'] = drupal_realpath('public://' . $filename);
+      $this->drupalPost($contact_form_path, $this->message, t('Send message'));
+      $this->assertNoText(t('Your message has been sent.'));
+      $this->assertUniqueText(t('The specified file @filename could not be uploaded.', array('@filename' => $filename)));
+      $this->assertUniqueText(t('Only files with the following extensions are allowed: @extensions.', array('@extensions' => $allowed_extensions)));
+      $this->assertUniqueText(t('The file is @filesize exceeding the maximum file size of @maxsize.', array('@filesize' => format_size(filesize('public://' . $filename)), '@maxsize' => format_size($file_size_limit))));
+
+      // Ensure that a 1 byte file can be attached, but will still be refused
+      // because it is not an allowed extension.
+      $tinyfile_name = 'tinyfile.txt';
+      file_put_contents('public://' . $tinyfile_name, '1');
+      $this->message['files[contact_attach_1]'] = drupal_realpath('public://' . $tinyfile_name);
+      $this->drupalPost($contact_form_path, $this->message, t('Send message'));
+      $this->assertNoText(t('Your message has been sent.'));
+      $this->assertUniqueText(t('The specified file @filename could not be uploaded. Only files with the following extensions are allowed: @extensions.', array('@filename' => $tinyfile_name, '@extensions' => $allowed_extensions)));
+
+      // Ensure that a file with an allowed extension can be attached, but will
+      // still be refused as it depasses the maximum file size.
+      $filename = 'testfile2.' . ltrim(substr($extensions, strrpos($extensions, ' ')));
+      file_put_contents('public://' . $filename, $this->randomString(1433));
+      $this->message['files[contact_attach_1]'] = drupal_realpath('public://' . $filename);
+      $this->drupalPost($contact_form_path, $this->message, t('Send message'));
+      $this->assertNoText(t('Your message has been sent.'));
+      $this->assertUniqueText(t('The specified file @filename could not be uploaded. The file is @filesize exceeding the maximum file size of @maxsize.', array('@filename' => $filename, '@filesize' => format_size(filesize('public://' . $filename)), '@maxsize' => format_size($file_size_limit))));
+
+      // Ensure that the user can send messages with attachments when the file
+      // does not depass the maximum file size and the file has an allowed
+      // extension.
+      $file = new stdClass();
+      $file->filename = 'testfile3-' . $contact_form_short . '.' . ltrim(substr($extensions, strrpos($extensions, ' ')));
+      $file->uri = 'temporary://' . $file->filename;
+      // Put the submitted file in another location so that file_save_upload()
+      // does not rename its saved file because the filename already exists.
+      file_put_contents('public://' . $file->filename, '1');
+      $file->filemime = file_get_mimetype($file->filename);
+      $this->message['files[contact_attach_1]'] = drupal_realpath('public://' . $file->filename);
+      $this->drupalPost($contact_form_path, $this->message, t('Send message'));
+      $this->assertUniqueText(t('Your message has been sent.'));
+      $this->assertNoText(t('The specified file @filename could not be uploaded.', array('@filename' => $file->filename)));
+      $this->assertNoText(t('Only files with the following extensions are allowed: @extensions.', array('@extensions' => $allowed_extensions)));
+      $this->assertNoText(t('The file is @filesize exceeding the maximum file size of @maxsize.', array('@filesize' => format_size(filesize($file->uri)), '@maxsize' => format_size($file_size_limit))));
+
+      // Verify that the mail was successfully sent and that the attachment is a
+      // part of the body.
+      $captured_email = $this->drupalGetMails(array(
+        'id' => $mail_id,
+        'from' => $this->message['mail'],
+        'subject' => $mail_subject_start . $this->message['subject'],
+      ));
+      $this->assertEqual(count($captured_email), 1, t('One mail successfully sent.'));
+      list( , $boundary_id) = explode('"', $captured_email[0]['headers']['Content-Type']);
+      $this->assertEqual(substr_count($captured_email[0]['body'], $boundary_id), 3, 'Boundary ID appears 3 times in the sent message.');
+      $attachment = "--$boundary_id\n" . _contact_attach_add_attachment($file) . "\n\n--$boundary_id--\n";
+      // chunk_split() uses \r\n as line ending sequence by default, but a mail
+      // function removes \r, so we must do the same for the comparison to work.
+      $attachment = str_replace("\r\n", "\n", $attachment);
+      $this->assertEqual(strstr($captured_email[0]['body'], $attachment), $attachment, 'Attachment exists in the body of the sent message.');
+    }
+    if ($user) {
+      $this->drupalLogoutWithUserGlobalRolesUpdate();
+    }
+  }
+
+  /**
+   * Logs in a user with the internal browser and updates the $user global.
+   *
+   * drupalLogin() does not update the $user global after logging in. This
+   * function populates $user->roles with the roles defined in the user object.
+   *
+   * @param stdClass $user_object
+   *   User object representing the user to log in.
+   */
+  function drupalLoginWithUserGlobalRolesUpdate(stdClass $user_object) {
+    global $user;
+
+    $this->drupalLogin($user_object);
+    $user->roles = $user_object->roles;
+  }
+
+  /**
+   * Logs out a user with the internal browser and updates the $user global.
+   *
+   * drupalLogout() does not update the $user global after logging out. This
+   * function populates $user->roles with the anonymous role.
+   */
+  function drupalLogoutWithUserGlobalRolesUpdate() {
+    global $user;
+
+    $this->drupalLogout();
+    $user->roles = array(DRUPAL_ANONYMOUS_RID => 'anonymous user');
+  }
+}
