~aleteoryx/muditaos

e2f59a13b23ea6e1b41ca125be03b21773b7de4b — rrandomsky 2 years ago b53dec1
[MOS-1007] Fix for add/update contact with 2 same phone numbers

There was possibility to add contact, or edit some contact, with have
2 same phone numbers (exactly the same or witch/without country code).
Now this has been fixed and is not possible to create, even by editing, contact
with 2 exactly the same numbers or 2 same numbers and one of them with a country code
M module-db/Interface/ContactRecord.cpp => module-db/Interface/ContactRecord.cpp +40 -0
@@ 35,6 35,15 @@ ContactRecordInterface::ContactRecordInterface(ContactsDB *db)

auto ContactRecordInterface::Add(ContactRecord &rec) -> bool
{
    if (rec.numbers.size() > 2) {
        LOG_WARN("Contact has more than 2 numbers");
    }

    if (hasContactRecordSameNumbers(rec)) {
        LOG_ERROR("New record can not have 2 same numbers");
        return false;
    }

    bool result = contactDB->contacts.add(ContactsTableRow{Record(DB_ID_NONE), .speedDial = rec.speeddial});
    if (!result) {
        return false;


@@ 125,6 134,10 @@ auto ContactRecordInterface::RemoveByID(uint32_t id) -> bool

auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
{
    if (rec.numbers.size() > 2) {
        LOG_WARN("Contact has more than 2 numbers");
    }

    ContactsTableRow contact = contactDB->contacts.getByIdWithTemporary(rec.ID);
    if (!contact.isValid()) {
        return false;


@@ 132,6 145,12 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool

    auto oldNumberIDs = splitNumberIDs(contact.numbersID);
    auto newNumbers   = rec.numbers;

    if (hasContactRecordSameNumbers(rec)) {
        LOG_ERROR("Updated record can not have 2 same numbers");
        return false;
    }

    if (!changeNumberRecordInPlaceIfCountryCodeIsOnlyDifferent(oldNumberIDs, newNumbers)) {
        return false;
    }


@@ 1595,3 1614,24 @@ auto ContactRecordInterface::changeNumberRecordInPlaceIfCountryCodeIsOnlyDiffere
    }
    return true;
}
auto ContactRecordInterface::hasContactRecordSameNumbers(const ContactRecord &rec) -> bool
{
    if (rec.numbers.size() >= 2) {
        if (rec.numbers.size() > 2) {
            LOG_WARN("Contact record has more than 2 numbers. Checking similarity for first 2 numbers only");
        }
        utils::PhoneNumber firstPhoneNumber(rec.numbers.at(0).number);
        utils::PhoneNumber secondPhoneNumber(rec.numbers.at(1).number);

        utils::PhoneNumber::Match matchLevel = firstPhoneNumber.match(secondPhoneNumber);

        switch (matchLevel) {
        case utils::PhoneNumber::Match::EXACT:
        case utils::PhoneNumber::Match::POSSIBLE:
            return true;
        default:
            return false;
        }
    }
    return false;
}

M module-db/Interface/ContactRecord.hpp => module-db/Interface/ContactRecord.hpp +2 -0
@@ 319,4 319,6 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco
     */
    auto changeNumberRecordInPlaceIfCountryCodeIsOnlyDifferent(const std::vector<std::uint32_t> &oldNumberIDs,
                                                               std::vector<ContactRecord::Number> &newNumbers) -> bool;

    auto hasContactRecordSameNumbers(const ContactRecord &rec) -> bool;
};

M module-db/tests/ContactsRecord_tests.cpp => module-db/tests/ContactsRecord_tests.cpp +250 -1
@@ 767,7 767,6 @@ TEST_CASE("Check replacement of number in place in db when only different is hav

    // Preparation of DB initial state
    auto records = ContactRecordInterface(&contactsDb.get());
    ContactRecord testContactRecord;

    std::array<std::string, 2> numbers   = {{{"500600100"}, {"500600200"}}};
    std::array<std::string, 2> numbersPL = {{{"+48500600100"}, {"+48500600200"}}}; // Poland country code


@@ 924,3 923,253 @@ TEST_CASE("Check replacement of number in place in db when only different is hav
        REQUIRE(contactsDb.get().number.getById(SECOND_NEW_CONTACT_ID).numberUser == numbersFR[0]);
    }
}

TEST_CASE("Check if contact can have 2 similar number (exactly the same or just with/with no country code))")
{
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    // Preparation of DB initial state
    auto records = ContactRecordInterface(&contactsDb.get());

    std::string number                 = "500600100";
    std::string numberPL               = "+48500600100"; // Poland country code
    constexpr int32_t FIRST_CONTACT_ID = 1;

    SECTION("Add a new contact with 2 exactly the same number (without country code)")
    {
        // Adding of normal contact with 2 exactly the same numbers
        ContactRecord noTempContactRecordExactlySameNumbers;
        noTempContactRecordExactlySameNumbers.primaryName     = "PrimaryNameNoTemporary";
        noTempContactRecordExactlySameNumbers.alternativeName = "AlternativeNameNoTemporary";
        noTempContactRecordExactlySameNumbers.numbers         = std::vector<ContactRecord::Number>({
            ContactRecord::Number(number, std::string(""), ContactNumberType::HOME),
            ContactRecord::Number(number, std::string(""), ContactNumberType::HOME),
        });

        REQUIRE(records.Add(noTempContactRecordExactlySameNumbers) ==
                false);                                // Try to add contact witch the same numbers
        REQUIRE(contactsDb.get().number.count() == 0); // There should be no contact

        // No new contact
        auto validationRecord = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());

        // No new temporary contact
        validationRecord = records.GetByIdWithTemporary(FIRST_CONTACT_ID);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Add a new contact with 2 exactly the same number (with country code)")
    {
        // Adding of normal contact with 2 exactly the same numbers
        ContactRecord noTempContactRecordExactlySameNumbers;
        noTempContactRecordExactlySameNumbers.primaryName     = "PrimaryNameNoTemporary";
        noTempContactRecordExactlySameNumbers.alternativeName = "AlternativeNameNoTemporary";
        noTempContactRecordExactlySameNumbers.numbers         = std::vector<ContactRecord::Number>({
            ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME),
            ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME),
        });

        REQUIRE(records.Add(noTempContactRecordExactlySameNumbers) ==
                false);                                // Try to add contact witch the same numbers
        REQUIRE(contactsDb.get().number.count() == 0); // There should be no contact

        // No new contact
        auto validationRecord = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());

        // No new temporary contact
        validationRecord = records.GetByIdWithTemporary(FIRST_CONTACT_ID);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Add a new contact with 2 same numbers but one with country code and one without")
    {
        // Adding of normal contact with 2 same numbers
        ContactRecord noTempContactRecordExactlySameNumbers;
        noTempContactRecordExactlySameNumbers.primaryName     = "PrimaryNameNoTemporary";
        noTempContactRecordExactlySameNumbers.alternativeName = "AlternativeNameNoTemporary";
        noTempContactRecordExactlySameNumbers.numbers         = std::vector<ContactRecord::Number>({
            ContactRecord::Number(number, std::string(""), ContactNumberType::HOME),
            ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME),
        });

        REQUIRE(records.Add(noTempContactRecordExactlySameNumbers) ==
                false);                                // Try to add contact witch the same numbers
        REQUIRE(contactsDb.get().number.count() == 0); // There should be no contact

        // No new contact
        auto validationRecord = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());

        // No new temporary contact
        validationRecord = records.GetByIdWithTemporary(FIRST_CONTACT_ID);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Update contact to contact with 2 exactly same numbers (without country code)")
    {
        // Adding of normal contact with
        ContactRecord noTempContactRecordExactlySameNumbers;
        noTempContactRecordExactlySameNumbers.primaryName     = "PrimaryNameNoTemporary";
        noTempContactRecordExactlySameNumbers.alternativeName = "AlternativeNameNoTemporary";
        noTempContactRecordExactlySameNumbers.numbers         = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(number, std::string(""), ContactNumberType::HOME)});

        // Add normal valid contact and check it
        REQUIRE(records.Add(noTempContactRecordExactlySameNumbers) == true);
        REQUIRE(contactsDb.get().number.count() == 1); // There should be no contact
        auto normalContactRecord = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(normalContactRecord.numbers.size() == 1);
        REQUIRE(normalContactRecord.numbers[0].number.getEntered() == number); // without country code

        // Update contact to contact with the same numers
        auto recordToUpdate    = records.GetByID(FIRST_CONTACT_ID);
        recordToUpdate.numbers = std::vector<ContactRecord::Number>({
            ContactRecord::Number(number, std::string(""), ContactNumberType::HOME),
            ContactRecord::Number(number, std::string(""), ContactNumberType::HOME),
        });
        REQUIRE(records.Update(recordToUpdate) == false);

        // Check contact record after Update
        auto recordAfterUpdate = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(recordAfterUpdate.numbers.size() == 1);
        REQUIRE(recordAfterUpdate.numbers[0].number.getEntered() == number); // without country code

        // No new contact
        auto validationRecord = records.GetByID(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());

        // No new temporary contact
        validationRecord = records.GetByIdWithTemporary(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Update contact to contact with 2 exactly same numbers (with country code)")
    {
        // Adding of normal contact
        ContactRecord noTempContactRecordExactlySameNumbers;
        noTempContactRecordExactlySameNumbers.primaryName     = "PrimaryNameNoTemporary";
        noTempContactRecordExactlySameNumbers.alternativeName = "AlternativeNameNoTemporary";
        noTempContactRecordExactlySameNumbers.numbers         = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME)});

        // Add normal valid contact and check it
        REQUIRE(records.Add(noTempContactRecordExactlySameNumbers) == true);
        REQUIRE(contactsDb.get().number.count() == 1); // There should be no contact
        auto normalContactRecord = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(normalContactRecord.numbers.size() == 1);
        REQUIRE(normalContactRecord.numbers[0].number.getEntered() == numberPL); // without country code

        // Update contact to contact with the same numbers
        auto recordToUpdate    = records.GetByID(FIRST_CONTACT_ID);
        recordToUpdate.numbers = std::vector<ContactRecord::Number>({
            ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME),
            ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME),
        });
        REQUIRE(records.Update(recordToUpdate) == false);

        // Check contact record after Update
        auto recordAfterUpdate = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(recordAfterUpdate.numbers.size() == 1);
        REQUIRE(recordAfterUpdate.numbers[0].number.getEntered() == numberPL); // without country code

        // No new contact
        auto validationRecord = records.GetByID(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());

        // No new temporary contact
        validationRecord = records.GetByIdWithTemporary(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Update contact to contact with 2 same numbers but one without country code and new with country code")
    {
        // Adding of normal contact
        ContactRecord noTempContactRecordExactlySameNumbers;
        noTempContactRecordExactlySameNumbers.primaryName     = "PrimaryNameNoTemporary";
        noTempContactRecordExactlySameNumbers.alternativeName = "AlternativeNameNoTemporary";
        noTempContactRecordExactlySameNumbers.numbers         = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(number, std::string(""), ContactNumberType::HOME)});

        // Add normal valid contact and check it
        REQUIRE(records.Add(noTempContactRecordExactlySameNumbers) == true);
        REQUIRE(contactsDb.get().number.count() == 1); // There should be no contact
        auto normalContactRecord = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(normalContactRecord.numbers.size() == 1);
        REQUIRE(normalContactRecord.numbers[0].number.getEntered() == number); // without country code

        // Update contact to contact with the same numbers
        auto recordToUpdate    = records.GetByID(FIRST_CONTACT_ID);
        recordToUpdate.numbers = std::vector<ContactRecord::Number>({
            ContactRecord::Number(number, std::string(""), ContactNumberType::HOME),
            ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME),
        });
        REQUIRE(records.Update(recordToUpdate) == false);

        // Check contact record after Update
        auto recordAfterUpdate = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(recordAfterUpdate.numbers.size() == 1);
        REQUIRE(recordAfterUpdate.numbers[0].number.getEntered() == number); // without country code

        // No new contact
        auto validationRecord = records.GetByID(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());

        // No new temporary contact
        validationRecord = records.GetByIdWithTemporary(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Update contact to contact with 2 same numbers but one with country code and new without")
    {
        // Adding of normal contact
        ContactRecord noTempContactRecordExactlySameNumbers;
        noTempContactRecordExactlySameNumbers.primaryName     = "PrimaryNameNoTemporary";
        noTempContactRecordExactlySameNumbers.alternativeName = "AlternativeNameNoTemporary";
        noTempContactRecordExactlySameNumbers.numbers         = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME)});

        // Add normal valid contact and check it
        REQUIRE(records.Add(noTempContactRecordExactlySameNumbers) == true);
        REQUIRE(contactsDb.get().number.count() == 1); // There should be no contact
        auto normalContactRecord = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(normalContactRecord.numbers.size() == 1);
        REQUIRE(normalContactRecord.numbers[0].number.getEntered() == numberPL); // without country code

        // Update contact to contact with the same numbers
        auto recordToUpdate    = records.GetByID(FIRST_CONTACT_ID);
        recordToUpdate.numbers = std::vector<ContactRecord::Number>({
            ContactRecord::Number(numberPL, std::string(""), ContactNumberType::HOME),
            ContactRecord::Number(number, std::string(""), ContactNumberType::HOME),
        });
        REQUIRE(records.Update(recordToUpdate) == false);

        // Check contact record after Update
        auto recordAfterUpdate = records.GetByID(FIRST_CONTACT_ID);
        REQUIRE(recordAfterUpdate.numbers.size() == 1);
        REQUIRE(recordAfterUpdate.numbers[0].number.getEntered() == numberPL); // without country code

        // No new contact
        auto validationRecord = records.GetByID(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());

        // No new temporary contact
        validationRecord = records.GetByIdWithTemporary(FIRST_CONTACT_ID + 1);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }
}

M pure_changelog.md => pure_changelog.md +1 -0
@@ 22,6 22,7 @@
* Fixed missing contact entries when scrolling through the contact list
* Fixed misunderstanding holes in sms conversations
* Fixed occasional crash when unplugging Pure from PC when connected with Mudita Center
* Fixed ability to create contact with 2 same numbers

## [1.7.2 2023-07-28]