Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retry failed download requests implementation #3673

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion app/test/testmerginapi.cpp
VitorVieiraZ marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2687,7 +2687,7 @@ void TestMerginApi::deleteRemoteProjectNow( MerginApi *api, const QString &proje
QUrl url( api->mApiRoot + QStringLiteral( "/v2/projects/%1" ).arg( projectId ) );
request.setUrl( url );
qDebug() << "Trying to delete project " << projectName << ", id: " << projectId << " (" << url << ")";
QNetworkReply *r = api->mManager.deleteResource( request );
QNetworkReply *r = api->mManager->deleteResource( request );
QSignalSpy spy( r, &QNetworkReply::finished );
spy.wait( TestUtils::SHORT_REPLY );

Expand Down Expand Up @@ -2942,3 +2942,60 @@ void TestMerginApi::testParseVersion()
QCOMPARE( major, 2024 );
QCOMPARE( minor, 4 );
}

void TestMerginApi::testDownloadWithNetworkError()
{
QString projectName = "testDownloadRetry";

// create the project on the server (the content is not important)
createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" );

// Create mock manager - initially not failing
MockNetworkManager *mockManager = new MockNetworkManager( this );
mApi->setNetworkManager( mockManager );
VitorVieiraZ marked this conversation as resolved.
Show resolved Hide resolved

// Create signal spies
QSignalSpy startSpy( mApi, &MerginApi::downloadItemsStarted );
QSignalSpy retrySpy( mApi, &MerginApi::downloadItemRetried );
QSignalSpy finishSpy( mApi, &MerginApi::syncProjectFinished );

// Connect to downloadItemsStarted to trigger failure when items download start
connect( mApi, &MerginApi::downloadItemsStarted, this, [this]()
{
MockNetworkManager *failingManager = new MockNetworkManager( this );
failingManager->setShouldFail( true );
mApi->setNetworkManager( failingManager );
VitorVieiraZ marked this conversation as resolved.
Show resolved Hide resolved
} );

// Try to download the project
mApi->pullProject( mWorkspaceName, projectName );

// Verify a transaction was created
QCOMPARE( mApi->transactions().count(), 1 );

// Wait for the download to start and then fail
QVERIFY( startSpy.wait( TestUtils::LONG_REPLY ) );
QVERIFY( finishSpy.wait( TestUtils::LONG_REPLY ) );

// Verify signals were emitted
QVERIFY( startSpy.count() > 0 );
QVERIFY( retrySpy.count() > 0 );
QCOMPARE( finishSpy.count(), 1 );

// Verify that 5 (MAX_RETRY_COUNT) retry attempts were made
int maxRetries = TransactionStatus::MAX_RETRY_COUNT;
QCOMPARE( retrySpy.count(), maxRetries );

// Verify the sync failed
QList<QVariant> arguments = finishSpy.takeFirst();
QVERIFY( !arguments.at( 1 ).toBool() );

// Verify no local project was created
LocalProject localProject = mApi->localProjectsManager().projectFromMerginName( mWorkspaceName, projectName );
QVERIFY( !localProject.isValid() );

// Restore the original manager
QNetworkAccessManager *originalManager = new QNetworkAccessManager( this );
mApi->setNetworkManager( originalManager );
VitorVieiraZ marked this conversation as resolved.
Show resolved Hide resolved
}

63 changes: 63 additions & 0 deletions app/test/testmerginapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,68 @@

#include <qgsapplication.h>

class MockReply : public QNetworkReply
{
public:
explicit MockReply( const QNetworkRequest &request, QNetworkAccessManager::Operation operation, QObject *parent = nullptr )
: QNetworkReply( parent )
{
setRequest( request );
setOperation( operation );
setUrl( request.url() );

setError( QNetworkReply::TimeoutError, "Mock network failure" );

QMetaObject::invokeMethod( this, "errorOccurred", Qt::QueuedConnection, Q_ARG( QNetworkReply::NetworkError, QNetworkReply::TimeoutError ) );
QMetaObject::invokeMethod( this, "finished", Qt::QueuedConnection );
}

void abort() override {}

qint64 readData( char *data, qint64 maxlen ) override
{
Q_UNUSED( data );
Q_UNUSED( maxlen );
return -1;
}

qint64 bytesAvailable() const override
{
return 0;
}
};

class MockNetworkManager : public QNetworkAccessManager
{
public:
explicit MockNetworkManager( QObject *parent = nullptr )
: QNetworkAccessManager( parent )
, mShouldFail( false )
{}

void setShouldFail( bool shouldFail )
{
mShouldFail = shouldFail;
}

bool shouldFail() const { return mShouldFail; }

protected:
QNetworkReply *createRequest( Operation op, const QNetworkRequest &request, QIODevice *outgoingData = nullptr ) override
{
if ( mShouldFail )
{
MockReply *reply = new MockReply( request, op, this );
return reply;
}

return QNetworkAccessManager::createRequest( op, request, outgoingData );
}

private:
bool mShouldFail;
};

class TestMerginApi: public QObject
{
Q_OBJECT
Expand All @@ -40,6 +102,7 @@ class TestMerginApi: public QObject
void testListProject();
void testListProjectsByName();
void testDownloadProject();
void testDownloadWithNetworkError();
void testDownloadProjectSpecChars();
void testCancelDownloadProject();
void testCreateProjectTwice();
Expand Down
Loading
Loading