Implement cross-database reference resolution and add test

Co-authored-by: droidmonkey <2809491+droidmonkey@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-06-19 14:08:44 +00:00
committed by Jonathan White
parent 44366feda7
commit b60b2420c9
4 changed files with 84 additions and 0 deletions

View File

@@ -1369,6 +1369,9 @@ void Entry::setGroup(Group* group, bool trackPrevious)
setPreviousParentGroup(nullptr);
m_group->database()->addDeletedObject(m_uuid);
// Resolve references before moving to a different database
resolveReferencesBeforeDatabaseMove();
// copy custom icon to the new database
if (!iconUuid().isNull() && group->database() && m_group->database()->metadata()->hasCustomIcon(iconUuid())
&& !group->database()->metadata()->hasCustomIcon(iconUuid())) {
@@ -1411,6 +1414,32 @@ Database* Entry::database()
return nullptr;
}
void Entry::resolveReferencesBeforeDatabaseMove()
{
if (!m_group || !m_group->database()) {
return;
}
// Resolve references in all default attributes
for (const QString& key : EntryAttributes::DefaultAttributes) {
if (m_attributes->contains(key) && m_attributes->isReference(key)) {
QString resolvedValue = resolveMultiplePlaceholdersRecursive(m_attributes->value(key), 10);
bool isProtected = m_attributes->isProtected(key);
m_attributes->set(key, resolvedValue, isProtected);
}
}
// Resolve references in custom attributes
const QList<QString> customKeys = m_attributes->customKeys();
for (const QString& key : customKeys) {
if (m_attributes->isReference(key)) {
QString resolvedValue = resolveMultiplePlaceholdersRecursive(m_attributes->value(key), 10);
bool isProtected = m_attributes->isProtected(key);
m_attributes->set(key, resolvedValue, isProtected);
}
}
}
QString Entry::maskPasswordPlaceholders(const QString& str) const
{
return QString{str}.replace(QStringLiteral("{PASSWORD}"), QStringLiteral("******"), Qt::CaseInsensitive);

View File

@@ -273,6 +273,8 @@ public:
bool canUpdateTimeinfo() const;
void setUpdateTimeinfo(bool value);
void resolveReferencesBeforeDatabaseMove();
signals:
/**
* Emitted when a default attribute has been changed.

View File

@@ -20,6 +20,9 @@
#include "TestEntry.h"
#include "core/Clock.h"
#include "core/Database.h"
#include "core/Entry.h"
#include "core/EntryAttributes.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/TimeInfo.h"
@@ -672,6 +675,55 @@ void TestEntry::testResolveClonedEntry()
QCOMPARE(cclone4->resolveMultiplePlaceholders(cclone4->password()), original->password());
}
void TestEntry::testCrossDatabaseReferences()
{
// Test that references are resolved when moving entries between databases
Database db1;
auto* root1 = db1.rootGroup();
Database db2;
auto* root2 = db2.rootGroup();
// Create original entry in database 1
auto* originalEntry = new Entry();
originalEntry->setGroup(root1);
originalEntry->setUuid(QUuid::createUuid());
originalEntry->setTitle("OriginalTitle");
originalEntry->setUsername("OriginalUsername");
originalEntry->setPassword("OriginalPassword");
originalEntry->setUrl("http://original.com");
// Create entry with references to original entry in database 1
auto* refEntry = new Entry();
refEntry->setGroup(root1);
refEntry->setUuid(QUuid::createUuid());
refEntry->setTitle(QString("{REF:T@I:%1}").arg(originalEntry->uuidToHex()));
refEntry->setUsername(QString("{REF:U@I:%1}").arg(originalEntry->uuidToHex()));
refEntry->setPassword(QString("{REF:P@I:%1}").arg(originalEntry->uuidToHex()));
refEntry->setUrl(QString("{REF:A@I:%1}").arg(originalEntry->uuidToHex()));
// Verify references work within same database
QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->title()), QString("OriginalTitle"));
QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->username()), QString("OriginalUsername"));
QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->password()), QString("OriginalPassword"));
QCOMPARE(refEntry->resolveMultiplePlaceholders(refEntry->url()), QString("http://original.com"));
// Move the referenced entry to database 2
// This should resolve the references before the move
refEntry->setGroup(root2);
// After move, the entry should have resolved values instead of references
QCOMPARE(refEntry->title(), QString("OriginalTitle"));
QCOMPARE(refEntry->username(), QString("OriginalUsername"));
QCOMPARE(refEntry->password(), QString("OriginalPassword"));
QCOMPARE(refEntry->url(), QString("http://original.com"));
// Verify that the references have been replaced with actual values
QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::TitleKey));
QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::UserNameKey));
QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::PasswordKey));
QVERIFY(!refEntry->attributes()->isReference(EntryAttributes::URLKey));
}
void TestEntry::testIsRecycled()
{
auto entry = new Entry();

View File

@@ -39,6 +39,7 @@ private slots:
void testResolveConversionPlaceholders();
void testResolveReplacePlaceholders();
void testResolveClonedEntry();
void testCrossDatabaseReferences();
void testIsRecycled();
void testMoveUpDown();
void testPreviousParentGroup();