Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit edb44ed3 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

Merge branch 'origin-main-to-upstream-#1-6.712-from-6650f8f6-to-bec88547' into...

Merge branch 'origin-main-to-upstream-#1-6.712-from-6650f8f6-to-bec88547' into 'main-6.711-to-upstream-6.712'

MR 1: Merge upstream v6.712 changes

See merge request !164
parents a3cc64d4 9b9a1446
Loading
Loading
Loading
Loading
Loading
+0 −229
Original line number Diff line number Diff line
package com.fsck.k9.preferences;


import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import android.content.Context;

import com.fsck.k9.K9RobolectricTest;
import com.fsck.k9.Preferences;
import com.fsck.k9.mail.AuthType;
import kotlin.text.Charsets;
import okio.Buffer;
import org.junit.Before;
import org.junit.Test;
import org.robolectric.RuntimeEnvironment;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;


public class SettingsImporterTest extends K9RobolectricTest {
    private final Context context = RuntimeEnvironment.getApplication();

    @Before
    public void before() {
        deletePreExistingAccounts();
    }

    private void deletePreExistingAccounts() {
        Preferences preferences = Preferences.getPreferences();
        preferences.clearAccounts();
    }

    @Test(expected = SettingsImportExportException.class)
    public void importSettings_throwsExceptionOnBlankFile() throws SettingsImportExportException {
        InputStream inputStream = inputStreamOf("");
        List<String> accountUuids = new ArrayList<>();

        SettingsImporter.importSettings(context, inputStream, true, accountUuids, true);
    }

    @Test(expected = SettingsImportExportException.class)
    public void importSettings_throwsExceptionOnMissingFormat() throws SettingsImportExportException {
        InputStream inputStream = inputStreamOf("<k9settings version=\"1\"></k9settings>");
        List<String> accountUuids = new ArrayList<>();

        SettingsImporter.importSettings(context, inputStream, true, accountUuids, true);
    }

    @Test(expected = SettingsImportExportException.class)
    public void importSettings_throwsExceptionOnInvalidFormat() throws SettingsImportExportException {
        InputStream inputStream = inputStreamOf("<k9settings version=\"1\" format=\"A\"></k9settings>");
        List<String> accountUuids = new ArrayList<>();

        SettingsImporter.importSettings(context, inputStream, true, accountUuids, true);
    }

    @Test(expected = SettingsImportExportException.class)
    public void importSettings_throwsExceptionOnNonPositiveFormat() throws SettingsImportExportException {
        InputStream inputStream = inputStreamOf("<k9settings version=\"1\" format=\"0\"></k9settings>");
        List<String> accountUuids = new ArrayList<>();

        SettingsImporter.importSettings(context, inputStream, true, accountUuids, true);
    }

    @Test(expected = SettingsImportExportException.class)
    public void importSettings_throwsExceptionOnMissingVersion() throws SettingsImportExportException {
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\"></k9settings>");
        List<String> accountUuids = new ArrayList<>();

        SettingsImporter.importSettings(context, inputStream, true, accountUuids, true);
    }

    @Test(expected = SettingsImportExportException.class)
    public void importSettings_throwsExceptionOnInvalidVersion() throws SettingsImportExportException {
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"A\"></k9settings>");
        List<String> accountUuids = new ArrayList<>();

        SettingsImporter.importSettings(context, inputStream, true, accountUuids, true);
    }

    @Test(expected = SettingsImportExportException.class)
    public void importSettings_throwsExceptionOnNonPositiveVersion() throws SettingsImportExportException {
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"0\"></k9settings>");
        List<String> accountUuids = new ArrayList<>();

        SettingsImporter.importSettings(context, inputStream, true, accountUuids, true);
    }

    @Test
    public void parseSettings_account() throws SettingsImportExportException {
        String validUUID = UUID.randomUUID().toString();
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"1\">" +
                "<accounts><account uuid=\"" + validUUID + "\"><name>Account</name></account></accounts></k9settings>");
        List<String> accountUuids = new ArrayList<>();
        accountUuids.add("1");

        SettingsImporter.Imported results = SettingsImporter.parseSettings(inputStream, true, accountUuids, true);

        assertEquals(1, results.accounts.size());
        assertEquals("Account", results.accounts.get(validUUID).name);
        assertEquals(validUUID, results.accounts.get(validUUID).uuid);
    }

    @Test
    public void parseSettings_account_identities() throws SettingsImportExportException {
        String validUUID = UUID.randomUUID().toString();
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"1\">" +
                "<accounts><account uuid=\"" + validUUID + "\"><name>Account</name>" +
                "<identities><identity><email>user@gmail.com</email></identity></identities>" +
                "</account></accounts></k9settings>");
        List<String> accountUuids = new ArrayList<>();
        accountUuids.add("1");

        SettingsImporter.Imported results = SettingsImporter.parseSettings(inputStream, true, accountUuids, true);

        assertEquals(1, results.accounts.size());
        assertEquals(validUUID, results.accounts.get(validUUID).uuid);
        assertEquals(1, results.accounts.get(validUUID).identities.size());
        assertEquals("user@gmail.com", results.accounts.get(validUUID).identities.get(0).email);
    }


    @Test
    public void parseSettings_account_cram_md5() throws SettingsImportExportException {
        String validUUID = UUID.randomUUID().toString();
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"1\">" +
                "<accounts><account uuid=\"" + validUUID + "\"><name>Account</name>" +
                "<incoming-server><authentication-type>CRAM_MD5</authentication-type></incoming-server>" +
                "</account></accounts></k9settings>");
        List<String> accountUuids = new ArrayList<>();
        accountUuids.add(validUUID);

        SettingsImporter.Imported results = SettingsImporter.parseSettings(inputStream, true, accountUuids, false);

        assertEquals("Account", results.accounts.get(validUUID).name);
        assertEquals(validUUID, results.accounts.get(validUUID).uuid);
        assertEquals(AuthType.CRAM_MD5, results.accounts.get(validUUID).incoming.authenticationType);
    }

    @Test
    public void importSettings_disablesAccountsNeedingPasswords() throws SettingsImportExportException {
        String validUUID = UUID.randomUUID().toString();
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"1\">" +
                "<accounts><account uuid=\"" + validUUID + "\"><name>Account</name>" +
                "<incoming-server type=\"IMAP\">" +
                "<connection-security>SSL_TLS_REQUIRED</connection-security>" +
                "<username>user@gmail.com</username>" +
                "<authentication-type>CRAM_MD5</authentication-type>" +
                "<host>googlemail.com</host>" +
                "</incoming-server>" +
                "<outgoing-server type=\"SMTP\">" +
                "<connection-security>SSL_TLS_REQUIRED</connection-security>" +
                "<username>user@googlemail.com</username>" +
                "<authentication-type>CRAM_MD5</authentication-type>" +
                "<host>googlemail.com</host>" +
                "</outgoing-server>" +
                "<settings><value key=\"a\">b</value></settings>" +
                "<identities><identity><email>user@gmail.com</email></identity></identities>" +
                "</account></accounts></k9settings>");
        List<String> accountUuids = new ArrayList<>();
        accountUuids.add(validUUID);

        SettingsImporter.ImportResults results = SettingsImporter.importSettings(
                context, inputStream, true, accountUuids, false);

        assertEquals(0, results.erroneousAccounts.size());
        assertEquals(1, results.importedAccounts.size());
        assertEquals("Account", results.importedAccounts.get(0).imported.name);
        assertEquals(validUUID, results.importedAccounts.get(0).imported.uuid);
        assertTrue(results.importedAccounts.get(0).incomingPasswordNeeded);
        assertTrue(results.importedAccounts.get(0).outgoingPasswordNeeded);
    }

    @Test
    public void getImportStreamContents_account() throws SettingsImportExportException {
        String validUUID = UUID.randomUUID().toString();
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"1\">" +
                "<accounts>" +
                "<account uuid=\"" + validUUID + "\">" +
                "<name>Account</name>" +
                "<identities>" +
                "<identity>" +
                "<email>user@gmail.com</email>" +
                "</identity>" +
                "</identities>" +
                "</account>" +
                "</accounts></k9settings>");

        SettingsImporter.ImportContents results = SettingsImporter.getImportStreamContents(inputStream);

        assertEquals(false, results.globalSettings);
        assertEquals(1, results.accounts.size());
        assertEquals("Account", results.accounts.get(0).name);
        assertEquals(validUUID, results.accounts.get(0).uuid);
    }

    @Test
    public void getImportStreamContents_alternativeName() throws SettingsImportExportException {
        String validUUID = UUID.randomUUID().toString();
        InputStream inputStream = inputStreamOf("<k9settings format=\"1\" version=\"1\">" +
                "<accounts>" +
                "<account uuid=\"" + validUUID + "\">" +
                "<name></name>" +
                "<identities>" +
                "<identity>" +
                "<email>user@gmail.com</email>" +
                "</identity>" +
                "</identities>" +
                "</account>" +
                "</accounts></k9settings>");

        SettingsImporter.ImportContents results = SettingsImporter.getImportStreamContents(inputStream);

        assertEquals(false, results.globalSettings);
        assertEquals(1, results.accounts.size());
        assertEquals("user@gmail.com", results.accounts.get(0).name);
        assertEquals(validUUID, results.accounts.get(0).uuid);
    }

    private InputStream inputStreamOf(String data) {
        return new Buffer()
                .writeString(data, Charsets.UTF_8)
                .inputStream();
    }
}
+318 −0
Original line number Diff line number Diff line
package com.fsck.k9.preferences

import android.content.Context
import assertk.all
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.containsExactly
import assertk.assertions.extracting
import assertk.assertions.first
import assertk.assertions.hasSize
import assertk.assertions.isEmpty
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
import assertk.assertions.isInstanceOf
import assertk.assertions.isTrue
import assertk.assertions.key
import assertk.assertions.prop
import com.fsck.k9.K9RobolectricTest
import com.fsck.k9.Preferences
import com.fsck.k9.mail.AuthType
import com.fsck.k9.preferences.SettingsImporter.AccountDescription
import com.fsck.k9.preferences.SettingsImporter.AccountDescriptionPair
import com.fsck.k9.preferences.SettingsImporter.ImportContents
import com.fsck.k9.preferences.SettingsImporter.ImportResults
import com.fsck.k9.preferences.SettingsImporter.ImportedAccount
import com.fsck.k9.preferences.SettingsImporter.ImportedIdentity
import com.fsck.k9.preferences.SettingsImporter.ImportedServer
import java.util.UUID
import org.junit.Before
import org.junit.Test
import org.robolectric.RuntimeEnvironment

class SettingsImporterTest : K9RobolectricTest() {
    private val context: Context = RuntimeEnvironment.getApplication()

    @Before
    fun before() {
        deletePreExistingAccounts()
    }

    private fun deletePreExistingAccounts() {
        val preferences = Preferences.getPreferences()
        preferences.clearAccounts()
    }

    @Test
    fun `importSettings() should throw on empty file`() {
        val inputStream = "".byteInputStream()
        val accountUuids = emptyList<String>()

        assertFailure {
            SettingsImporter.importSettings(context, inputStream, true, accountUuids, true)
        }.isInstanceOf<SettingsImportExportException>()
    }

    @Test
    fun `importSettings() should throw on missing format attribute`() {
        val inputStream = """<k9settings version="1"></k9settings>""".byteInputStream()
        val accountUuids = emptyList<String>()

        assertFailure {
            SettingsImporter.importSettings(context, inputStream, true, accountUuids, true)
        }.isInstanceOf<SettingsImportExportException>()
    }

    @Test
    fun `importSettings() should throw on invalid format attribute value`() {
        val inputStream = """<k9settings version="1" format="A"></k9settings>""".byteInputStream()
        val accountUuids = emptyList<String>()

        assertFailure {
            SettingsImporter.importSettings(context, inputStream, true, accountUuids, true)
        }.isInstanceOf<SettingsImportExportException>()
    }

    @Test
    fun `importSettings() should throw on invalid format version`() {
        val inputStream = """<k9settings version="1" format="0"></k9settings>""".byteInputStream()
        val accountUuids = emptyList<String>()

        assertFailure {
            SettingsImporter.importSettings(context, inputStream, true, accountUuids, true)
        }.isInstanceOf<SettingsImportExportException>()
    }

    @Test
    fun `importSettings() should throw on missing version attribute`() {
        val inputStream = """<k9settings format="1"></k9settings>""".byteInputStream()
        val accountUuids = emptyList<String>()

        assertFailure {
            SettingsImporter.importSettings(context, inputStream, true, accountUuids, true)
        }.isInstanceOf<SettingsImportExportException>()
    }

    @Test
    fun `importSettings() should throws on invalid version attribute value`() {
        val inputStream = """<k9settings format="1" version="A"></k9settings>""".byteInputStream()
        val accountUuids = emptyList<String>()

        assertFailure {
            SettingsImporter.importSettings(context, inputStream, true, accountUuids, true)
        }.isInstanceOf<SettingsImportExportException>()
    }

    @Test
    fun `importSettings() should throw on invalid version`() {
        val inputStream = """<k9settings format="1" version="0"></k9settings>""".byteInputStream()
        val accountUuids = emptyList<String>()

        assertFailure {
            SettingsImporter.importSettings(context, inputStream, true, accountUuids, true)
        }.isInstanceOf<SettingsImportExportException>()
    }

    @Test
    fun `parseSettings() should return accounts`() {
        val accountUuid = UUID.randomUUID().toString()
        val inputStream =
            """
            <k9settings format="1" version="1">
              <accounts>
                <account uuid="$accountUuid">
                  <name>Account</name>
                </account>
              </accounts>
            </k9settings>
            """.trimIndent().byteInputStream()
        val accountUuids = listOf("1")

        val results = SettingsImporter.parseSettings(inputStream, true, accountUuids, true)

        assertThat(results.accounts).all {
            hasSize(1)
            key(accountUuid).all {
                prop(ImportedAccount::uuid).isEqualTo(accountUuid)
                prop(ImportedAccount::name).isEqualTo("Account")
            }
        }
    }

    @Test
    fun `parseSettings() should return identities`() {
        val accountUuid = UUID.randomUUID().toString()
        val inputStream =
            """
            <k9settings format="1" version="1">
              <accounts>
                <account uuid="$accountUuid">
                  <name>Account</name>
                  <identities>
                    <identity>
                      <email>user@gmail.com</email>
                    </identity>
                  </identities>
                </account>
              </accounts>
            </k9settings>
            """.trimIndent().byteInputStream()
        val accountUuids = listOf("1")

        val results = SettingsImporter.parseSettings(inputStream, true, accountUuids, true)

        assertThat(results.accounts).all {
            hasSize(1)
            key(accountUuid).all {
                prop(ImportedAccount::uuid).isEqualTo(accountUuid)
                prop(ImportedAccount::identities).extracting(ImportedIdentity::email).containsExactly("user@gmail.com")
            }
        }
    }

    @Test
    fun `parseSettings() should parse incoming server authentication type`() {
        val accountUuid = UUID.randomUUID().toString()
        val inputStream =
            """
            <k9settings format="1" version="1">
              <accounts>
                <account uuid="$accountUuid">
                  <name>Account</name>
                  <incoming-server>
                    <authentication-type>CRAM_MD5</authentication-type>
                  </incoming-server>
                </account>
              </accounts>
            </k9settings>
            """.trimIndent().byteInputStream()
        val accountUuids = listOf(accountUuid)

        val results = SettingsImporter.parseSettings(inputStream, true, accountUuids, false)

        assertThat(results.accounts)
            .key(accountUuid)
            .prop(ImportedAccount::incoming)
            .prop(ImportedServer::authenticationType)
            .isEqualTo(AuthType.CRAM_MD5)
    }

    @Test
    fun `importSettings() should disable accounts needing passwords`() {
        val accountUuid = UUID.randomUUID().toString()
        val inputStream =
            """
            <k9settings format="1" version="1">
              <accounts>
                <account uuid="$accountUuid">
                  <name>Account</name>
                  <incoming-server type="IMAP">
                    <connection-security>SSL_TLS_REQUIRED</connection-security>
                    <username>user@gmail.com</username>
                    <authentication-type>CRAM_MD5</authentication-type>
                    <host>googlemail.com</host>
                  </incoming-server>
                  <outgoing-server type="SMTP">
                    <connection-security>SSL_TLS_REQUIRED</connection-security>
                    <username>user@googlemail.com</username>
                    <authentication-type>CRAM_MD5</authentication-type>
                    <host>googlemail.com</host>
                  </outgoing-server>
                  <settings>
                    <value key="a">b</value>
                  </settings>
                  <identities>
                    <identity>
                      <email>user@gmail.com</email>
                    </identity>
                  </identities>
                </account>
              </accounts>
            </k9settings>
            """.trimIndent().byteInputStream()
        val accountUuids = listOf(accountUuid)

        val results = SettingsImporter.importSettings(context, inputStream, true, accountUuids, false)

        assertThat(results).all {
            prop(ImportResults::erroneousAccounts).isEmpty()
            prop(ImportResults::importedAccounts).all {
                hasSize(1)
                first().all {
                    prop(AccountDescriptionPair::imported).all {
                        prop(AccountDescription::uuid).isEqualTo(accountUuid)
                        prop(AccountDescription::name).isEqualTo("Account")
                    }
                    prop(AccountDescriptionPair::incomingPasswordNeeded).isTrue()
                    prop(AccountDescriptionPair::outgoingPasswordNeeded).isTrue()
                }
            }
        }
    }

    @Test
    fun `getImportStreamContents() should return list of accounts`() {
        val accountUuid = UUID.randomUUID().toString()
        val inputStream =
            """
            <k9settings format="1" version="1">
              <accounts>
                <account uuid="$accountUuid">
                  <name>Account</name>
                  <identities>
                    <identity>
                      <email>user@gmail.com</email>
                    </identity>
                  </identities>
                </account>
              </accounts>
            </k9settings>
            """.trimIndent().byteInputStream()

        val results = SettingsImporter.getImportStreamContents(inputStream)

        assertThat(results).all {
            prop(ImportContents::globalSettings).isFalse()
            prop(ImportContents::accounts).all {
                hasSize(1)
                first().all {
                    prop(AccountDescription::uuid).isEqualTo(accountUuid)
                    prop(AccountDescription::name).isEqualTo("Account")
                }
            }
        }
    }

    @Test
    fun `getImportStreamContents() should return email address as account name when no account name provided`() {
        val accountUuid = UUID.randomUUID().toString()
        val inputStream =
            """
            <k9settings format="1" version="1">
              <accounts>
                <account uuid="$accountUuid">
                  <name></name>
                  <identities>
                    <identity>
                      <email>user@gmail.com</email>
                    </identity>
                  </identities>
                </account>
              </accounts>
            </k9settings>
            """.trimIndent().byteInputStream()

        val results = SettingsImporter.getImportStreamContents(inputStream)

        assertThat(results).all {
            prop(ImportContents::globalSettings).isFalse()
            prop(ImportContents::accounts).all {
                hasSize(1)
                first().all {
                    prop(AccountDescription::uuid).isEqualTo(accountUuid)
                    prop(AccountDescription::name).isEqualTo("user@gmail.com")
                }
            }
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -66,7 +66,7 @@ android {
        testApplicationId = "foundation.e.mail.tests"

        versionCode = 37011
        versionName = "6.711"
        versionName = "6.712-SNAPSHOT"

        // Keep in sync with the resource string array "supported_languages"
        resourceConfigurations.addAll(
+15 −1
Original line number Diff line number Diff line
@@ -11,6 +11,12 @@
   k9mail.keyPassword=<password 'k9mail@tb-android' in 1Password>
   ```

## One-time setup for F-Droid builds

1. Install _fdroidserver_ by following the [installation instructions](https://f-droid.org/docs/Installing_the_Server_and_Repo_Tools).
2. [Sign up for a Gitlab account](https://gitlab.com/users/sign_up) and fork the [fdroiddata](https://gitlab.com/fdroid/fdroiddata) repository.
3. Clone your fork of the _fdroiddata_ repository.

## Release a beta version

1. Update versionCode and versionName in `app/k9mail/build.gradle`
@@ -49,7 +55,15 @@

### Create release on F-Droid

TODO
1. Fetch the latest changes from the _fdroiddata_ repository.
2. Switch to a new branch in your copy of the _fdroiddata_ repository.
3. Edit `metadata/com.fsck.k9.yml` to create a new entry for the version you want to release. Usually it's copy & paste of the previous entry and adjusting `versionName`, `versionCode`, and `commit` (use the tag name). Leave `CurrentVersion` and `CurrentVersionCode` unchanged. Those specify which version is the stable/recommended build.
4. Commit the changes. Message: "Update K-9 Mail to $newVersionName"
5. Run `fdroid build --latest com.fsck.k9` to build the project using F-Droid's toolchain.
6. Push the changes to your fork of the _fdroiddata_ repository.
7. Open a merge request on Gitlab. (The message from the server after the push in the previous step should contain a URL)
8. Select the _App update_ template and fill it out.
9. Create merge request and the F-Droid team will do the rest.

### Create release on Google Play

+29 −0
Original line number Diff line number Diff line
# [NNNN] - [Descriptive Title in Title Case]

## Status

<!-- [Status from the options: proposed, accepted, rejected, deprecated, superseded] -->

- **Status**

## Context

<!-- [Description of the context and problem statement that the decision is addressing. It should contain any relevant factors that influenced the decision.] -->

## Decision

<!-- [Description of the decision that was made. Detail the change that will be implemented.] -->

## Consequences

<!-- [Explanation of the consequences of the decision. This includes both the positive and negative effects, and any potential risks.] -->

- **Positive Consequences**

  - consequence 1
  - consequence 2

- **Negative Consequences**

  - consequence 1
  - consequence 2
Loading