~aleteoryx/muditaos

14d6d53ac84de3648c07e57fd9bf834f6461bcdd — rrandomsky 3 years ago 61145cf
[MOS-737] Fix for adding a country code prefix to existing contact

Adding or removing code country from contact's number creates a
new number record. Old number, which was connected witch contact
previously, is removed from the numbers table to avoid miss match
numbers. Changing country code only creates new number records,
without removing old ones. Old ones are temporary number from now.
M module-db/Interface/ContactRecord.cpp => module-db/Interface/ContactRecord.cpp +36 -10
@@ 124,9 124,13 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
    }

    auto oldNumberIDs  = splitNumberIDs(contact.numbersID);
    auto newNumbersIDs = getNumbersIDs(contact.ID, rec);
    auto newNumbersIDs = getNumbersIDs(contact.ID, rec, utils::PhoneNumber::Match::EXACT);
    for (auto oldNumberID : oldNumberIDs) {
        if (std::find(std::begin(newNumbersIDs), std::end(newNumbersIDs), oldNumberID) == std::end(newNumbersIDs)) {
            // If oldNumberID is NOT in newNumbersIDs vector then:
            //  -> remove oldNumberID from DB - if the old number is the same as one of the new ones
            //     but with different country number (to prevent a mismatch of same numbers)
            //  -> make temporary contact with this oldNumberID - otherwise
            auto numberRecord = contactDB->number.getById(oldNumberID);
            if (!numberRecord.isValid()) {
                return false;


@@ 141,13 145,34 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
                          e.what());
                return false;
            }
            const auto tmpContactRecord = addTemporaryContactForNumber(number);
            if (!tmpContactRecord.has_value()) {
                return false;

            bool isOldNumberToRemove = false;
            utils::PhoneNumber oldPhoneNumber(numberRecord.numberUser, numberRecord.numbere164);
            for (const auto newNumberID : newNumbersIDs) {
                auto newNumberRecord = contactDB->number.getById(newNumberID);
                utils::PhoneNumber newPhoneNumber(newNumberRecord.numberUser, newNumberRecord.numbere164);
                if (newPhoneNumber.match(oldPhoneNumber) >= utils::PhoneNumber::Match::POSSIBLE &&
                    oldPhoneNumber.getCountryCode() != newPhoneNumber.getCountryCode()) {
                    isOldNumberToRemove = true;
                }
            }
            numberRecord.contactID = tmpContactRecord.value().ID;
            if (!contactDB->number.update(numberRecord)) {
                return false;

            if (isOldNumberToRemove) {
                // remove old number from DB table
                if (!contactDB->number.removeById(oldNumberID)) {
                    return false;
                }
            }
            else {
                // make temporary contact with old number
                const auto tmpContactRecord = addTemporaryContactForNumber(number);
                if (!tmpContactRecord.has_value()) {
                    return false;
                }
                numberRecord.contactID = tmpContactRecord.value().ID;
                if (!contactDB->number.update(numberRecord)) {
                    return false;
                }
            }
        }
    }


@@ 195,8 220,9 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
    return true;
}

auto ContactRecordInterface::getNumbersIDs(std::uint32_t contactID, const ContactRecord &contact)
    -> std::vector<std::uint32_t>
auto ContactRecordInterface::getNumbersIDs(std::uint32_t contactID,
                                           const ContactRecord &contact,
                                           utils::PhoneNumber::Match matchLevel) -> std::vector<std::uint32_t>
{
    std::vector<std::uint32_t> result;



@@ 213,7 239,7 @@ auto ContactRecordInterface::getNumbersIDs(std::uint32_t contactID, const Contac
            return {};
        }

        auto numberMatch = numberMatcher.bestMatch(phoneNumber, utils::PhoneNumber::Match::POSSIBLE);
        auto numberMatch = numberMatcher.bestMatch(phoneNumber, matchLevel);
        if (!numberMatch.has_value()) {
            // number does not exist in the DB yet. Let's add it.
            if (!contactDB->number.add(ContactsNumberTableRow{Record(DB_ID_NONE),

M module-db/Interface/ContactRecord.hpp => module-db/Interface/ContactRecord.hpp +4 -1
@@ 289,7 289,10 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco
        -> const std::unique_ptr<db::QueryResult>;

    auto addTemporaryContactForNumber(const ContactRecord::Number &number) -> std::optional<ContactRecord>;
    auto getNumbersIDs(std::uint32_t contactID, const ContactRecord &contact) -> std::vector<std::uint32_t>;
    auto getNumbersIDs(std::uint32_t contactID,
                       const ContactRecord &contact,
                       utils::PhoneNumber::Match matchLevel = utils::PhoneNumber::Match::POSSIBLE)
        -> std::vector<std::uint32_t>;
    auto addNumbers(std::uint32_t contactID, const std::vector<ContactRecord::Number> &numbers)
        -> std::optional<std::string>;
    auto addOrUpdateName(std::uint32_t contactID, std::uint32_t nameID, const ContactRecord &contact)

M module-db/tests/ContactsRecord_tests.cpp => module-db/tests/ContactsRecord_tests.cpp +255 -1
@@ 264,7 264,12 @@ TEST_CASE("Contact record numbers update")
    auto records = ContactRecordInterface(&contactDB);

    ContactRecord testRecord, otherRecord;
    std::array<std::string, 4> numbers = {{{"600100100"}, {"600100200"}, {"600100300"}, {"600100400"}}};
    std::array<std::string, 4> numbers   = {{{"600100100"}, {"600100200"}, {"600100300"}, {"600100400"}}};
    std::array<std::string, 4> numbersPL = {
        {{"+48600100100"}, {"+48600100200"}, {"+48600100300"}, {"+48600100400"}}}; // Poland country code
    std::array<std::string, 4> numbersFR = {
        {{"+33600100100"}, {"+33600100200"}, {"+33600100300"}, {"+33600100400"}}}; // France country code
    std::array<std::string, 2> unUsedNumbers = {{{"612130140"}, {"612130141"}}};

    testRecord.primaryName     = "number";
    testRecord.alternativeName = "test";


@@ 369,6 374,223 @@ TEST_CASE("Contact record numbers update")
        REQUIRE(validatationRecord.numbers[0].number.getEntered() == numbers[3]);
    }

    SECTION("Single number update - new unused number")
    {
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);

        newRecord.numbers =
            std::vector<ContactRecord::Number>({ContactRecord::Number(unUsedNumbers[0], std::string("")),
                                                ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.contacts.count() == 2); // amount of non-temporary contacts
        REQUIRE(contactDB.number.count() == 5);
        REQUIRE(contactDB.number.getById(1).contactID != 1); // old numbers do not belong to any contact
        REQUIRE(contactDB.number.getById(1).contactID != 2);

        auto validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == unUsedNumbers[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[1]);

        validationRecord = records.GetByID(2);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[2]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[3]);

        // A temporary contact for number[0] (which was previously assigned to recordID=1) has been created.
        validationRecord = records.GetByIdWithTemporary(3);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[0]);
    }

    SECTION("Single number update twice - new unused number")
    {
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);

        newRecord.numbers =
            std::vector<ContactRecord::Number>({ContactRecord::Number(unUsedNumbers[0], std::string("")),
                                                ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));

        newRecord.numbers =
            std::vector<ContactRecord::Number>({ContactRecord::Number(unUsedNumbers[1], std::string("")),
                                                ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.contacts.count() == 2); // amount of non-temporary contacts
        REQUIRE(contactDB.number.count() == 6);
        REQUIRE(contactDB.number.getById(1).contactID != 1); // old numbers do not belong to any contact
        REQUIRE(contactDB.number.getById(1).contactID != 2);
        REQUIRE(contactDB.number.getById(5).contactID != 1);
        REQUIRE(contactDB.number.getById(5).contactID != 2);

        auto validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == unUsedNumbers[1]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[1]);

        validationRecord = records.GetByID(2);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[2]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[3]);

        // A temporary contact for number ID 1 (which was previously assigned to recordID=1) has been created.
        validationRecord = records.GetByIdWithTemporary(3);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[0]);

        // A temporary contact for numberID 5 (which was previously assigned to recordID=1 for a moment) has been
        // created.
        validationRecord = records.GetByIdWithTemporary(4);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == unUsedNumbers[0]);
    }

    SECTION("Both numbers update - new newer used number")
    {
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);

        newRecord.numbers =
            std::vector<ContactRecord::Number>({ContactRecord::Number(unUsedNumbers[0], std::string("")),
                                                ContactRecord::Number(unUsedNumbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.contacts.count() == 2); // amount of non-temporary contacts
        REQUIRE(contactDB.number.count() == 6);
        REQUIRE(contactDB.number.getById(1).contactID != 1); // old numbers do not belong to any contact
        REQUIRE(contactDB.number.getById(1).contactID != 2);
        REQUIRE(contactDB.number.getById(2).contactID != 1);
        REQUIRE(contactDB.number.getById(2).contactID != 2);

        auto validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == unUsedNumbers[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == unUsedNumbers[1]);

        validationRecord = records.GetByID(2);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[2]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[3]);

        // A temporary contact for number ID 1 (which was previously assigned to recordID=1) has been created.
        validationRecord = records.GetByIdWithTemporary(3);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[0]);

        // A temporary contact for numberID 2 (which was previously assigned to recordID=1) has been created.
        validationRecord = records.GetByIdWithTemporary(4);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[1]);
    }

    SECTION("Single number update - add country code")
    {
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);

        newRecord.numbers = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(numbersPL[0], std::string("")), ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);

        auto validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbersPL[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[1]);

        validationRecord = records.GetByID(2);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[2]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[3]);

        // there is no new temporary number
        validationRecord = records.GetByIdWithTemporary(3);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Single number update - change country code")
    {
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);

        newRecord.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(numbersPL[0], std::string("")),
                                                                ContactRecord::Number(numbersPL[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);

        auto validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbersPL[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbersPL[1]);
        REQUIRE(contactDB.contacts.count() == 2); // amount of non-temporary contacts

        newRecord.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(numbersFR[0], std::string("")),
                                                                ContactRecord::Number(numbersPL[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 5);

        validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbersFR[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbersPL[1]);

        validationRecord = records.GetByID(2);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[2]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[3]);

        // There is new temporary contact for number[0] (which was previously assigned to recordID=1) with
        // old country code
        validationRecord = records.GetByIdWithTemporary(3);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbersPL[0]);

        // There is new temporary contact for number[1] (which was previously assigned to recordID=1) with
        // old country code
        validationRecord = records.GetByIdWithTemporary(4);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Both number update - remove country code")
    {
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);

        newRecord.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(numbersPL[0], std::string("")),
                                                                ContactRecord::Number(numbersFR[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);

        auto validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbersPL[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbersFR[1]);
        REQUIRE(contactDB.contacts.count() == 2); // amount of non-temporary contacts

        newRecord.numbers = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(numbers[0], std::string("")), ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);

        validationRecord = records.GetByID(1);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[1]);

        validationRecord = records.GetByID(2);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[2]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[3]);

        // There is no new temporary number
        validationRecord = records.GetByIdWithTemporary(3);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    SECTION("Change both numbers")
    {
        auto newRecord = records.GetByID(1);


@@ 400,6 622,38 @@ TEST_CASE("Contact record numbers update")
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[1]);
    }

    SECTION("Change both numbers - same number with different country code")
    {
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);

        newRecord.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(numbersPL[0], std::string("")),
                                                                ContactRecord::Number(numbersFR[0], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 5);

        auto validationRecord = records.GetByIdWithTemporary(1);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbersPL[0]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbersFR[0]);
        REQUIRE(contactDB.contacts.count() == 2); // amount of non-temporary contacts

        validationRecord = records.GetByID(2);
        REQUIRE(validationRecord.numbers.size() == 2);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[2]);
        REQUIRE(validationRecord.numbers[1].number.getEntered() == numbers[3]);

        // A temporary contact for number[1] (which was previously assigned to recordID=1) has been created.
        validationRecord = records.GetByIdWithTemporary(3);
        REQUIRE(validationRecord.ID != DB_ID_NONE);
        REQUIRE(validationRecord.numbers[0].number.getEntered() == numbers[1]);

        // There is no new temporary number
        validationRecord = records.GetByIdWithTemporary(4);
        REQUIRE(validationRecord.ID == DB_ID_NONE);
        REQUIRE(validationRecord.numbers.empty());
    }

    Database::deinitialize();
}


M pure_changelog.md => pure_changelog.md +1 -0
@@ 41,6 41,7 @@
* Fixed windows flow regarding PUK requests after too many PIN mistakes
* Fixed disappearing "confirm" button in PIN entering screen
* Fixed looping on the SIM card selection screen
* Fixed adding country code prefix to existing contact

### Added
* Added a popup for changing the SIM card