diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3633d61bf..6a02d2c09 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -149,6 +149,8 @@ jobs: - name: run tests env: TEST_MERGIN_URL: https://test.dev.merginmaps.com/ + TEST_API_USERNAME: test_mobileapp + TEST_API_PASSWORD: ${{ secrets.TEST_API_PASSWORD }} QT_QPA_PLATFORM: "offscreen" run: | cd build-Input-db/ diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index b91531c71..f17d2ebd1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -148,6 +148,8 @@ jobs: - name: run tests env: TEST_MERGIN_URL: https://test.dev.merginmaps.com/ + TEST_API_USERNAME: test_mobileapp2 + TEST_API_PASSWORD: ${{ secrets.TEST_API_PASSWORD }} run: | cd build-Input-db/ ctest --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index bf353ed86..a2761c5b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,14 +81,6 @@ else () set(HAVE_BLUETOOTH_DEFAULT TRUE) endif () -if (IOS) - set(HAVE_PURCHASING_DEFAULT TRUE) - set(HAVE_APPLE_PURCHASING_DEFAULT TRUE) -else () - set(HAVE_PURCHASING_DEFAULT ${ENABLE_TESTS_DEFAULT}) - set(HAVE_APPLE_PURCHASING_DEFAULT FALSE) -endif () - # on android in multi-ABI build, command line variables are NOT passed to # ExternalProject_Add called by QT. Therefore we need to pass the variables through ENV if (ANDROID) @@ -150,14 +142,7 @@ set(HAVE_BLUETOOTH ${HAVE_BLUETOOTH_DEFAULT} CACHE BOOL "Building with bluetooth position provider" ) -set(HAVE_PURCHASING - ${HAVE_PURCHASING_DEFAULT} - CACHE BOOL "Build with purchasing (e.g. for test of purchasing GUI)" -) -set(HAVE_APPLE_PURCHASING - ${HAVE_APPLE_PURCHASING_DEFAULT} - CACHE BOOL "Building with Apple's StoreKit support" -) + set(QT6_VERSION ${QT_VERSION_DEFAULT} CACHE STRING "QT6 version to use" @@ -343,14 +328,6 @@ if (ENABLE_TESTS) set(TEST_DATA_DIR "${CMAKE_CURRENT_BINARY_DIR}/test/test_data") endif () -if (HAVE_PURCHASING) - set(PURCHASING TRUE) -endif () - -if (HAVE_APPLE_PURCHASING) - set(APPLE_PURCHASING TRUE) -endif () - configure_file( ${CMAKE_SOURCE_DIR}/cmake_templates/inputconfig.h.in ${CMAKE_BINARY_DIR}/inputconfig.h ) diff --git a/INSTALL.md b/INSTALL.md index 8d55eda91..3375b0f77 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,4 +1,4 @@ -Building Mergin Maps Input from source - step by step +Building Mergin Maps mobile app from source - step by step # Table of Contents @@ -16,7 +16,7 @@ Building Mergin Maps Input from source - step by step * [5. Building iOS](#5-building-ios) * [6. Building macOS](#6-building-macos) * [7. Building Windows](#7-building-windows) - +* [8. Auto Testing](#9-auto-testing) # 1. Introduction @@ -372,3 +372,20 @@ set CL=/MP nmake ``` +# 8. Auto Testing + +You need to add cmake define `-DENABLE_TESTING=TRUE` on your cmake configure line. +Also you need to open Passbolt and check for password for user `test_mobileapp_dev` on `test.dev.merginmaps.com`, +or you need some user with unlimited projects limit. First workspace from list is taken. + +! Note that the same user cannot run tests in paraller ! + +now you need to set environment variables: +``` +TEST_MERGIN_URL=test.dev.merginmaps.com +TEST_API_USERNAME=test_mobileapp_dev +TEST_API_PASSWORD= +``` + +Build binary and you can run tests either with `ctest` or you can run individual tests by adding `--test` +e.g. ` ./input --testMerginApi` \ No newline at end of file diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b13513dbd..fe48ee1a2 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -68,7 +68,6 @@ set(MM_SRCS projectsmodel.cpp projectsproxymodel.cpp projectwizard.cpp - purchasing.cpp qrdecoder.cpp relationfeaturesmodel.cpp relationreferencefeaturesmodel.cpp @@ -149,7 +148,6 @@ set(MM_HDRS projectsmodel.h projectsproxymodel.h projectwizard.h - purchasing.h qrdecoder.h relationfeaturesmodel.h relationreferencefeaturesmodel.h @@ -181,14 +179,12 @@ if (ENABLE_TESTS) test/testformeditors.cpp test/testidentifykit.cpp test/testimageutils.cpp - test/testingpurchasingbackend.cpp test/testlayertree.cpp test/testlinks.cpp test/testmaptools.cpp test/testmerginapi.cpp test/testmodels.cpp test/testposition.cpp - test/testpurchasing.cpp test/testrememberattributescontroller.cpp test/testscalebarkit.cpp test/testutils.cpp @@ -207,14 +203,12 @@ if (ENABLE_TESTS) test/testformeditors.h test/testidentifykit.h test/testimageutils.h - test/testingpurchasingbackend.h test/testlayertree.h test/testlinks.h test/testmaptools.h test/testmerginapi.h test/testmodels.h test/testposition.h - test/testpurchasing.h test/testrememberattributescontroller.h test/testscalebarkit.h test/testutils.h @@ -223,6 +217,12 @@ if (ENABLE_TESTS) test/testactiveproject.h test/testprojectchecksumcache.h ) + + if (NOT USE_MM_SERVER_API_KEY) + set_property( + SOURCE test/testmerginapi.cpp PROPERTY COMPILE_DEFINITIONS USE_MERGIN_DUMMY_API_KEY + ) + endif () endif () if (IOS) @@ -246,12 +246,6 @@ if (IOS) ) endif () -if (HAVE_APPLE_PURCHASING) - set(MM_HDRS ${MM_HDRS} ios/iospurchasing.h) - - set(MM_SRCS ${MM_SRCS} ios/iospurchasing.mm) -endif () - if (ANDROID) set(MM_HDRS ${MM_HDRS} position/tracking/androidtrackingbackend.h position/tracking/androidtrackingbroadcast.h @@ -538,12 +532,6 @@ if (IOS) target_link_libraries(Input PUBLIC AppleFrameworks::CoreLocation) endif () -if (HAVE_APPLE_PURCHASING) - target_link_libraries( - Input PUBLIC AppleFrameworks::StoreKit AppleFrameworks::Foundation - ) -endif () - if (ENABLE_TESTS) target_link_libraries(Input PUBLIC Qt6::Test) endif () diff --git a/app/ios/iospurchasing.h b/app/ios/iospurchasing.h deleted file mode 100644 index e333c71c7..000000000 --- a/app/ios/iospurchasing.h +++ /dev/null @@ -1,120 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef IOSPURCHASING_H -#define IOSPURCHASING_H - -#include -#include -#include -#include - -#include "inputconfig.h" -#include "purchasing.h" - -class IosPurchasingBackend; -Q_FORWARD_DECLARE_OBJC_CLASS( InAppPurchaseManager ); -Q_FORWARD_DECLARE_OBJC_CLASS( SKProduct ); -Q_FORWARD_DECLARE_OBJC_CLASS( SKPaymentTransaction ); - -/** - * Wrapper around SKProduct class - */ -class IosPurchasingPlan: public PurchasingPlan -{ - Q_OBJECT - public: - SKProduct *nativeProduct() const; - IosPurchasingBackend *backend() const; - void setNativeProduct( SKProduct *nativeProduct ); - - private: - SKProduct *mNativeProduct; //this is objective-C instance -}; - -/** - * Wrapper around SKPaymentTransaction class - */ -class IosPurchasingTransaction: public PurchasingTransaction -{ - Q_OBJECT - public: - enum TransactionStatus - { - Unknown, - PurchaseApproved, - PurchaseFailed, - PurchaseRestored - }; - Q_ENUM( TransactionStatus ) - - IosPurchasingTransaction( SKPaymentTransaction *transaction, - TransactionStatus status, - QSharedPointer plan - ); - - IosPurchasingPlan *iosPlan() const; - - SKPaymentTransaction *nativeTransaction() const; - TransactionStatus status() const; - - QString receipt() const override; - MerginSubscriptionType::SubscriptionType provider() const override { return MerginSubscriptionType::AppleSubscriptionType; } - - /** - * Localized ios error message from the native framework, - * populated when TransactionStatus::PurchaseFailed - */ - QString errMsg() const; - - void finalizeTransaction() override; - - private: - TransactionType status2type( TransactionStatus status ); - - TransactionStatus mStatus; - SKPaymentTransaction *mNativeTransaction; //objective-C instance -}; - -/** - * Backend encapsulating the ios purchasing methods. - * Contains InAppPurchaseManager, - * which is Objective-C implementation of - * KProductsRequestDelegate and SKPaymentTransactionObserver - */ -class IosPurchasingBackend: public PurchasingBackend -{ - Q_OBJECT - public: - IosPurchasingBackend(); - ~IosPurchasingBackend() override; - - void init() override; - QSharedPointer createPlan() override {return QSharedPointer( new IosPurchasingPlan() );} - void registerPlan( QSharedPointer plan ) override; - - void createTransaction( QSharedPointer plan ) override; - void restore() override; - - QString subscriptionManageUrl() override {return "https://apps.apple.com/account/subscriptions"; } - QString subscriptionBillingUrl() override {return "https://apps.apple.com/account/billing"; } - MerginSubscriptionType::SubscriptionType provider() const override { return MerginSubscriptionType::AppleSubscriptionType; } - bool userCanMakePayments() const override; - bool hasManageSubscriptionCapability() const override { return false; } - QString getLocalizedPrice( const QString &planId ) const override; - - QSharedPointer findRegisteredPlan( const QString &productId ) const; - QSharedPointer findPendingPlan( const QString &productId ) const; - void processTransaction( QSharedPointer transaction ); - - private: - InAppPurchaseManager *mManager; //this is objective-C instance -}; - -#endif // IOSPURCHASING_H diff --git a/app/ios/iospurchasing.mm b/app/ios/iospurchasing.mm deleted file mode 100644 index cf84f67ff..000000000 --- a/app/ios/iospurchasing.mm +++ /dev/null @@ -1,354 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -#include -#include "inpututils.h" -#include "coreutils.h" - -#import "iospurchasing.h" -#import - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - -SKProduct *IosPurchasingPlan::nativeProduct() const -{ - return mNativeProduct; -} - -IosPurchasingBackend *IosPurchasingPlan::backend() const -{ - - return static_cast( purchasing()->backend() ); -} - -void IosPurchasingPlan::setNativeProduct( SKProduct *nativeProduct ) -{ - mNativeProduct = nativeProduct; -} - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - -IosPurchasingTransaction::IosPurchasingTransaction( SKPaymentTransaction *transaction, TransactionStatus status, - QSharedPointer plan - ) - : PurchasingTransaction( status2type( status ), plan ) - , mStatus( status ) - , mNativeTransaction( transaction ) -{ -} - -IosPurchasingPlan *IosPurchasingTransaction::iosPlan() const -{ - return static_cast( plan() ); -} - -SKPaymentTransaction *IosPurchasingTransaction::nativeTransaction() const -{ - return mNativeTransaction; -} - -IosPurchasingTransaction::TransactionStatus IosPurchasingTransaction::status() const -{ - return mStatus; -} - -QString IosPurchasingTransaction::receipt() const -{ - NSData *dataReceipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; - NSString *receipt = [dataReceipt base64EncodedStringWithOptions:0]; - return QString::fromNSString( receipt ); -} - -QString IosPurchasingTransaction::errMsg() const -{ - NSString *nativeStr = [ mNativeTransaction.error localizedDescription ]; - QString err = QString::fromNSString( nativeStr ); - return err; -} - -void IosPurchasingTransaction::finalizeTransaction() -{ - [[SKPaymentQueue defaultQueue] finishTransaction:mNativeTransaction]; -} - -PurchasingTransaction::TransactionType IosPurchasingTransaction::status2type( IosPurchasingTransaction::TransactionStatus status ) -{ - if ( status == TransactionStatus::PurchaseRestored ) - return PurchasingTransaction::RestoreTransaction; - else - { - return PurchasingTransaction::PuchaseTransaction; - } -} - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - -@interface InAppPurchaseManager : NSObject -{ - IosPurchasingBackend *backend; - NSMutableArray *pendingTransactions; -} - --( void )requestProductData:( NSString * )identifier; --( void )processPendingTransactions; - -@end - -@implementation InAppPurchaseManager - --( id )initWithBackend:( IosPurchasingBackend * )iapBackend -{ - if ( ( self = [super init] ) ) - { - backend = iapBackend; - pendingTransactions = [[NSMutableArray alloc] init]; - [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; - } - return self; -} - --( void )dealloc -{ - [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; - [pendingTransactions release]; - [super dealloc]; -} - --( void )requestProductData:( NSString * )identifier -{ - NSSet *productId = [NSSet setWithObject:identifier]; - SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productId]; - productsRequest.delegate = self; - [productsRequest start]; -} - --( void )productsRequest:( SKProductsRequest * )request didReceiveResponse:( SKProductsResponse * )response -{ - NSArray *products = response.products; - SKProduct *product = [products count] == 1 ? [[products firstObject] retain] : nil; - - if ( product == nil ) - { - // failed to fetch product id from store - NSString *invalidId = [response.invalidProductIdentifiers firstObject]; - QMetaObject::invokeMethod( backend, "planRegistrationFailed", Qt::AutoConnection, Q_ARG( QString, QString::fromNSString( invalidId ) ) ); - } - else - { - QSharedPointer plan = backend->findPendingPlan( QString::fromNSString( [product productIdentifier] ) ); - if ( plan ) - { - plan->setNativeProduct( product ); - - NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; - [numberFormatter setLocale:product.priceLocale]; - NSString *formattedPrice = [numberFormatter stringFromNumber:product.price]; - plan->setPrice( QString::fromNSString( formattedPrice ) ); - - QMetaObject::invokeMethod( backend, "planRegistrationSucceeded", Qt::AutoConnection, Q_ARG( QString, plan->id() ) ); - } - else - { - QMetaObject::invokeMethod( backend, "planRegistrationFailed", Qt::AutoConnection, Q_ARG( QString, QString::fromNSString( [product productIdentifier] ) ) ); - } - } - - [request release]; -} - -+( IosPurchasingTransaction::TransactionStatus )statusFromTransaction:( SKPaymentTransaction * )transaction -{ - IosPurchasingTransaction::TransactionStatus status; - switch ( transaction.transactionState ) - { - case SKPaymentTransactionStatePurchasing: - //Ignore the purchasing state as it's not really a transaction - //And its important that it doesn't need to be finalized as - //Calling finishTransaction: on a transaction that is - //in the SKPaymentTransactionStatePurchasing state throws an exception - status = IosPurchasingTransaction::Unknown; - break; - case SKPaymentTransactionStatePurchased: - status = IosPurchasingTransaction::PurchaseApproved; - break; - case SKPaymentTransactionStateFailed: - status = IosPurchasingTransaction::PurchaseFailed; - break; - case SKPaymentTransactionStateRestored: - status = IosPurchasingTransaction::PurchaseRestored; - break; - default: - status = IosPurchasingTransaction::Unknown; - break; - } - return status; -} - --( void )processPendingTransactions -{ - NSMutableArray *registeredTransactions = [NSMutableArray array]; - - for ( SKPaymentTransaction * transaction in pendingTransactions ) - { - IosPurchasingTransaction::TransactionStatus status = [InAppPurchaseManager statusFromTransaction:transaction]; - - QSharedPointer plan = backend->findRegisteredPlan( QString::fromNSString( transaction.payment.productIdentifier ) ); - if ( plan ) - { - //It is possible that the product doesn't exist yet (because of previous restores). - QSharedPointer qtTransaction( new IosPurchasingTransaction( transaction, status, plan ) ); - if ( qtTransaction->thread() != backend->thread() ) - { - qtTransaction->moveToThread( backend->thread() ); - } - [registeredTransactions addObject:transaction]; - backend->processTransaction( qtTransaction ); - } - } - - //Remove registeredTransactions from pendingTransactions - [pendingTransactions removeObjectsInArray:registeredTransactions]; -} - -//SKPaymentTransactionObserver -- ( void )paymentQueue:( SKPaymentQueue * )queue updatedTransactions:( NSArray * )transactions -{ - Q_UNUSED( queue ) - for ( SKPaymentTransaction * transaction in transactions ) - { - //Create IosTransaction - IosPurchasingTransaction::TransactionStatus status = [InAppPurchaseManager statusFromTransaction:transaction]; - - if ( status == IosPurchasingTransaction::Unknown ) - continue; - - QSharedPointer plan = backend->findRegisteredPlan( QString::fromNSString( transaction.payment.productIdentifier ) ); - if ( plan ) - { - QSharedPointer qtTransaction( new IosPurchasingTransaction( transaction, status, plan ) ); - if ( qtTransaction->thread() != backend->thread() ) - { - qtTransaction->moveToThread( backend->thread() ); - } - backend->processTransaction( qtTransaction ); - } - else - { - // Add the transaction to the pending transactions list, since product may not be yet registered - [pendingTransactions addObject:transaction]; - } - } -} - -@end - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - - -IosPurchasingBackend::IosPurchasingBackend() - : mManager( 0 ) -{ -} - -IosPurchasingBackend::~IosPurchasingBackend() -{ - [mManager release]; -} - -void IosPurchasingBackend::init() -{ - mManager = [[InAppPurchaseManager alloc] initWithBackend:this]; -} - -void IosPurchasingBackend::registerPlan( QSharedPointer plan ) -{ - [mManager requestProductData:( plan->id().toNSString() )]; -} - -void IosPurchasingBackend::createTransaction( QSharedPointer plan ) -{ - QSharedPointer iosPlan = qSharedPointerObjectCast ( plan ); - SKPayment *payment = [SKPayment paymentWithProduct:iosPlan->nativeProduct()]; - [[SKPaymentQueue defaultQueue] addPayment:payment]; -} - -void IosPurchasingBackend::restore() -{ - [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; -} - -void IosPurchasingBackend::processTransaction( QSharedPointer transaction ) -{ - if ( transaction->status() == IosPurchasingTransaction::PurchaseApproved ) - { - emit transactionCreationSucceeded( transaction ); - } - else if ( transaction->status() == IosPurchasingTransaction::PurchaseRestored ) - { - emit transactionCreationSucceeded( transaction ); - } - else if ( transaction->status() == IosPurchasingTransaction::PurchaseFailed ) - { - CoreUtils::log( "transaction creation", QStringLiteral( "Failed: " ) + transaction->errMsg() ); - emit transactionCreationFailed(); - transaction->finalizeTransaction(); - } - - qDebug() << "Transaction for product " << transaction->plan()->id() << " processed with status " << transaction->status(); -} - -bool IosPurchasingBackend::userCanMakePayments() const -{ - return [SKPaymentQueue canMakePayments]; -} - -QString IosPurchasingBackend::getLocalizedPrice( const QString &planId ) const -{ - QSharedPointer plan = findRegisteredPlan( planId ); - if ( plan ) - { - return plan->price(); - } - return QString(); -} - -QSharedPointer IosPurchasingBackend::findRegisteredPlan( const QString &productId ) const -{ - Purchasing *p = purchasing(); - Q_ASSERT( p ); - - QSharedPointer plan = p->registeredPlan( productId ); - if ( !plan ) - return nullptr; - - return qSharedPointerObjectCast( plan ); -} - -QSharedPointer IosPurchasingBackend::findPendingPlan( const QString &productId ) const -{ - Purchasing *p = purchasing(); - Q_ASSERT( p ); - - QSharedPointer plan = p->pendingPlan( productId ); - if ( !plan ) - return QSharedPointer(); - - return qSharedPointerObjectCast( plan ); -} - -#include "moc_iospurchasing.cpp" diff --git a/app/main.cpp b/app/main.cpp index edee257ea..b879f1c0d 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -45,12 +45,10 @@ #include "merginservertype.h" #include "merginsubscriptioninfo.h" #include "merginsubscriptionstatus.h" -#include "merginsubscriptiontype.h" #include "merginprojectstatusmodel.h" #include "layersproxymodel.h" #include "layersmodel.h" #include "activelayer.h" -#include "purchasing.h" #include "merginuserauth.h" #include "merginuserinfo.h" #include "variablesmanager.h" @@ -257,7 +255,6 @@ void initDeclarative() qmlRegisterUncreatableType( "lc", 1, 0, "MerginUserAuth", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "MerginUserInfo", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "MerginSubscriptionInfo", "" ); - qmlRegisterUncreatableType( "lc", 1, 0, "MerginPlan", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "ActiveProject", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "SynchronizationManager", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "SyncError", "SyncError Enum" ); @@ -265,7 +262,6 @@ void initDeclarative() qmlRegisterUncreatableType( "lc", 1, 0, "MerginApiStatus", "MerginApiStatus Enum" ); qmlRegisterUncreatableType( "lc", 1, 0, "MerginServerType", "MerginServerType Enum" ); qmlRegisterUncreatableType( "lc", 1, 0, "MerginSubscriptionStatus", "MerginSubscriptionStatus Enum" ); - qmlRegisterUncreatableType( "lc", 1, 0, "MerginSubscriptionType", "MerginSubscriptionType Enum" ); qmlRegisterUncreatableType( "lc", 1, 0, "MerginProjectStatusModel", "Enum" ); qmlRegisterUncreatableType( "lc", 1, 0, "LayersModel", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "LayersProxyModel", "" ); @@ -491,7 +487,6 @@ int main( int argc, char *argv[] ) ActiveLayer al; ActiveProject activeProject( as, al, recordingLpm, localProjectsManager ); - std::unique_ptr purchasing( new Purchasing( ma.get() ) ); std::unique_ptr vm( new VariablesManager( ma.get() ) ); vm->registerInputExpressionFunctions(); @@ -570,7 +565,7 @@ int main( int argc, char *argv[] ) if ( tests.testingRequested() ) { tests.initTestDeclarative(); - tests.init( ma.get(), purchasing.get(), &iu, vm.get(), &pk, &as ); + tests.init( ma.get(), &iu, vm.get(), &pk, &as ); return tests.runTest(); } #endif @@ -611,7 +606,6 @@ int main( int argc, char *argv[] ) engine.rootContext()->setContextProperty( "__merginProjectStatusModel", &mpsm ); engine.rootContext()->setContextProperty( "__recordingLayersModel", &recordingLpm ); engine.rootContext()->setContextProperty( "__activeLayer", &al ); - engine.rootContext()->setContextProperty( "__purchasing", purchasing.get() ); engine.rootContext()->setContextProperty( "__projectWizard", &pw ); engine.rootContext()->setContextProperty( "__localProjectsManager", &localProjectsManager ); engine.rootContext()->setContextProperty( "__variablesManager", vm.get() ); diff --git a/app/purchasing.cpp b/app/purchasing.cpp deleted file mode 100644 index 38b3c6c8f..000000000 --- a/app/purchasing.cpp +++ /dev/null @@ -1,717 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include -#include -#include - -#include "purchasing.h" -#include "merginapi.h" -#include "inpututils.h" -#include "merginuserinfo.h" -#include "merginsubscriptioninfo.h" -#include "coreutils.h" - -#if defined (APPLE_PURCHASING) -#include "ios/iospurchasing.h" -#endif -#if defined (INPUT_TEST) -#include "test/testingpurchasingbackend.h" -#endif - -void PurchasingPlan::clear() -{ - mAlias = QString(); - mId = QString(); - mPeriod = QString(); - mPrice = QString(); - mStorage = QString(); -} - -QString PurchasingPlan::alias() const -{ - return mAlias; -} - -void PurchasingPlan::setAlias( const QString &alias ) -{ - mAlias = alias; - emit planChanged(); -} - -QString PurchasingPlan::id() const -{ - return mId; -} - -void PurchasingPlan::setId( const QString &billingProductId ) -{ - mId = billingProductId; - emit planChanged(); -} - -QString PurchasingPlan::period() const -{ - return mPeriod; -} - -void PurchasingPlan::setPeriod( const QString &billingPeriod ) -{ - mPeriod = billingPeriod; - emit planChanged(); -} - -QString PurchasingPlan::price() const -{ - return mPrice; -} - -void PurchasingPlan::setPrice( const QString &billingPrice ) -{ - mPrice = billingPrice; - emit planChanged(); -} - -bool PurchasingPlan::isIndividualPlan() const -{ - return !mIsProfessional; -} - -bool PurchasingPlan::isProfessionalPlan() const -{ - return mIsProfessional; -} - -QString PurchasingPlan::storage() const -{ - return mStorage; -} - -void PurchasingPlan::setStorage( const QString &storage ) -{ - mStorage = storage; - emit planChanged(); -} - -Purchasing *PurchasingPlan::purchasing() const -{ - return mPurchasing; -} - -void PurchasingPlan::setPurchasing( Purchasing *purchasing ) -{ - mPurchasing = purchasing; -} - -void PurchasingPlan::setFromJson( QJsonObject docObj ) -{ - mAlias = docObj.value( QStringLiteral( "alias" ) ).toString(); - mId = docObj.value( QStringLiteral( "billing_product_id" ) ).toString(); - const QString billingPeriod = docObj.value( QStringLiteral( "billing_period" ) ).toString(); - bool isMonthly = billingPeriod.contains( "month", Qt::CaseInsensitive ); - if ( isMonthly ) - { - mPeriod = tr( "Monthly subscription" ); - } - else - { - mPeriod = tr( "Annual subscription" ); - } - const QString billingPrice = docObj.value( QStringLiteral( "billing_price" ) ).toString(); - if ( isMonthly ) - { - mPrice = billingPrice + "/" + tr( "month" ); - } - else - { - mPrice = billingPrice + "/" + tr( "year" ); - } - - double bytes = docObj.value( QStringLiteral( "storage" ) ).toDouble(); - mStorage = InputUtils::bytesToHumanSize( bytes ); - - mIsProfessional = ( bytes > 1024.0 * 1024.0 * 1024.0 * 5 ); // storage > 5GB - - emit planChanged(); -} - -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ - - -PurchasingTransaction::PurchasingTransaction( PurchasingTransaction::TransactionType type, QSharedPointer plan ) - : mPlan( plan ) - , mType( type ) -{ -} - -void PurchasingTransaction::verificationFinished() -{ - Q_ASSERT( mPlan ); - Q_ASSERT( mPlan->purchasing() ); - MerginApi *api = mPlan->purchasing()->merginApi(); - - QNetworkReply *r = qobject_cast( sender() ); - Q_ASSERT( r ); - - if ( r->error() == QNetworkReply::NoError ) - { - CoreUtils::log( "purchase successful", QStringLiteral( "Payment success" ) ); - mPlan->purchasing()->onTransactionVerificationSucceeded( this ); - } - else - { - QString serverMsg = api->extractServerErrorMsg( r->readAll() ); - QString message = QStringLiteral( "Network API error: %1(): %2. %3" ).arg( QStringLiteral( "purchase" ), r->errorString(), serverMsg ); - emit api->networkErrorOccurred( serverMsg, QStringLiteral( "Mergin API error: purchase" ) ); - CoreUtils::log( "purchase", QStringLiteral( "FAILED - %1" ).arg( message ) ); - mPlan->purchasing()->onTransactionVerificationFailed( this ); - } - r->deleteLater(); -} - -PurchasingTransaction::TransactionType PurchasingTransaction::type() const -{ - return mType; -} - -PurchasingPlan *PurchasingTransaction::plan() const -{ - return mPlan.get(); -} - -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ - -Purchasing::Purchasing( MerginApi *merginApi, QObject *parent ) - : QObject( parent ) - , mMerginApi( merginApi ) -{ - setDefaultUrls(); - createBackend(); - - connect( mMerginApi, &MerginApi::apiRootChanged, this, &Purchasing::onMerginServerChanged ); - connect( mMerginApi, &MerginApi::apiSupportsSubscriptionsChanged, this, &Purchasing::onMerginServerStatusChanged ); - connect( mMerginApi, &MerginApi::apiVersionStatusChanged, this, &Purchasing::onMerginServerStatusChanged ); - connect( mMerginApi->subscriptionInfo(), &MerginSubscriptionInfo::planProviderChanged, this, &Purchasing::evaluateHasInAppPurchases ); - connect( mMerginApi->subscriptionInfo(), &MerginSubscriptionInfo::planProductIdChanged, this, &Purchasing::onMerginPlanProductIdChanged ); - - connect( this, &Purchasing::hasInAppPurchasesChanged, this, &Purchasing::onHasInAppPurchasesChanged ); -} - -void Purchasing::createBackend() -{ - mBackend.reset(); -#if defined( PURCHASING ) -#if defined (APPLE_PURCHASING) - mBackend.reset( new IosPurchasingBackend ); -#elif defined (INPUT_TEST) - mBackend.reset( new TestingPurchasingBackend( mMerginApi ) ); -#endif -#endif - - if ( mBackend ) - { - mBackend->setPurchasing( this ); - mBackend->init(); - - connect( mBackend.get(), &PurchasingBackend::planRegistrationSucceeded, this, &Purchasing::onPlanRegistrationSucceeded ); - connect( mBackend.get(), &PurchasingBackend::planRegistrationFailed, this, &Purchasing::onPlanRegistrationFailed ); - - connect( mBackend.get(), &PurchasingBackend::transactionCreationSucceeded, this, &Purchasing::onTransactionCreationSucceeded ); - connect( mBackend.get(), &PurchasingBackend::transactionCreationFailed, this, &Purchasing::onTransactionCreationFailed ); - } -} - -void Purchasing::evaluateHasInAppPurchases() -{ - bool hasInApp = - mMerginApi->apiSupportsSubscriptions() && - mMerginApi->apiVersionStatus() == MerginApiStatus::OK && - bool( mBackend ) && - mBackend->userCanMakePayments(); - - bool hasCompatiblePlanType = true; - if ( mMerginApi->subscriptionInfo()->ownsActiveSubscription() ) - { - hasCompatiblePlanType = - bool( mBackend ) && - mBackend->provider() == mMerginApi->subscriptionInfo()->planProvider(); - } - setHasInAppPurchases( hasInApp && hasCompatiblePlanType ); -} - -void Purchasing::onHasInAppPurchasesChanged() -{ - bool hasManageCapability = false; - QString subscriptionManageUrl = mMerginApi->apiRoot() + "subscription"; - QString subscriptionBillingUrl = mMerginApi->apiRoot() + "billing"; - - if ( hasInAppPurchases() ) - { - hasManageCapability = mBackend->hasManageSubscriptionCapability(); - subscriptionManageUrl = mBackend->subscriptionManageUrl(); - subscriptionBillingUrl = mBackend->subscriptionBillingUrl(); - } - - setHasManageSubscriptionCapability( hasManageCapability ); - setSubscriptionManageUrl( subscriptionManageUrl ); - setSubscriptionBillingUrl( subscriptionBillingUrl ); -} - -bool Purchasing::hasInAppPurchases() const -{ - return mHasInAppPurchases; -} - -bool Purchasing::hasManageSubscriptionCapability() const -{ - return mHasManageSubscriptionCapability; -} - -QString Purchasing::subscriptionManageUrl() -{ - return mSubscriptionManageUrl; -} - -QString Purchasing::subscriptionBillingUrl() -{ - return mSubscriptionBillingUrl; -} - -void Purchasing::onMerginServerChanged() -{ - qDebug() << "Mergin Server Url changed reseting purchasing"; - clean(); -} - -void Purchasing::purchase( const QString &planId ) -{ - if ( transactionPending() ) - { - qDebug() << "unable to initiate purchase, there is a transaction pending"; - return; - } - - if ( hasInAppPurchases() ) - { - if ( mPlansWithPendingRegistration.contains( planId ) ) - { - qDebug() << "unable to initiate purchase, plan " << planId << " is not yet registered"; - } - - if ( !mRegisteredPlans.contains( planId ) ) - { - qDebug() << "unable to initiate purchase, unable to find plan " << planId; - } - - setTransactionCreationRequested( true ); - return mBackend->createTransaction( mRegisteredPlans.value( planId ) ); - } - else - { - qDebug() << "unable to initiate purchase, purchase api not ready"; - } -} - -void Purchasing::restore() -{ - if ( transactionPending() ) - { - qDebug() << "unable to initiate restore, there is a transaction pending"; - return; - } - - if ( hasInAppPurchases() ) - { - setTransactionCreationRequested( true ); - return mBackend->restore( ); - } - else - { - qDebug() << "unable to initiate restore, purchase api not ready"; - } -} - -void Purchasing::onMerginPlanProductIdChanged() -{ - if ( !mBackend ) - return; - - QString planId = mMerginApi->subscriptionInfo()->planProductId(); - if ( planId.isEmpty() ) - return; - - if ( mBackend->provider() != mMerginApi->subscriptionInfo()->planProvider() ) - return; - - QString price = mBackend->getLocalizedPrice( mMerginApi->subscriptionInfo()->planProductId() ); - mMerginApi->subscriptionInfo()->setLocalizedPrice( price ); -} - -void Purchasing::onMerginServerStatusChanged() -{ - qDebug() << "Mergin Server status changed, fetching purchasing plan"; - if ( mBackend && mPlansWithPendingRegistration.empty() && mRegisteredPlans.empty() ) - { - fetchPurchasingPlans(); - } - evaluateHasInAppPurchases(); -} - -void Purchasing::fetchPurchasingPlans( ) -{ - if ( !mMerginApi->apiSupportsSubscriptions() ) return; - if ( mMerginApi->apiVersionStatus() != MerginApiStatus::OK ) return; - - QUrl url( mMerginApi->apiRoot() + QStringLiteral( "/v1/plan" ) ); - QUrlQuery query; - query.addQueryItem( "billing_service", MerginSubscriptionType::toString( mBackend->provider() ) ); - url.setQuery( query ); - QNetworkRequest request = mMerginApi->getDefaultRequest( false ); - request.setUrl( url ); - QNetworkReply *reply = mMerginApi->mManager.get( request ); - connect( reply, &QNetworkReply::finished, this, &Purchasing::onFetchPurchasingPlansFinished ); - CoreUtils::log( "request plan", QStringLiteral( "Requesting purchasing plans for provider %1" ).arg( MerginSubscriptionType::toString( mBackend->provider() ) ) ); -} - -void Purchasing::onFetchPurchasingPlansFinished() -{ - QNetworkReply *r = qobject_cast( sender() ); - Q_ASSERT( r ); - QString serverMsg; - if ( r->error() == QNetworkReply::NoError ) - { - CoreUtils::log( "fetch plans", QStringLiteral( "Success" ) ); - QByteArray data = r->readAll(); - const QJsonDocument doc = QJsonDocument::fromJson( data ); - if ( doc.isArray() ) - { - const QJsonArray vArray = doc.array(); - for ( auto it = vArray.constBegin(); it != vArray.constEnd(); ++it ) - { - const QJsonObject obj = it->toObject(); - QSharedPointer plan = mBackend->createPlan(); - plan->setFromJson( obj ); - plan->setPurchasing( this ); - if ( mPlansWithPendingRegistration.contains( plan->id() ) ) - { - qDebug() << "Plan " << plan->id() << " registration already pending"; - } - else if ( mRegisteredPlans.contains( plan->id() ) ) - { - qDebug() << "Plan " << plan->id() << " already registered"; - } - else - { - qDebug() << "Plan " << plan->id() << " requested registration"; - mPlansWithPendingRegistration.insert( plan->id(), plan ); - mBackend->registerPlan( plan ); - } - } - } - } - else - { - serverMsg = mMerginApi->extractServerErrorMsg( r->readAll() ); - CoreUtils::log( "fetch plans", QStringLiteral( "FAILED - %1. %2" ).arg( r->errorString(), serverMsg ) ); - } - r->deleteLater(); -} - -void Purchasing::clean() -{ - createBackend(); - mRegisteredPlans.clear(); - mPlansWithPendingRegistration.clear(); - mTransactionsWithPendingVerification.clear(); - mTransactionCreationRequested = false; - mIndividualPlanId.clear(); - mProfessionalPlanId.clear(); - setDefaultUrls(); - setHasInAppPurchases( false ); - - emit individualPlanChanged(); - emit professionalPlanChanged(); - emit transactionPendingChanged(); -} - -void Purchasing::onPlanRegistrationFailed( const QString &id ) -{ - qDebug() << "Failed to register plan " + id; - if ( mPlansWithPendingRegistration.contains( id ) ) - mPlansWithPendingRegistration.remove( id ); - - if ( mPlansWithPendingRegistration.empty() && mRegisteredPlans.empty() ) - { - CoreUtils::log( "Plan Registration", QStringLiteral( "Failed to register any plans" ) ); - } -} - -void Purchasing::onPlanRegistrationSucceeded( const QString &id ) -{ - if ( mPlansWithPendingRegistration.contains( id ) ) - { - QSharedPointer plan = mPlansWithPendingRegistration.take( id ); - if ( mRegisteredPlans.contains( plan->id() ) ) - { - qDebug() << "Plan " << id << " is already in registered plans."; - } - else - { - qDebug() << "Plan " + id + " registered"; - mRegisteredPlans.insert( id, plan ); - if ( plan->isProfessionalPlan() ) - { - qDebug() << "Plan " + id + " is professional plan"; - setProfessionalPlanId( plan->id() ); - } - if ( plan->isIndividualPlan() ) - { - qDebug() << "Plan " + id + " is individual plan"; - setIndividualPlanId( plan->id() ); - } - } - } - else - { - qDebug() << "Plan " + id + " registered OK, but failed to find in pending registrations"; - } - emit hasInAppPurchasesChanged(); -} - -void Purchasing::onTransactionCreationSucceeded( QSharedPointer transaction ) -{ - setTransactionCreationRequested( false ); - - if ( !mMerginApi->validateAuth() || mMerginApi->apiVersionStatus() != MerginApiStatus::OK ) - { - return; - } - - mTransactionsWithPendingVerification.push_back( transaction ); - - QNetworkRequest request = mMerginApi->getDefaultRequest(); - QUrl url( mMerginApi->apiRoot() + QStringLiteral( "v1/subscription/process-transaction" ) ); - request.setUrl( url ); - request.setHeader( QNetworkRequest::ContentTypeHeader, QVariant( "application/json" ) ); - QJsonDocument jsonDoc; - QJsonObject jsonObject; - jsonObject.insert( QStringLiteral( "type" ), MerginSubscriptionType::toString( transaction->provider() ) ); - jsonObject.insert( QStringLiteral( "receipt-data" ), transaction->receipt() ); - jsonObject.insert( QStringLiteral( "api_key" ), mMerginApi->getApiKey( mMerginApi->apiRoot() ) ); - - if ( mMerginApi->serverType() == MerginServerType::SAAS ) - { - jsonObject.insert( QStringLiteral( "workspace" ), mMerginApi->userInfo()->activeWorkspaceId() ); - } - - jsonDoc.setObject( jsonObject ); - QByteArray json = jsonDoc.toJson( QJsonDocument::Compact ); - QNetworkReply *reply = mMerginApi->mManager.post( request, json ); - connect( reply, &QNetworkReply::finished, transaction.get(), &PurchasingTransaction::verificationFinished ); - CoreUtils::log( "process transaction", QStringLiteral( "Requesting processing of in-app transaction: " ) + url.toString() ); -} - -void Purchasing::onTransactionCreationFailed() -{ - notify( tr( "Failed to process payment details.%1Subscription is not purchased." ).arg( "
" ) ); - setTransactionCreationRequested( false ); -} - -void Purchasing::onTransactionVerificationSucceeded( PurchasingTransaction *transaction ) -{ - Q_ASSERT( transaction ); - if ( transaction->type() == PurchasingTransaction::RestoreTransaction ) - notify( tr( "Successfully restored your subscription" ) ); - else - notify( tr( "Successfully purchased subscription" ) ); - - removePendingTransaction( transaction ); - mMerginApi->getServiceInfo(); -} - -void Purchasing::onTransactionVerificationFailed( PurchasingTransaction *transaction ) -{ - Q_ASSERT( transaction ); - if ( transaction->type() == PurchasingTransaction::RestoreTransaction ) - notify( tr( "Unable to restore your subscription" ) ); - else - notify( tr( "Failed to purchase subscription" ) ); - - removePendingTransaction( transaction ); -} - -void Purchasing::notify( const QString &msg ) -{ - mMerginApi->notify( msg ); -} - - -MerginApi *Purchasing::merginApi() const -{ - return mMerginApi; -} - -int Purchasing::registeredPlansCount() const -{ - return mRegisteredPlans.count(); -} - -void Purchasing::removePendingTransaction( PurchasingTransaction *transaction ) -{ - transaction->finalizeTransaction(); - - int index = -1; - for ( int i = 0; i < mTransactionsWithPendingVerification.count(); ++i ) - { - if ( mTransactionsWithPendingVerification.at( i ).get() == transaction ) - { - index = i; - break; - } - } - if ( index >= 0 ) - { - mTransactionsWithPendingVerification.removeAt( index ); - } - - emit transactionPendingChanged(); -} - - -bool Purchasing::transactionPending() const -{ - return !mTransactionsWithPendingVerification.empty() || mTransactionCreationRequested; -} - -PurchasingPlan *Purchasing::individualPlan() const -{ - static PurchasingPlan sEmptyPlan; - - QSharedPointer plan = registeredPlan( mIndividualPlanId ); - if ( plan ) - { - return plan.get(); - } - else - { - return &sEmptyPlan; - } -} - -PurchasingPlan *Purchasing::professionalPlan() const -{ - static PurchasingPlan sEmptyPlan; - - QSharedPointer plan = registeredPlan( mProfessionalPlanId ); - if ( plan ) - { - return plan.get(); - } - else - { - return &sEmptyPlan; - } -} - -QSharedPointer Purchasing::registeredPlan( const QString &id ) const -{ - if ( id.isEmpty() ) - return nullptr; - - if ( mRegisteredPlans.contains( id ) ) - return mRegisteredPlans.value( id ); - - return nullptr; -} - -QSharedPointer Purchasing::pendingPlan( const QString &id ) const -{ - if ( id.isEmpty() ) - return nullptr; - - if ( mPlansWithPendingRegistration.contains( id ) ) - return mPlansWithPendingRegistration.value( id ); - - return nullptr; -} - -void Purchasing::setSubscriptionBillingUrl( const QString &subscriptionBillingUrl ) -{ - if ( mSubscriptionBillingUrl != subscriptionBillingUrl ) - { - mSubscriptionBillingUrl = subscriptionBillingUrl; - emit subscriptionBillingUrlChanged(); - } -} - -void Purchasing::setDefaultUrls() -{ - mSubscriptionManageUrl = mMerginApi->apiRoot() + "subscription"; - emit subscriptionManageUrlChanged(); - mSubscriptionBillingUrl = mMerginApi->apiRoot() + "billing"; - emit subscriptionBillingUrlChanged(); -} - -void Purchasing::setSubscriptionManageUrl( const QString &subscriptionManageUrl ) -{ - if ( mSubscriptionManageUrl != subscriptionManageUrl ) - { - mSubscriptionManageUrl = subscriptionManageUrl; - emit subscriptionManageUrlChanged(); - } -} - -void Purchasing::setHasManageSubscriptionCapability( bool hasManageSubscriptionCapability ) -{ - if ( mHasManageSubscriptionCapability != hasManageSubscriptionCapability ) - { - mHasManageSubscriptionCapability = hasManageSubscriptionCapability; - emit hasManageSubscriptionCapabilityChanged(); - } -} - -void Purchasing::setHasInAppPurchases( bool hasInAppPurchases ) -{ - if ( mHasInAppPurchases != hasInAppPurchases ) - { - mHasInAppPurchases = hasInAppPurchases; - emit hasInAppPurchasesChanged(); - } -} - -void Purchasing::setTransactionCreationRequested( bool transactionCreationRequested ) -{ - if ( mTransactionCreationRequested != transactionCreationRequested ) - { - mTransactionCreationRequested = transactionCreationRequested; - emit transactionPendingChanged(); - } -} - -void Purchasing::setIndividualPlanId( const QString &planId ) -{ - if ( mIndividualPlanId != planId ) - { - mIndividualPlanId = planId; - emit individualPlanChanged(); - } -} - -void Purchasing::setProfessionalPlanId( const QString &planId ) -{ - if ( mProfessionalPlanId != planId ) - { - mProfessionalPlanId = planId; - emit professionalPlanChanged(); - } -} diff --git a/app/purchasing.h b/app/purchasing.h deleted file mode 100644 index 7ce0ae6dc..000000000 --- a/app/purchasing.h +++ /dev/null @@ -1,338 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef PURCHASING_H -#define PURCHASING_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "inputconfig.h" -#include "merginsubscriptiontype.h" - -class MerginApi; -class Purchasing; - -/** - * Purchasing Plan is a plan for subscription - * - * Each user can have assigned one subscription plan on Mergin. - * Plan defines the maximum storage, billing price, period and so. - */ -class PurchasingPlan: public QObject -{ - Q_OBJECT - Q_PROPERTY( QString alias READ alias NOTIFY planChanged ) - Q_PROPERTY( QString id READ id NOTIFY planChanged ) - Q_PROPERTY( QString period READ period NOTIFY planChanged ) - Q_PROPERTY( QString price READ price NOTIFY planChanged ) - Q_PROPERTY( QString storage READ storage NOTIFY planChanged ) - - public: - void clear(); - - QString alias() const; - void setAlias( const QString &alias ); - - QString id() const; - void setId( const QString &id ); - - QString period() const; - void setPeriod( const QString &period ); - - QString price() const; - void setPrice( const QString &price ); - - bool isIndividualPlan() const; - bool isProfessionalPlan() const; - - void setFromJson( QJsonObject docObj ); - - QString storage() const; - - void setStorage( const QString &storage ); - - Purchasing *purchasing() const; - void setPurchasing( Purchasing *purchasing ); - - signals: - void planChanged(); - - private: - QString mAlias; - QString mId; - QString mPeriod; - QString mPrice; - QString mStorage; - - Purchasing *mPurchasing = nullptr; - bool mIsProfessional = false; -}; - -/** - * The Purchasing Transaction represent one payment transaction - * - * The puchasing transaction is created when user pays the plan - * and is verified on the Mergin server. - */ -class PurchasingTransaction : public QObject -{ - Q_OBJECT - public: - enum TransactionType - { - RestoreTransaction, - PuchaseTransaction - }; - Q_ENUM( TransactionType ) - - PurchasingTransaction( TransactionType type, QSharedPointer plan ); - - void virtual finalizeTransaction() = 0; - - //! Transaction receipt, e.g. apple base64 receipt - virtual QString receipt() const = 0; - - //! Transaction provider, either apple, stripe or test - virtual MerginSubscriptionType::SubscriptionType provider() const = 0; - - PurchasingPlan *plan() const; - TransactionType type() const; - - public slots: - void verificationFinished(); - - private: - QSharedPointer mPlan; - const TransactionType mType; -}; - - -class PurchasingBackend: public QObject -{ - Q_OBJECT - public: - /** - * Initializes the service - * Called once on very startup of the QApplication - */ - virtual void init() = 0; - - /** - * Creates an instance of the purchasing plan from JSON. - * Registration is done later in registerPlan - */ - virtual QSharedPointer createPlan( ) = 0; - - /** - * Register the products in the native SDK. - */ - virtual void registerPlan( QSharedPointer plan ) = 0; - - /** - * Buys the selected product. - */ - virtual void createTransaction( QSharedPointer plan ) = 0; - - /** - * Restore purchases with recipes stored on this device - */ - virtual void restore() = 0; - - /** - * Returns whether the backend can manage (upgrade/downgrade/...) - * the subscriptions or that has to be done on external - * website (mergin/apple/...) - */ - virtual bool hasManageSubscriptionCapability() const = 0; - - /** - * URL to show user when he wants so upgrade/downgrade - * existing (paid) subscriptions - * - * non-empty URL only when hasManageSubscriptionCapability() == false - */ - virtual QString subscriptionManageUrl() = 0; - - /** - * URL to show user when he wants so - * change billing details (e.g. credit card number) - * - * non-empty URL only when hasManageSubscriptionCapability() == false - */ - virtual QString subscriptionBillingUrl() = 0; - - /** - * Name of the billing service for Mergin API - * (e.g. stripe, apple, google, ...) - */ - virtual MerginSubscriptionType::SubscriptionType provider() const = 0; - - /** - * Whether user can make purchases on the device - */ - virtual bool userCanMakePayments() const = 0; - - void setPurchasing( Purchasing *purchasing ) {mPurchasing = purchasing;} - Purchasing *purchasing() const {return mPurchasing;} - - //! Returns localised prize of plan, empty string if cannot be fetched - virtual QString getLocalizedPrice( const QString &planId ) const = 0; - - signals: - void transactionCreationFailed( ); - void transactionCreationSucceeded( QSharedPointer transaction ); - - void planRegistrationFailed( const QString &id ); - void planRegistrationSucceeded( const QString &id ); - - private: - Purchasing *mPurchasing = nullptr; -}; - -/** - * The Purchasing class, responsible for in-app purchases - * - * The purchasing can be disabled or enabled based on these factors - * 1. Mergin Server can be deployed with the flag that subscriptions are disabled - * 2. Device (e.g. iPhone) can be in mode where purchases are not allowed - * 3. The plans in the 3rd party store (e.g. AppStore) does not match the plans in Mergin Server - * 4. The InputApp does not have backed to manage user plan on Mergin Server (e.g. stripe plans) - * - * When purchasing is enabled the workflow is as follows - * 1. Fetches the plan details from Mergin to check which plans are available for user. One of the plan - * is reccomended and only this one is shown in the GUI - * 2. The plans are then registered in the backend (e.g. with Apple's StoreKit) - * 3. If the plans are correctly registered, the purchasing API is now in state to do payments/transactions - * Therefore user has 2 options: - * 3.A) restore subscriptions (in case for example he paid the subscription but - * network error caused that it was not correctly processed on Mergin) - * 3.B) buy new subscription (the reccomeneded one) - * If there are some pending transactions on the device, these can create transactions even without the user - * action. - * 4. When user pays the plan, the backend creates the transaction. The transaction receipt is extracted - * and send to the Mergin. Mergin verifies the receipt and based on the data changes the subscription on - * the server. - * 5. User is notified about successfull payment and the storage is increased. - * - * For managing existing subscriptions, user needs to use the official URLs (e.g. Apple iTunes subscription manager) - * - * For testing purposes and the development purposes on the Linux, we have also created a fake backend, - * that can be used to simulate various subscription statuses/workflows on the Mergin. The testing backend can - * only be used on development Mergin servers. - * - * For testing the in-app purchases in the 3rd party stores, check the documentation of the PurchasingBackend implementations. - */ -class Purchasing : public QObject -{ - Q_OBJECT - - Q_PROPERTY( PurchasingPlan *individualPlan READ individualPlan NOTIFY individualPlanChanged ) - Q_PROPERTY( PurchasingPlan *professionalPlan READ professionalPlan NOTIFY professionalPlanChanged ) - Q_PROPERTY( bool transactionPending READ transactionPending NOTIFY transactionPendingChanged ) - Q_PROPERTY( bool hasInAppPurchases READ hasInAppPurchases NOTIFY hasInAppPurchasesChanged ) - Q_PROPERTY( bool hasManageSubscriptionCapability READ hasManageSubscriptionCapability NOTIFY hasManageSubscriptionCapabilityChanged ) - Q_PROPERTY( QString subscriptionManageUrl READ subscriptionManageUrl NOTIFY subscriptionManageUrlChanged ) - Q_PROPERTY( QString subscriptionBillingUrl READ subscriptionBillingUrl NOTIFY subscriptionBillingUrlChanged ) - - public: - explicit Purchasing( MerginApi *merginApi, QObject *parent = nullptr ); - - Q_INVOKABLE void purchase( const QString &planId ); - Q_INVOKABLE void restore(); - - bool hasManageSubscriptionCapability() const; - bool transactionPending() const; - bool hasInAppPurchases() const; - QString subscriptionManageUrl(); - QString subscriptionBillingUrl(); - PurchasingPlan *individualPlan() const; - PurchasingPlan *professionalPlan() const; - - // Public function required only internally within the purchasing classes - public: - PurchasingBackend *backend() {return mBackend.get();} - QSharedPointer registeredPlan( const QString &id ) const; - QSharedPointer pendingPlan( const QString &id ) const; - MerginApi *merginApi() const; - int registeredPlansCount() const; - - signals: - void transactionPendingChanged(); - void individualPlanChanged(); - void professionalPlanChanged(); - void hasInAppPurchasesChanged(); - void hasManageSubscriptionCapabilityChanged(); - void subscriptionManageUrlChanged(); - void subscriptionBillingUrlChanged(); - - private slots: - void onFetchPurchasingPlansFinished(); - - void onPlanRegistrationFailed( const QString &id ); - void onPlanRegistrationSucceeded( const QString &id ); - - void onTransactionCreationSucceeded( QSharedPointer transaction ); - void onTransactionCreationFailed( ); - - void onTransactionVerificationSucceeded( PurchasingTransaction *transaction ); - void onTransactionVerificationFailed( PurchasingTransaction *transaction ); - - void onMerginServerStatusChanged(); - void onMerginServerChanged(); - void onMerginPlanProductIdChanged(); - void evaluateHasInAppPurchases(); - void onHasInAppPurchasesChanged(); - - private: - void createBackend(); - - void clean(); - void fetchPurchasingPlans(); - void setTransactionCreationRequested( bool transactionCreationRequested ); - void setIndividualPlanId( const QString &PlanId ); - void setProfessionalPlanId( const QString &PlanId ); - void removePendingTransaction( PurchasingTransaction *transaction ); - - void setHasInAppPurchases( bool hasInAppPurchases ); - void setHasManageSubscriptionCapability( bool hasManageSubscriptionCapability ); - void setSubscriptionManageUrl( const QString &subscriptionManageUrl ); - void setSubscriptionBillingUrl( const QString &subscriptionBillingUrl ); - - void setDefaultUrls(); - - void notify( const QString &msg ); - - QMap > mPlansWithPendingRegistration; - QMap > mRegisteredPlans; - QString mIndividualPlanId; - QString mProfessionalPlanId; - - bool mTransactionCreationRequested = false; - QList> mTransactionsWithPendingVerification; - - std::unique_ptr mBackend = nullptr; - MerginApi *mMerginApi = nullptr; - - bool mHasInAppPurchases = false; - bool mHasManageSubscriptionCapability; - QString mSubscriptionManageUrl; - QString mSubscriptionBillingUrl; - - friend class PurchasingTransaction; -}; - -#endif // PURCHASING_H diff --git a/app/qml/AccountPage.qml b/app/qml/AccountPage.qml index dc6813757..23c8b98e8 100644 --- a/app/qml/AccountPage.qml +++ b/app/qml/AccountPage.qml @@ -20,7 +20,6 @@ Page { signal back signal managePlansClicked signal signOutClicked - signal restorePurchasesClicked signal accountDeleted property color bgColor: "white" property real fieldHeight: InputStyle.rowHeight @@ -149,7 +148,7 @@ Page { Qt.openUrlExternally(link) } text: qsTr("Please update your %1billing details%2 as soon as possible") - .arg("") + .arg("") .arg("") iconColor: InputStyle.highlightColor } @@ -206,13 +205,12 @@ Page { Button { id: subscribeButton - enabled: !__purchasing.transactionPending width: root.width - 2 * InputStyle.rowHeightHeader anchors.horizontalCenter: parent.horizontalCenter visible: __merginApi.apiSupportsSubscriptions height: InputStyle.rowHeightHeader - text: __purchasing.transactionPending ? qsTr("Working...") : root.ownsActiveSubscription ? qsTr("Manage Subscription") : qsTr("Subscription plans") + text: root.ownsActiveSubscription ? qsTr("Manage Subscription") : qsTr("Subscription plans") font.pixelSize: InputStyle.fontPixelSizeBig background: Rectangle { @@ -231,33 +229,6 @@ Page { elide: Text.ElideRight } } - - Item { - id: spacer - visible: textRestore.visible - height: InputStyle.rowHeightHeader - width:parent.width - } - - Text { - id: textRestore - visible: __iosUtils.isIos && __merginApi.apiSupportsSubscriptions && __purchasing.hasInAppPurchases && !__purchasing.transactionPending - textFormat: Text.RichText - onLinkActivated: restorePurchasesClicked() - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: "" + qsTr( - "You can also %1restore%2 your purchases") - .arg("") - .arg("") - font.pixelSize: InputStyle.fontPixelSizeNormal - color: InputStyle.fontColor - width: root.width - leftPadding: InputStyle.rowHeightHeader - rightPadding: InputStyle.rowHeightHeader - } } // ///////////////// diff --git a/app/qml/ProjectPanel.qml b/app/qml/ProjectPanel.qml index 0b4caa47e..87146f58f 100644 --- a/app/qml/ProjectPanel.qml +++ b/app/qml/ProjectPanel.qml @@ -915,9 +915,6 @@ Item { stackView.pop( null ) root.resetView() } - onRestorePurchasesClicked: { - __purchasing.restore() - } onAccountDeleted: { stackView.popOnePageOrClose() root.resetView() diff --git a/app/qml/WorkspaceAccountPage.qml b/app/qml/WorkspaceAccountPage.qml index 999b9a010..f6afaae45 100644 --- a/app/qml/WorkspaceAccountPage.qml +++ b/app/qml/WorkspaceAccountPage.qml @@ -177,7 +177,7 @@ Page { titleText: qsTr( "Subscription status" ) text: qsTr("Please update your %1billing details%2 as soon as possible") - .arg("") + .arg("") .arg("") textComponent.onLinkActivated: function ( link ) { @@ -215,33 +215,6 @@ Page { onClicked: root.managePlansClicked() } - Text { - Layout.fillWidth: true - Layout.preferredHeight: InputStyle.rowHeightHeader - - leftPadding: InputStyle.formSpacing - - color: InputStyle.fontColor - linkColor: InputStyle.highlightColor - - wrapMode: Text.WordWrap - textFormat: Text.StyledText - - font.bold: true - font.pixelSize: InputStyle.fontPixelSizeNormal - - verticalAlignment: Qt.AlignVCenter - horizontalAlignment: Qt.AlignLeft - - visible: __iosUtils.isIos && root.apiSupportsSubscriptions && root.canAccessSubscription - - text: qsTr("You can also %1restore%2 your purchase.") - .arg("") - .arg("") - - onLinkActivated: __purchasing.restore() - } - // user profile Rectangle { diff --git a/app/test/inputtests.cpp b/app/test/inputtests.cpp index 16bad90aa..eef9caccd 100644 --- a/app/test/inputtests.cpp +++ b/app/test/inputtests.cpp @@ -32,10 +32,6 @@ #include "test/testactiveproject.h" #include "test/testprojectchecksumcache.h" -#if not defined APPLE_PURCHASING -#include "test/testpurchasing.h" -#endif - InputTests::InputTests() = default; InputTests::~InputTests() = default; @@ -63,10 +59,9 @@ bool InputTests::testingRequested() const return !mTestRequested.isEmpty(); } -void InputTests::init( MerginApi *api, Purchasing *purchasing, InputUtils *utils, VariablesManager *varManager, PositionKit *kit, AppSettings *settings ) +void InputTests::init( MerginApi *api, InputUtils *utils, VariablesManager *varManager, PositionKit *kit, AppSettings *settings ) { mApi = api; - mPurchasing = purchasing; mInputUtils = utils; mVariablesManager = varManager; mPositionKit = kit; @@ -94,7 +89,7 @@ int InputTests::runTest() const { int nFailed = 0; - if ( !mApi || !mPurchasing || !mInputUtils ) + if ( !mApi || !mInputUtils ) { nFailed = 1000; qDebug() << "input tests not initialized"; @@ -187,19 +182,20 @@ int InputTests::runTest() const TestProjectChecksumCache projectChecksumTest; nFailed = QTest::qExec( &projectChecksumTest, mTestArgs ); } -#if not defined APPLE_PURCHASING - else if ( mTestRequested == "--testPurchasing" ) - { - TestPurchasing purchasingTest( mApi, mPurchasing ); - nFailed = QTest::qExec( &purchasingTest, mTestArgs ); - } else if ( mTestRequested == "--testMerginApi" ) { - TestMerginApi merginApiTest( mApi, mPurchasing ); - nFailed = QTest::qExec( &merginApiTest, mTestArgs ); - } + TestMerginApi merginApiTest( mApi ); + QStringList args = mTestArgs; + if ( !args.contains( "-maxwarnings" ) ) + { + args << "-maxwarnings" << "0"; //show all debug output + } -#endif + // To pick just one particular test, uncomment + // following line and add function name + // args << "testRegisterAndDelete"; + nFailed = QTest::qExec( &merginApiTest, args ); + } else { qDebug() << "invalid test requested" << mTestRequested; diff --git a/app/test/inputtests.h b/app/test/inputtests.h index 3d994bafe..3fe36fd98 100644 --- a/app/test/inputtests.h +++ b/app/test/inputtests.h @@ -16,7 +16,6 @@ #include class MerginApi; -class Purchasing; class InputUtils; class VariablesManager; class PositionKit; @@ -32,7 +31,7 @@ class InputTests bool testingRequested() const; - void init( MerginApi *api, Purchasing *purchasing, InputUtils *utils, VariablesManager *varManager, PositionKit *positionKit, AppSettings *settings ); + void init( MerginApi *api, InputUtils *utils, VariablesManager *varManager, PositionKit *positionKit, AppSettings *settings ); void initTestDeclarative(); QString initTestingDir(); int runTest() const; @@ -41,7 +40,6 @@ class InputTests QString mTestRequested; QStringList mTestArgs; MerginApi *mApi = nullptr; - Purchasing *mPurchasing = nullptr; InputUtils *mInputUtils = nullptr; VariablesManager *mVariablesManager = nullptr; PositionKit *mPositionKit = nullptr; diff --git a/app/test/testingpurchasingbackend.cpp b/app/test/testingpurchasingbackend.cpp deleted file mode 100644 index cd03f723a..000000000 --- a/app/test/testingpurchasingbackend.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "testingpurchasingbackend.h" - -#include "merginapi.h" -#include "merginuserinfo.h" -#include "merginsubscriptioninfo.h" -#include "inpututils.h" - -#if defined (HAVE_WIDGETS) -#include -#include -#endif - -TestingPurchasingTransaction::TestingPurchasingTransaction( QString receipt, PurchasingTransaction::TransactionType type, QSharedPointer plan ) - : PurchasingTransaction( type, plan ), mReceipt( receipt ) {} - -QString TestingPurchasingTransaction::receipt() const {return mReceipt;} - -TestingPurchasingBackend::TestingPurchasingBackend( MerginApi *api ) - : PurchasingBackend() - , mMerginApi( api ) -{ -} - -void TestingPurchasingBackend::setNextPurchaseResult( const TestingPurchasingBackend::NextPurchaseResult expected ) -{ - mNextResult = expected; -} - -void TestingPurchasingBackend::registerPlan( QSharedPointer plan ) -{ - if ( plan->isIndividualPlan() ) - mIndividualPlan = plan; - - emit planRegistrationSucceeded( plan->id() ); -} - - -void TestingPurchasingBackend::createTransaction( QSharedPointer plan ) -{ - if ( mNextResult == Interactive ) - { -#if defined (HAVE_WIDGETS) - QStringList items; - if ( plan->isIndividualPlan() ) - { - items << "Buy individual plan | tier01"; - } - else - { - items << "Buy professional plan | tier12"; - } - - if ( mMerginApi->subscriptionInfo()->ownsActiveSubscription() ) - { - items << "Immediately refund the subscription (got refund) | cancel" - << "Set grace period | grace" - << "Set unsubscribed | unsubscribe"; - } - items << "Cancel Payment | cancelPayment" - << "Send invalid receipt | invalidreceipt"; - - bool ok; - QString item = QInputDialog::getItem( nullptr, "QInputDialog::getItem()", - "PURCHASING TEST", items, 0, false, &ok ); - if ( ok && !item.isEmpty() ) - { - const QStringList parts = item.split( " | " ); - Q_ASSERT( parts.size() == 2 ); - const QString key = parts[1]; - if ( key.contains( "cancelPayment" ) ) - emit transactionCreationFailed(); - else - emit transactionCreationSucceeded( createTestingTransaction( plan, key ) ); - } -#else - emit transactionCreationFailed(); -#endif - } - else if ( mNextResult == NonInteractiveBuyIndividualPlan ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "tier01" ) ); - } - else if ( mNextResult == NonInteractiveBuyProfessionalPlan ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "tier12" ) ); - } - else if ( mNextResult == NonInteractiveSimulateImmediatelyCancelSubscription ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "cancel" ) ); - } - else if ( mNextResult == NonInteractiveSimulateGracePeriod ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "grace" ) ); - } - else if ( mNextResult == NonInteractiveSimulateUnsubscribed ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "unsubscribe" ) ); - } - else if ( mNextResult == NonInteractiveBadReceipt ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "invalidreceipt" ) ); - } - else if ( mNextResult == NonInteractiveUserCancelled ) - { - emit transactionCreationFailed(); - } -} - -void TestingPurchasingBackend::restore() -{ - if ( mMerginApi->subscriptionInfo()->ownsActiveSubscription() ) - { - // we can try to "restore" only recommended plan in test backend - return; - } - - if ( mNextResult == Interactive ) - { -#if defined (HAVE_WIDGETS) - QMessageBox::information( nullptr, "TEST RESTORE", "Test restore individual plan" ); - emit transactionCreationSucceeded( createTestingTransaction( mIndividualPlan, "tier01", true ) ); - return; -#endif - } - emit transactionCreationSucceeded( createTestingTransaction( mIndividualPlan, "tier01", true ) ); -} - -QString TestingPurchasingBackend::subscriptionManageUrl() -{ - return mMerginApi->apiRoot() + "subscription"; -} - -QString TestingPurchasingBackend::subscriptionBillingUrl() -{ - return mMerginApi->apiRoot() + "billing"; -} - -QSharedPointer TestingPurchasingBackend::createTestingTransaction( QSharedPointer plan, const QString &data, bool restore ) -{ - QString planMerginId; - const int id = mMerginApi->subscriptionInfo()->subscriptionId(); - if ( id > 0 ) - { - // this is an existing subscription - planMerginId = QString::number( mMerginApi->subscriptionInfo()->subscriptionId() ); - } - - QString recept = planMerginId + "|" + data; - - PurchasingTransaction::TransactionType type; - restore ? type = PurchasingTransaction::RestoreTransaction : type = PurchasingTransaction::PuchaseTransaction; - - QSharedPointer transaction( new TestingPurchasingTransaction( recept, type, plan ) ); - return transaction; -} diff --git a/app/test/testingpurchasingbackend.h b/app/test/testingpurchasingbackend.h deleted file mode 100644 index 15d92a48e..000000000 --- a/app/test/testingpurchasingbackend.h +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef TESTINGPURCHASINGBACKEND_H -#define TESTINGPURCHASINGBACKEND_H - -#include "purchasing.h" -#include "inputconfig.h" - -#include -#include - -#if defined (HAVE_WIDGETS) -#include -#endif - -class MerginApi; - -/** - * The receipt send to Mergin can trigger either some action to active - * subscription or change the subscription. - * - * Receipt is created in this form: - * "TEST|", - * - where plan_id can be set to -1 (means server please find out my plan_id) - * - and keyword is one of - * tier01 (trial plan active, buy recommended plan) - * tier12 (recommended active, buy upgrade) - * cancel (got refund from provider) - * grace (grace period entered) - * unsubscribed (user chosen not to extend subscription when this one ends) - */ -class TestingPurchasingTransaction: public PurchasingTransaction -{ - public: - TestingPurchasingTransaction( QString receipt, TransactionType type, QSharedPointer plan ); - QString receipt() const override; - MerginSubscriptionType::SubscriptionType provider() const override {return MerginSubscriptionType::TestSubscriptionType; } - - void finalizeTransaction() override {} - - private: - QString mReceipt; -}; - -/** - * Backend usefull for - * GUI testing (select NextPurchaseResult::Interactive) - * Automated tests (select NextPurchaseResult to simulate what next createTransaction() will simulate) - * - * See TestingPurchasingTransaction for description of various transactions. - * - * For the moment, the testing backend does not append plan id to receipt and let the server - * decode - */ -class TestingPurchasingBackend: public PurchasingBackend -{ - Q_OBJECT - public: - TestingPurchasingBackend( MerginApi *api ); - - enum NextPurchaseResult - { - Interactive, - NonInteractiveBuyIndividualPlan, - NonInteractiveBuyProfessionalPlan, - NonInteractiveSimulateImmediatelyCancelSubscription, - NonInteractiveSimulateGracePeriod, - NonInteractiveSimulateUnsubscribed, - NonInteractiveUserCancelled, - NonInteractiveBadReceipt - }; - void setNextPurchaseResult( const NextPurchaseResult expected ); - - void init() override {} - QSharedPointer createPlan( ) override {return QSharedPointer( new PurchasingPlan ); } - void registerPlan( QSharedPointer plan ) override; - void createTransaction( QSharedPointer plan ) override; - void restore() override; - QString subscriptionManageUrl() override; - QString subscriptionBillingUrl() override; - MerginSubscriptionType::SubscriptionType provider() const override { return MerginSubscriptionType::TestSubscriptionType; } - bool userCanMakePayments() const override { return true; } - bool hasManageSubscriptionCapability() const override { return true; } - QString getLocalizedPrice( const QString & ) const override { return ""; } - - void setMerginApi( const QString &url ); - - private: - QSharedPointer createTestingTransaction( QSharedPointer plan, const QString &data, bool restore = false ); - NextPurchaseResult mNextResult = NextPurchaseResult::Interactive; - - MerginApi *mMerginApi = nullptr; - QSharedPointer mIndividualPlan; -}; - -#endif // TESTINGPURCHASINGBACKEND_H diff --git a/app/test/testmerginapi.cpp b/app/test/testmerginapi.cpp index 232ee27c6..c3923f8e8 100644 --- a/app/test/testmerginapi.cpp +++ b/app/test/testmerginapi.cpp @@ -32,14 +32,11 @@ static MerginProject _findProjectByName( const QString &projectNamespace, const } -TestMerginApi::TestMerginApi( MerginApi *api, Purchasing *purchasing ) +TestMerginApi::TestMerginApi( MerginApi *api ) { mApi = api; Q_ASSERT( mApi ); // does not make sense to run without API - mPurchasing = purchasing; - Q_ASSERT( purchasing ); - mSyncManager = std::make_unique( mApi ); mLocalProjectsModel = std::unique_ptr( new ProjectsModel ); @@ -59,11 +56,18 @@ TestMerginApi::~TestMerginApi() = default; void TestMerginApi::initTestCase() { - QString apiRoot, username, password; - TestUtils::mergin_setup_auth( mApi, apiRoot, username, password ); - TestUtils::mergin_setup_pro_subscription( mApi, mPurchasing ); + + QString apiRoot, username, password, workspace; + TestUtils::merginGetAuthCredentials( mApi, apiRoot, username, password ); + if ( TestUtils::needsToAuthorizeAgain( mApi, username ) ) + { + TestUtils::authorizeUser( mApi, username, password ); + TestUtils::selectFirstWorkspace( mApi, workspace ); + } mUsername = username; // keep for later + mWorkspaceName = workspace; // keep for later + qDebug() << "AUTH: username:" << mUsername << ", workspace:" << mWorkspaceName; QDir testDataDir( TEST_DATA_DIR ); mTestDataPath = testDataDir.canonicalPath(); // get rid of any ".." that may cause problems later @@ -79,55 +83,15 @@ void TestMerginApi::initTestCase() // create extra API to do requests we are not testing (as if some other user did those) mLocalProjectsExtra = new LocalProjectsManager( projectsExtraDir ); mApiExtra = new MerginApi( *mLocalProjectsExtra ); + mApiExtra->setApiRoot( mApi->apiRoot() ); - QSignalSpy spyExtra( mApiExtra, &MerginApi::authChanged ); - mApiExtra->authorize( username, password ); - QVERIFY( spyExtra.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spyExtra.count(), 1 ); - - // remove any projects on the server that may prevent us from creating them - deleteRemoteProject( mApiExtra, mUsername, "testListProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testListProjectByName" ); - deleteRemoteProject( mApiExtra, mUsername, "testDownloadProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testDownloadProjectSpecChars" ); - deleteRemoteProject( mApiExtra, mUsername, "testPushAddedFile" ); - deleteRemoteProject( mApiExtra, mUsername, "testPushRemovedFile" ); - deleteRemoteProject( mApiExtra, mUsername, "testPushModifiedFile" ); - deleteRemoteProject( mApiExtra, mUsername, "testPushNoChanges" ); - deleteRemoteProject( mApiExtra, mUsername, "testUpdateAddedFile" ); - deleteRemoteProject( mApiExtra, mUsername, "testUpdateRemovedFiles" ); - deleteRemoteProject( mApiExtra, mUsername, "testUpdateRemovedVsModifiedFiles" ); - deleteRemoteProject( mApiExtra, mUsername, "testConflictRemoteUpdateLocalUpdate" ); - deleteRemoteProject( mApiExtra, mUsername, "testConflictRemoteAddLocalAdd" ); - deleteRemoteProject( mApiExtra, mUsername, "testEditConflictScenario" ); - deleteRemoteProject( mApiExtra, mUsername, "testUploadProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testCancelDownloadProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testCreateProjectTwice" ); - deleteRemoteProject( mApiExtra, mUsername, "testCreateDeleteProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testMultiChunkUploadDownload" ); - deleteRemoteProject( mApiExtra, mUsername, "testEmptyFileUploadDownload" ); - deleteRemoteProject( mApiExtra, mUsername, "testUploadWithUpdate" ); - deleteRemoteProject( mApiExtra, mUsername, "testDiffUpload" ); - deleteRemoteProject( mApiExtra, mUsername, "testDiffSubdirsUpload" ); - deleteRemoteProject( mApiExtra, mUsername, "testDiffUpdateBasic" ); - deleteRemoteProject( mApiExtra, mUsername, "testDiffUpdateWithRebase" ); - deleteRemoteProject( mApiExtra, mUsername, "testDiffUpdateWithRebaseFailed" ); - deleteRemoteProject( mApiExtra, mUsername, "testUpdateWithDiffs" ); - deleteRemoteProject( mApiExtra, mUsername, "testUpdateWithMissedVersion" ); - deleteRemoteProject( mApiExtra, mUsername, "testMigrateProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testMigrateProjectAndSync" ); - deleteRemoteProject( mApiExtra, mUsername, "testMigrateDetachProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSync" ); - deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncSubfolder" ); - deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncAddConfigToExistingProject" ); - deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncRemoveConfig" ); - deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncChangeSyncFolder" ); - deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncDisabledInConfig" ); - deleteRemoteProject( mApiExtra, mUsername, "testSelectiveSyncCorruptedFormat" ); - deleteRemoteProject( mApiExtra, mUsername, "testSynchronizationViaManager" ); - deleteRemoteProject( mApiExtra, mUsername, "testAutosync" ); + if ( TestUtils::needsToAuthorizeAgain( mApiExtra, username ) ) + { + TestUtils::authorizeUser( mApiExtra, username, password ); + mApiExtra->userInfo()->setActiveWorkspace( mApi->userInfo()->activeWorkspaceId() ); + } - deleteLocalDir( mApi, "testExcludeFromSync" ); + // Note: projects on the server are deleted in createRemoteProject function when needed qRegisterMetaType(); } @@ -146,22 +110,24 @@ void TestMerginApi::testListProject() { QString projectName = "testListProject"; + deleteRemoteProjectNow( mApi, mWorkspaceName, projectName ); + // check that there's no testListProject MerginProjectsList projects = getProjectList(); - QVERIFY( !_findProjectByName( mUsername, projectName, projects ).isValid() ); - QVERIFY( !mApi->localProjectsManager().projectFromMerginName( mUsername, projectName ).isValid() ); + QVERIFY( !_findProjectByName( mWorkspaceName, projectName, projects ).isValid() ); + QVERIFY( !mApi->localProjectsManager().projectFromMerginName( mWorkspaceName, projectName ).isValid() ); // create the project on the server (the content is not important) - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/", false ); // check the project exists on the server projects = getProjectList(); - QVERIFY( _findProjectByName( mUsername, projectName, projects ).isValid() ); + QVERIFY( _findProjectByName( mWorkspaceName, projectName, projects ).isValid() ); // project is not available locally, so it has no entry - QVERIFY( !mApi->localProjectsManager().projectFromMerginName( mUsername, projectName ).isValid() ); + QVERIFY( !mApi->localProjectsManager().projectFromMerginName( mWorkspaceName, projectName ).isValid() ); } void TestMerginApi::testListProjectsByName() @@ -169,7 +135,7 @@ void TestMerginApi::testListProjectsByName() QString projectName = "testListProjectByName"; // create the project on the server with other client - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); QByteArray oldToken = mApi->userAuth()->authToken(); @@ -179,7 +145,7 @@ void TestMerginApi::testListProjectsByName() mApi->userAuth()->setTokenExpiration( now ); QStringList projects; - projects.append( MerginApi::getFullProjectName( mUsername, projectName ) ); + projects.append( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QSignalSpy responseReceived( mApi, &MerginApi::listProjectsByNameFinished ); mApi->listProjectsByName( projects ); @@ -201,7 +167,7 @@ void TestMerginApi::testDownloadProject() { // create the project on the server (the content is not important) QString projectName = "testDownloadProject"; - QString projectNamespace = mUsername; + QString projectNamespace = mWorkspaceName; createRemoteProject( mApiExtra, projectNamespace, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); // add an entry about this project to main API - otherwise it fails @@ -217,7 +183,7 @@ void TestMerginApi::testDownloadProject() QCOMPARE( mApi->transactions().count(), 0 ); // check that the local projects are updated - QVERIFY( mApi->localProjectsManager().projectFromMerginName( mUsername, projectName ).isValid() ); + QVERIFY( mApi->localProjectsManager().projectFromMerginName( mWorkspaceName, projectName ).isValid() ); // update model to have latest info refreshProjectsModel( ProjectsModel::LocalProjectsModel ); @@ -247,9 +213,12 @@ void TestMerginApi::testDownloadProjectSpecChars() // Especially testing a name containing "+" sign which was converted into a space when a download query gets to Mergin server // https://doc.qt.io/qt-5/qurlquery.html#handling-of-spaces-and-plus QString projectName = "testDownloadProjectSpecChars"; - QString projectNamespace = mUsername; + QString projectNamespace = mWorkspaceName; QString projectDir = mApi->projectsPath() + "/" + projectName + "/"; + // First remove project on remote server (from previous test runs) + deleteRemoteProjectNow( mApi, projectNamespace, projectName ); + // create an empty project on the server QSignalSpy spy( mApi, &MerginApi::projectCreated ); mApi->createProject( projectNamespace, projectName, true ); @@ -279,7 +248,7 @@ void TestMerginApi::testDownloadProjectSpecChars() QVERIFY( arguments.at( 2 ).toBool() ); // Download project and check if the project file is there - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; QFile projectFileExtra( projectDirExtra + "/" + newProjectFileName ); QVERIFY( projectFileExtra.exists() ); @@ -289,7 +258,7 @@ void TestMerginApi::testCancelDownloadProject() { QString projectName = "testCancelDownloadProject"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TestMerginApi::TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TestMerginApi::TEST_PROJECT_NAME + "/" ); QCOMPARE( mApi->transactions().count(), 0 ); @@ -297,9 +266,9 @@ void TestMerginApi::testCancelDownloadProject() // Test download and cancel before transaction actually starts QSignalSpy spy5( mApi, &MerginApi::syncProjectFinished ); - mApi->pullProject( mUsername, projectName ); + mApi->pullProject( mWorkspaceName, projectName ); QCOMPARE( mApi->transactions().count(), 1 ); - mApi->cancelPull( MerginApi::getFullProjectName( mUsername, projectName ) ); + mApi->cancelPull( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); // no need to wait for the signal here - as we call abort() the reply's finished() signal is immediately emitted QCOMPARE( spy5.count(), 1 ); @@ -313,12 +282,12 @@ void TestMerginApi::testCancelDownloadProject() // Test download and cancel after transcation starts QSignalSpy spy6( mApi, &MerginApi::pullFilesStarted ); - mApi->pullProject( mUsername, projectName ); + mApi->pullProject( mWorkspaceName, projectName ); QVERIFY( spy6.wait( TestUtils::LONG_REPLY ) ); QCOMPARE( spy6.count(), 1 ); QSignalSpy spy7( mApi, &MerginApi::syncProjectFinished ); - mApi->cancelPull( MerginApi::getFullProjectName( mUsername, projectName ) ); + mApi->cancelPull( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); // no need to wait for the signal here - as we call abort() the reply's finished() signal is immediately emitted QCOMPARE( spy7.count(), 1 ); @@ -334,7 +303,10 @@ void TestMerginApi::testCancelDownloadProject() void TestMerginApi::testCreateProjectTwice() { QString projectName = "testCreateProjectTwice"; - QString projectNamespace = mUsername; + QString projectNamespace = mWorkspaceName; + + // First remove project on remote server (from previous test runs) + deleteRemoteProjectNow( mApi, projectNamespace, projectName ); MerginProjectsList projects = getProjectList(); QVERIFY( !_findProjectByName( projectNamespace, projectName, projects ).isValid() ); @@ -364,10 +336,7 @@ void TestMerginApi::testCreateProjectTwice() QCOMPARE( arguments.at( 1 ).toString(), QStringLiteral( "Mergin API error: createProject" ) ); //Clean created project - QSignalSpy spy3( mApi, &MerginApi::serverProjectDeleted ); - mApi->deleteProject( projectNamespace, projectName ); - QVERIFY( spy3.wait( TestUtils::SHORT_REPLY ) ); - QCOMPARE( spy3.takeFirst().at( 1 ).toBool(), true ); + deleteRemoteProjectNow( mApi, projectNamespace, projectName ); projects = getProjectList(); QVERIFY( !_findProjectByName( projectNamespace, projectName, projects ).isValid() ); @@ -377,7 +346,7 @@ void TestMerginApi::testDeleteNonExistingProject() { // Checks if projects doesn't exist QString projectName = "testDeleteNonExistingProject"; - QString projectNamespace = mUsername; + QString projectNamespace = mWorkspaceName; MerginProjectsList projects = getProjectList(); QVERIFY( !_findProjectByName( projectNamespace, projectName, projects ).isValid() ); @@ -396,7 +365,11 @@ void TestMerginApi::testCreateDeleteProject() { // Create a project QString projectName = "testCreateDeleteProject"; - QString projectNamespace = mUsername; + QString projectNamespace = mWorkspaceName; + + // First remove project on remote server (from previous test runs) + deleteRemoteProjectNow( mApi, projectNamespace, projectName ); + MerginProjectsList projects = getProjectList(); QVERIFY( !_findProjectByName( projectNamespace, projectName, projects ).isValid() ); @@ -413,10 +386,7 @@ void TestMerginApi::testCreateDeleteProject() Q_ASSERT( _findProjectByName( projectNamespace, projectName, projects ).isValid() ); // Delete created project - QSignalSpy spy2( mApi, &MerginApi::serverProjectDeleted ); - mApi->deleteProject( projectNamespace, projectName ); - QVERIFY( spy2.wait( TestUtils::SHORT_REPLY ) ); - QCOMPARE( spy2.takeFirst().at( 1 ).toBool(), true ); + deleteRemoteProjectNow( mApi, projectNamespace, projectName ); projects = getProjectList(); QVERIFY( !_findProjectByName( projectNamespace, projectName, projects ).isValid() ); @@ -425,9 +395,12 @@ void TestMerginApi::testCreateDeleteProject() void TestMerginApi::testUploadProject() { QString projectName = "testUploadProject"; - QString projectNamespace = mUsername; + QString projectNamespace = mWorkspaceName; QString projectDir = mApi->projectsPath() + "/" + projectName; + // clean leftovers from previous run first + deleteRemoteProjectNow( mApi, projectNamespace, projectName ); + QSignalSpy spy0( mApiExtra, &MerginApi::projectCreated ); mApiExtra->createProject( projectNamespace, projectName, true ); QVERIFY( spy0.wait( TestUtils::LONG_REPLY ) ); @@ -522,9 +495,9 @@ void TestMerginApi::testMultiChunkUploadDownload() QString projectName = "testMultiChunkUploadDownload"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // create a big file (21mb) QString bigFilePath = mApi->projectsPath() + "/" + projectName + "/" + "big_file.dat"; @@ -538,12 +511,12 @@ void TestMerginApi::testMultiChunkUploadDownload() QVERIFY( !checksum.isEmpty() ); // upload - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // download again - deleteLocalProject( mApi, mUsername, projectName ); + deleteLocalProject( mApi, mWorkspaceName, projectName ); QVERIFY( !QFileInfo::exists( bigFilePath ) ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // verify it's there and with correct content QByteArray checksum2 = CoreUtils::calculateChecksum( bigFilePath ); @@ -557,9 +530,9 @@ void TestMerginApi::testEmptyFileUploadDownload() QString projectName = QStringLiteral( "testEmptyFileUploadDownload" ); - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QString emptyFileDestinationPath = mApi->projectsPath() + "/" + projectName + "/" + TEST_EMPTY_FILE_NAME; @@ -571,12 +544,12 @@ void TestMerginApi::testEmptyFileUploadDownload() QVERIFY( !checksum.isEmpty() ); //upload - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); //download again - deleteLocalProject( mApi, mUsername, projectName ); + deleteLocalProject( mApi, mWorkspaceName, projectName ); QVERIFY( !QFileInfo::exists( emptyFileDestinationPath ) ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // verify it's there and with correct content QByteArray checksum2 = CoreUtils::calculateChecksum( emptyFileDestinationPath ); @@ -588,12 +561,12 @@ void TestMerginApi::testPushAddedFile() { QString projectName = "testPushAddedFile"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project0 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project0 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project0.isLocal() && project0.isMergin() ); QCOMPARE( project0.local.localVersion, 1 ); QCOMPARE( project0.mergin.serverVersion, 1 ); @@ -609,26 +582,26 @@ void TestMerginApi::testPushAddedFile() // check that the status is "modified" refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); // force update of status - Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project1.isLocal() && project1.isMergin() ); QCOMPARE( project1.local.localVersion, 1 ); QCOMPARE( project1.mergin.serverVersion, 1 ); QCOMPARE( project1.mergin.status, ProjectStatus::NeedsSync ); // upload - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project2.isLocal() && project2.isMergin() ); QCOMPARE( project2.local.localVersion, 2 ); QCOMPARE( project2.mergin.serverVersion, 2 ); QCOMPARE( project2.mergin.status, ProjectStatus::UpToDate ); - deleteLocalProject( mApi, mUsername, projectName ); + deleteLocalProject( mApi, mWorkspaceName, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project3 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project3 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project3.isLocal() && project3.isMergin() ); QCOMPARE( project3.local.localVersion, 2 ); QCOMPARE( project3.mergin.serverVersion, 2 ); @@ -646,12 +619,12 @@ void TestMerginApi::testPushRemovedFile() QString projectName = "testPushRemovedFile"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project0 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project0 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project0.isLocal() && project0.isMergin() ); QCOMPARE( project0.local.localVersion, 1 ); QCOMPARE( project0.mergin.serverVersion, 1 ); @@ -667,7 +640,7 @@ void TestMerginApi::testPushRemovedFile() // check that it is considered as modified now refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); // force update of status - Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project1.isLocal() && project1.isMergin() ); QCOMPARE( project1.local.localVersion, 1 ); QCOMPARE( project1.mergin.serverVersion, 1 ); @@ -675,19 +648,19 @@ void TestMerginApi::testPushRemovedFile() // upload changes - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project2.isLocal() && project2.isMergin() ); QCOMPARE( project2.local.localVersion, 2 ); QCOMPARE( project2.mergin.serverVersion, 2 ); QCOMPARE( project2.mergin.status, ProjectStatus::UpToDate ); - deleteLocalProject( mApi, mUsername, projectName ); + deleteLocalProject( mApi, mWorkspaceName, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project3 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project3 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project3.isLocal() && project3.isMergin() ); QCOMPARE( project3.local.localVersion, 2 ); QCOMPARE( project3.mergin.serverVersion, 2 ); @@ -705,10 +678,10 @@ void TestMerginApi::testPushModifiedFile() { QString projectName = "testPushModifiedFile"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // need to sleep at least for a second so that last modified time // has later timestamp than the last sync (seems there's one second resolution) @@ -723,16 +696,16 @@ void TestMerginApi::testPushModifiedFile() // check that the status is "modified" refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); // force update of status - Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project1.isLocal() && project1.isMergin() ); QCOMPARE( project1.local.localVersion, 1 ); QCOMPARE( project1.mergin.serverVersion, 1 ); QCOMPARE( project1.mergin.status, ProjectStatus::NeedsSync ); // upload - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project2.isLocal() && project2.isMergin() ); QCOMPARE( project2.local.localVersion, 2 ); QCOMPARE( project2.mergin.serverVersion, 2 ); @@ -740,13 +713,13 @@ void TestMerginApi::testPushModifiedFile() // verify the remote project has updated file - deleteLocalProject( mApi, mUsername, projectName ); + deleteLocalProject( mApi, mWorkspaceName, projectName ); QVERIFY( !file.open( QIODevice::ReadOnly ) ); // it should not exist at this point - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project3 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project3 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project3.isLocal() && project3.isMergin() ); QCOMPARE( project3.local.localVersion, 2 ); QCOMPARE( project3.mergin.serverVersion, 2 ); @@ -762,23 +735,23 @@ void TestMerginApi::testPushNoChanges() QString projectName = "testPushNoChanges"; QString projectDir = mApi->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // check that the status is still "up-to-date" - Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project1.isLocal() && project1.isMergin() ); QCOMPARE( project1.local.localVersion, 1 ); QCOMPARE( project1.mergin.serverVersion, 1 ); QCOMPARE( project1.mergin.status, ProjectStatus::UpToDate ); // upload - should do nothing - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project2.isLocal() && project2.isMergin() ); QCOMPARE( project2.local.localVersion, 1 ); QCOMPARE( project2.mergin.serverVersion, 1 ); @@ -797,38 +770,38 @@ void TestMerginApi::testUpdateAddedFile() QString projectDir = mApi->projectsPath() + "/" + projectName; QString extraProjectDir = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); // download initial version - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( !QFile::exists( projectDir + "/test-remote-new.txt" ) ); - Project project0 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project0 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project0.isLocal() && project0.isMergin() ); QCOMPARE( project0.local.localVersion, 1 ); QCOMPARE( project0.mergin.serverVersion, 1 ); QCOMPARE( project0.mergin.status, ProjectStatus::UpToDate ); // remove a file on the server - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); writeFileContent( extraProjectDir + "/test-remote-new.txt", QByteArray( "my new content" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QVERIFY( QFile::exists( extraProjectDir + "/test-remote-new.txt" ) ); // list projects - just so that we can figure out we are behind refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); - Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project1.isLocal() && project1.isMergin() ); QCOMPARE( project1.local.localVersion, 1 ); QCOMPARE( project1.mergin.serverVersion, 2 ); QCOMPARE( project1.mergin.status, ProjectStatus::NeedsSync ); // now try to update - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project2 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project2.isLocal() && project2.isMergin() ); QCOMPARE( project2.local.localVersion, 2 ); QCOMPARE( project2.mergin.serverVersion, 2 ); @@ -850,19 +823,19 @@ void TestMerginApi::testUpdateRemovedFiles() QString projectDir = mApi->projectsPath() + "/" + projectName; QString extraProjectDir = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); // download initial version - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFile::exists( projectDir + "/test1.txt" ) ); // remove a file on the server - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QVERIFY( QFile::remove( extraProjectDir + "/test1.txt" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // now try to update - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // check that the removed file is not there anymore QVERIFY( QFile::exists( projectDir + "/project.qgs" ) ); @@ -879,16 +852,16 @@ void TestMerginApi::testUpdateRemovedVsModifiedFiles() QString projectDir = mApi->projectsPath() + "/" + projectName; QString extraProjectDir = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); // download initial version - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFile::exists( projectDir + "/test1.txt" ) ); // remove a file on the server - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QVERIFY( QFile::remove( extraProjectDir + "/test1.txt" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // modify the same file locally QFile file( projectDir + "/test1.txt" ); @@ -897,7 +870,7 @@ void TestMerginApi::testUpdateRemovedVsModifiedFiles() file.close(); // now try to update - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // check that the file removed on the server is still there, with modified content QVERIFY( QFile::exists( projectDir + "/project.qgs" ) ); @@ -920,15 +893,15 @@ void TestMerginApi::testConflictRemoteUpdateLocalUpdate() QString filename = projectDir + "/test1.txt"; QString extraFilename = extraProjectDir + "/test1.txt"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); qDebug() << "download initial version"; - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); qDebug() << "modify test1.txt on the server"; - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); writeFileContent( extraFilename, QByteArray( "remote content" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); qDebug() << "modify test1.txt locally and do the sync"; writeFileContent( filename, QByteArray( "local content" ) ); @@ -938,8 +911,8 @@ void TestMerginApi::testConflictRemoteUpdateLocalUpdate() // out... in upload's project info handler if there is a need for update, // the upload should be cancelled (or paused to update first). // - downloadRemoteProject( mApi, mUsername, projectName ); - uploadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // verify the result: the server version should be in test1.txt // and the local version should go to "test1 (conflicted copy, v).txt" @@ -949,9 +922,9 @@ void TestMerginApi::testConflictRemoteUpdateLocalUpdate() // Second conflict qDebug() << "modify test1.txt on the server"; - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); writeFileContent( extraFilename, QByteArray( "remote content 2" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); qDebug() << "modify test1.txt locally and do the sync"; writeFileContent( filename, QByteArray( "local content 2" ) ); @@ -961,8 +934,8 @@ void TestMerginApi::testConflictRemoteUpdateLocalUpdate() // out... in upload's project info handler if there is a need for update, // the upload should be cancelled (or paused to update first). // - downloadRemoteProject( mApi, mUsername, projectName ); - uploadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // verify the result: the server version should be in test1.txt // and the local version should go to "test1 (conflicted copy, v).txt" @@ -986,15 +959,15 @@ void TestMerginApi::testConflictRemoteAddLocalAdd() QString filename = projectDir + "/test-new-file.txt"; QString extraFilename = extraProjectDir + "/test-new-file.txt"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); qDebug() << "download initial version"; - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); qDebug() << "create test-new-file.txt on the server"; - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); writeFileContent( extraFilename, QByteArray( "new remote content" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); qDebug() << "create test-new-file.txt locally and do the sync"; writeFileContent( filename, QByteArray( "new local content" ) ); @@ -1004,8 +977,8 @@ void TestMerginApi::testConflictRemoteAddLocalAdd() // out... in upload's project info handler if there is a need for update, // the upload should be cancelled (or paused to update first). // - downloadRemoteProject( mApi, mUsername, projectName ); - uploadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // verify the result: the server version should be in test1.txt // and the local version should go to conflicted copy file @@ -1027,22 +1000,22 @@ void TestMerginApi::testEditConflictScenario() // folder rebase_edit_conflict QString dataProjectDir = TestUtils::testDataDir() + "/" + QStringLiteral( "rebase_edit_conflict" ); qDebug() << "About to create the project"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); qDebug() << "Project has been created!"; QString dbName = QStringLiteral( "data.gpkg" ); QString baseDB = dataProjectDir + QStringLiteral( "/base.gpkg" ); QString localChangeDB = dataProjectDir + QStringLiteral( "/local-change.gpkg" ); QString remoteChangeDB = dataProjectDir + QStringLiteral( "/remote-change.gpkg" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // upload base db InputUtils::copyFile( baseDB, projectDir + "/" + dbName ); - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // both clients now sync the project so that both of them have base gpkg - downloadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // both clients now make change to the same field InputUtils::removeFile( projectDir + "/" + dbName ); @@ -1051,13 +1024,13 @@ void TestMerginApi::testEditConflictScenario() InputUtils::copyFile( remoteChangeDB, extraProjectDir + "/" + dbName ); // client B syncs his changes - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // // now client A syncs, resulting in edit conflict // - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); QDir projDir( projectDir ); @@ -1065,7 +1038,7 @@ void TestMerginApi::testEditConflictScenario() QVERIFY( InputUtils::fileExists( projectDir + "/" + QString( "data (edit conflict, %1 v2).json" ).arg( mUsername ) ) ); // when client B downloads changes, he should also have that edit conflict file - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QVERIFY( InputUtils::fileExists( projectDir + "/" + QString( "data (edit conflict, %1 v2).json" ).arg( mUsername ) ) ); } @@ -1081,28 +1054,28 @@ void TestMerginApi::testUploadWithUpdate() QString filenameRemote = projectDir + "/test-new-remote-file.txt"; QString extraFilenameRemote = extraProjectDir + "/test-new-remote-file.txt"; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); refreshProjectsModel( ProjectsModel::CreatedProjectsModel ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); writeFileContent( extraFilenameRemote, QByteArray( "new remote content" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); writeFileContent( filenameLocal, QByteArray( "new local content" ) ); qDebug() << "now do both update + upload"; - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); QCOMPARE( readFileContent( filenameLocal ), QByteArray( "new local content" ) ); QCOMPARE( readFileContent( filenameRemote ), QByteArray( "new remote content" ) ); // try to re-download the project and see if everything went fine - deleteLocalProject( mApi, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); + deleteLocalProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); - Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mUsername, projectName ) ); + Project project1 = mCreatedProjectsModel->projectFromId( MerginApi::getFullProjectName( mWorkspaceName, projectName ) ); QVERIFY( project1.isLocal() && project1.isMergin() ); QCOMPARE( project1.local.localVersion, 3 ); QCOMPARE( project1.mergin.serverVersion, 3 ); @@ -1117,9 +1090,9 @@ void TestMerginApi::testDiffUpload() QString projectName = "testDiffUpload"; QString projectDir = mApi->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + "diff_project" + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + "diff_project" + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); @@ -1142,7 +1115,7 @@ void TestMerginApi::testDiffUpload() GeodiffUtils::ChangesetSummary summary = GeodiffUtils::parseChangesetSummary( changes ); QCOMPARE( summary, expectedSummary ); - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); @@ -1153,9 +1126,9 @@ void TestMerginApi::testDiffSubdirsUpload() QString projectName = "testDiffSubdirsUpload"; QString projectDir = mApi->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + "diff_project_subs" + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + "diff_project_subs" + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); const QString base( "subdir/subsubdir/base.gpkg" ); QVERIFY( QFileInfo::exists( projectDir + "/.mergin/" + base ) ); @@ -1179,7 +1152,7 @@ void TestMerginApi::testDiffSubdirsUpload() GeodiffUtils::ChangesetSummary summary = GeodiffUtils::parseChangesetSummary( changes ); QCOMPARE( summary, expectedSummary ); - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected QVERIFY( !MerginApi::hasLocalProjectChanges( projectDir ) ); @@ -1194,9 +1167,9 @@ void TestMerginApi::testDiffUpdateBasic() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + "diff_project" + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + "diff_project" + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected @@ -1211,14 +1184,14 @@ void TestMerginApi::testDiffUpdateBasic() // download with mApiExtra + modify + upload // - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); bool r0 = QFile::remove( projectDirExtra + "/base.gpkg" ); bool r1 = QFile::copy( mTestDataPath + "/added_row.gpkg", projectDirExtra + "/base.gpkg" ); QVERIFY( r0 && r1 ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // update our local version now - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // // check the result @@ -1244,9 +1217,9 @@ void TestMerginApi::testDiffUpdateWithRebase() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + "diff_project" + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + "diff_project" + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected @@ -1256,11 +1229,11 @@ void TestMerginApi::testDiffUpdateWithRebase() // download with mApiExtra + modify + upload // - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); bool r0 = QFile::remove( projectDirExtra + "/base.gpkg" ); bool r1 = QFile::copy( mTestDataPath + "/added_row.gpkg", projectDirExtra + "/base.gpkg" ); QVERIFY( r0 && r1 ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // // do a local update of the file @@ -1294,7 +1267,7 @@ void TestMerginApi::testDiffUpdateWithRebase() QCOMPARE( summary, expectedSummary ); // update our local version now - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // // check the result @@ -1323,9 +1296,9 @@ void TestMerginApi::testDiffUpdateWithRebaseFailed() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + "diff_project" + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + "diff_project" + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected @@ -1335,11 +1308,11 @@ void TestMerginApi::testDiffUpdateWithRebaseFailed() // download with mApiExtra + modify + upload // - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); bool r0 = QFile::remove( projectDirExtra + "/base.gpkg" ); bool r1 = QFile::copy( mTestDataPath + "/added_row.gpkg", projectDirExtra + "/base.gpkg" ); QVERIFY( r0 && r1 ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // // do a local update of the file @@ -1364,11 +1337,11 @@ void TestMerginApi::testDiffUpdateWithRebaseFailed() QSignalSpy spy( mApi, &MerginApi::projectReloadNeededAfterSync ); // update our local version now - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // check that projectReloadNeededAfterSync is emited and has correct argument QCOMPARE( spy.count(), 1 ); - QCOMPARE( spy.takeFirst().at( 0 ).toString(), mUsername + "/" + projectName ); + QCOMPARE( spy.takeFirst().at( 0 ).toString(), mWorkspaceName + "/" + projectName ); // // check the result @@ -1393,9 +1366,9 @@ void TestMerginApi::testUpdateWithDiffs() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + "diff_project" + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + "diff_project" + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFileInfo::exists( projectDir + "/.mergin/base.gpkg" ) ); QCOMPARE( MerginApi::localProjectChanges( projectDir ), ProjectDiff() ); // no local changes expected @@ -1405,24 +1378,24 @@ void TestMerginApi::testUpdateWithDiffs() // download with mApiExtra + modify + upload // - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); bool r0 = QFile::remove( projectDirExtra + "/base.gpkg" ); bool r1 = QFile::copy( mTestDataPath + "/added_row.gpkg", projectDirExtra + "/base.gpkg" ); QVERIFY( r0 && r1 ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // one more change + upload bool r2 = QFile::remove( projectDirExtra + "/base.gpkg" ); bool r3 = QFile::copy( mTestDataPath + "/added_row_2.gpkg", projectDirExtra + "/base.gpkg" ); QVERIFY( r2 && r3 ); writeFileContent( projectDirExtra + "/dummy.txt", "first" ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // // now update project locally // - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QgsVectorLayer *vl = new QgsVectorLayer( projectDir + "/base.gpkg|layername=simple", "base", "ogr" ); QVERIFY( vl->isValid() ); @@ -1451,20 +1424,20 @@ void TestMerginApi::testUpdateWithMissedVersion() QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; // step 1 - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + "diff_project" + "/" ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + "diff_project" + "/" ); // step 2 - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); writeFileContent( projectDirExtra + "/file1.txt", QByteArray( "hello" ) ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // step 3 - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // step 4 bool r0 = QFile::remove( projectDirExtra + "/base.gpkg" ); bool r1 = QFile::copy( mTestDataPath + "/added_row.gpkg", projectDirExtra + "/base.gpkg" ); QVERIFY( r0 && r1 ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // step 5 - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // check that added row in v3 has been added in our local file too QgsVectorLayer *vl = new QgsVectorLayer( projectDir + "/base.gpkg|layername=simple", "base", "ogr" ); @@ -1476,6 +1449,10 @@ void TestMerginApi::testUpdateWithMissedVersion() void TestMerginApi::testMigrateProject() { QString projectName = "testMigrateProject"; + + // clean leftovers from previous tests + deleteRemoteProjectNow( mApi, mWorkspaceName, projectName ); + // make local copy of project QString projectDir = mApi->projectsPath() + "/" + projectName; createLocalProject( projectDir ); @@ -1488,20 +1465,20 @@ void TestMerginApi::testMigrateProject() QSignalSpy spy( mApi, &MerginApi::projectCreated ); QSignalSpy spy2( mApi, &MerginApi::syncProjectFinished ); - mApi->migrateProjectToMergin( projectName ); + mApi->migrateProjectToMergin( projectName, mWorkspaceName ); - QVERIFY( spy.wait( TestUtils::SHORT_REPLY ) ); + QVERIFY( spy.wait( TestUtils::LONG_REPLY ) ); QCOMPARE( spy.count(), 1 ); QCOMPARE( spy.takeFirst().at( 1 ).toBool(), true ); QCOMPARE( mApi->transactions().count(), 1 ); QVERIFY( spy2.wait( TestUtils::LONG_REPLY * 5 ) ); // remove local copy of project - deleteLocalProject( mApi, mUsername, projectName ); + deleteLocalProject( mApi, mWorkspaceName, projectName ); QVERIFY( !QFileInfo::exists( projectDir ) ); // download the project - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // verify that all files have been uploaded QStringList entryList2 = QDir( projectDir ).entryList( QDir::NoDotAndDotDot | QDir::Dirs ); @@ -1512,7 +1489,7 @@ void TestMerginApi::testMigrateProjectAndSync() { // When a new project is migrated to Mergin, creating basefiles for diffable files was omitted. // Therefore sync was not properly working resulting into having a conflict file. - // Test covers creting a new project, migrating it to Mergin and both sides sync. + // Test covers creating a new project, migrating it to Mergin and both sides sync. // 1. [main] create project with .gpkg (v1) file // 2. [main] migrate the project to mergin @@ -1525,6 +1502,9 @@ void TestMerginApi::testMigrateProjectAndSync() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; + // clean leftovers from previous tests + deleteRemoteProjectNow( mApi, mWorkspaceName, projectName ); + // step 1 createLocalProject( projectDir ); mApi->mLocalProjects.reloadDataDir(); @@ -1532,23 +1512,23 @@ void TestMerginApi::testMigrateProjectAndSync() QSignalSpy spy( mApi, &MerginApi::projectCreated ); QSignalSpy spy2( mApi, &MerginApi::syncProjectFinished ); - mApi->migrateProjectToMergin( projectName ); + mApi->migrateProjectToMergin( projectName, mWorkspaceName ); - QVERIFY( spy.wait( TestUtils::SHORT_REPLY ) ); + QVERIFY( spy.wait( TestUtils::LONG_REPLY ) ); QCOMPARE( spy.count(), 1 ); QCOMPARE( spy.takeFirst().at( 1 ).toBool(), true ); QCOMPARE( mApi->transactions().count(), 1 ); QVERIFY( spy2.wait( TestUtils::LONG_REPLY * 5 ) ); // step 3 - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); bool r0 = QFile::remove( projectDirExtra + "/base.gpkg" ); bool r1 = QFile::copy( mTestDataPath + "/added_row.gpkg", projectDirExtra + "/base.gpkg" ); QVERIFY( r0 && r1 ); - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // step 4 - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( QFile( projectDir + "/base.gpkg" ).exists() ); QStringList projectMerginDirEntries = QDir( projectDir + "/.mergin" ).entryList( QDir::AllEntries | QDir::NoDotAndDotDot ); for ( QString filepath : projectMerginDirEntries ) @@ -1560,10 +1540,10 @@ void TestMerginApi::testMigrateProjectAndSync() r0 = QFile::remove( projectDir + "/base.gpkg" ); r1 = QFile::copy( mTestDataPath + "/added_row_2.gpkg", projectDir + "/base.gpkg" ); QVERIFY( r0 && r1 ); - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // step 6 - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QVERIFY( QFile( projectDir + "/base.gpkg" ).exists() ); QStringList projectMerginDirExtraEntries = QDir( projectDirExtra + "/.mergin" ).entryList( QDir::AllEntries | QDir::NoDotAndDotDot ); for ( QString filepath : projectMerginDirExtraEntries ) @@ -1575,6 +1555,10 @@ void TestMerginApi::testMigrateProjectAndSync() void TestMerginApi::testMigrateDetachProject() { QString projectName = "testMigrateDetachProject"; + + // clean leftovers from previous tests + deleteRemoteProjectNow( mApi, mWorkspaceName, projectName ); + // make local copy of project QString projectDir = mApi->projectsPath() + "/" + projectName; createLocalProject( projectDir ); @@ -1586,9 +1570,9 @@ void TestMerginApi::testMigrateDetachProject() QSignalSpy spy( mApi, &MerginApi::projectCreated ); QSignalSpy spy2( mApi, &MerginApi::syncProjectFinished ); - mApi->migrateProjectToMergin( projectName ); + mApi->migrateProjectToMergin( projectName, mWorkspaceName ); - QVERIFY( spy.wait( TestUtils::SHORT_REPLY ) ); + QVERIFY( spy.wait( TestUtils::LONG_REPLY ) ); QCOMPARE( spy.count(), 1 ); QCOMPARE( spy.takeFirst().at( 1 ).toBool(), true ); QCOMPARE( mApi->transactions().count(), 1 ); @@ -1598,7 +1582,7 @@ void TestMerginApi::testMigrateDetachProject() QVERIFY( QFileInfo::exists( projectDir + "/.mergin/" ) ); // detach project - QString projectNamespace = mUsername; + QString projectNamespace = mWorkspaceName; mApi->detachProjectFromMergin( projectNamespace, projectName ); // TEST if is NOT mergin project QVERIFY( !QFileInfo::exists( projectDir + "/.mergin/" ) ); @@ -1615,8 +1599,8 @@ void TestMerginApi::testSelectiveSync() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApiExtra, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + createRemoteProject( mApiExtra, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Create photo files QDir dir; @@ -1631,18 +1615,18 @@ void TestMerginApi::testSelectiveSync() file1.open( QIODevice::WriteOnly ); // Download the project and copy mergin config file containing selective sync properties - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QString configFilePathExtra( projectDirExtra + "/mergin-config.json" ); QVERIFY( QFile::copy( mTestDataPath + "/mergin-config-project-dir.json", configFilePathExtra ) ); // Upload config file - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // Sync event 1: // Client 1 uploads images - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // Download project and check - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QFile fileExtra( projectDirExtra + "/photo.jpg" ); QVERIFY( !fileExtra.exists() ); @@ -1657,10 +1641,10 @@ void TestMerginApi::testSelectiveSync() fileExtra2.open( QIODevice::WriteOnly ); // Client 2 uploads a new image - uploadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // Client 1 syncs without Client 2's new image and without removing own images. - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QVERIFY( file.exists() ); QVERIFY( file1.exists() ); @@ -1691,8 +1675,8 @@ void TestMerginApi::testSelectiveSyncSubfolder() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApi, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + createRemoteProject( mApi, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Create photo files QDir dir; @@ -1713,10 +1697,10 @@ void TestMerginApi::testSelectiveSyncSubfolder() QVERIFY( QFile::copy( mTestDataPath + "/mergin-config-subfolder.json", configFilePath ) ); // Upload project - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); // Client 2 in Action 1: should download project without images in subfolder "photos" - downloadRemoteProject( mApiExtra, mUsername, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QString photoPathExtra( projectDirExtra + "/photos" ); @@ -1744,8 +1728,8 @@ void TestMerginApi::testSelectiveSyncSubfolder() extraRootFile.close(); // Client 2 uploads, Client 1 downloads - uploadRemoteProject( mApiExtra, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Check existence of "photoD" in root and not existence of photoC in "photos" QFile fileExtra2( photoPath + "/" + "photoC.jpg" ); @@ -1777,8 +1761,8 @@ void TestMerginApi::testSelectiveSyncAddConfigToExistingProject() QString projectDir = mApi->projectsPath() + "/" + projectName; QString projectDirExtra = mApiExtra->projectsPath() + "/" + projectName; - createRemoteProject( mApi, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + createRemoteProject( mApi, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Create photo files QDir dir; @@ -1795,8 +1779,8 @@ void TestMerginApi::testSelectiveSyncAddConfigToExistingProject() file1.close(); // Sync project for both clients, Client 2 should have both pictures - uploadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QString photoPathExtra( projectDirExtra + "/photos" ); @@ -1815,8 +1799,8 @@ void TestMerginApi::testSelectiveSyncAddConfigToExistingProject() file2.close(); // Sync project for both clients - uploadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // With mergin-config, "photoC" should not exist for Client 2 QFile fileExtra2( photoPathExtra + "/" + "photoC.png" ); @@ -1848,8 +1832,8 @@ void TestMerginApi::testSelectiveSyncRemoveConfig() QString projectClient2 = mApiExtra->projectsPath() + "/" + projectName; QString projectServer = serverMirror->projectsPath() + "/" + projectName; - createRemoteProject( mApi, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + createRemoteProject( mApi, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Create photo files QDir dir; @@ -1865,8 +1849,8 @@ void TestMerginApi::testSelectiveSyncRemoveConfig() file1.open( QIODevice::WriteOnly ); file1.close(); - uploadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); QString configFilePath = projectServer + "/" + "mergin-config.json"; QVERIFY( createJsonFile( configFilePath, @@ -1875,8 +1859,8 @@ void TestMerginApi::testSelectiveSyncRemoveConfig() { "input-selective-sync-dir", "photos" } } ) ); - uploadRemoteProject( serverMirror, mUsername, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QString photoPathClient2( projectClient2 + "/photos" ); @@ -1895,25 +1879,25 @@ void TestMerginApi::testSelectiveSyncRemoveConfig() file2.open( QIODevice::WriteOnly ); file2.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); // Let's remove mergin config InputUtils::removeFile( configFilePath ); QVERIFY( !InputUtils::fileExists( configFilePath ) ); // Sync removed config - uploadRemoteProject( serverMirror, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); // download back to apply the changes -> should download photos + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // download back to apply the changes -> should download photos QFile fextra( photoPathClient2 + "/" + "photoC2-extra.png" ); fextra.open( QIODevice::WriteOnly ); fextra.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QString photoPathServer = serverMirrorDataPath + "/" + projectName + "/" + "photos"; @@ -1959,8 +1943,8 @@ void TestMerginApi::testSelectiveSyncDisabledInConfig() QString projectClient2 = mApiExtra->projectsPath() + "/" + projectName; QString projectServer = serverMirror->projectsPath() + "/" + projectName; - createRemoteProject( mApi, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + createRemoteProject( mApi, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Create photo files QDir dir; @@ -1976,8 +1960,8 @@ void TestMerginApi::testSelectiveSyncDisabledInConfig() file1.open( QIODevice::WriteOnly ); file1.close(); - uploadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); QString configFilePath = projectServer + "/" + "mergin-config.json"; QVERIFY( createJsonFile( configFilePath, @@ -1986,8 +1970,8 @@ void TestMerginApi::testSelectiveSyncDisabledInConfig() { "input-selective-sync-dir", "photos" } } ) ); - uploadRemoteProject( serverMirror, mUsername, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); QString photoPathClient2( projectClient2 + "/photos" ); @@ -2012,11 +1996,11 @@ void TestMerginApi::testSelectiveSyncDisabledInConfig() f2.open( QIODevice::WriteOnly ); f2.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); } - downloadRemoteProject( serverMirror, mUsername, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); // Let's disable selective sync InputUtils::removeFile( configFilePath ); @@ -2029,16 +2013,16 @@ void TestMerginApi::testSelectiveSyncDisabledInConfig() } ) ); // Sync changed config - uploadRemoteProject( serverMirror, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); // download back to apply the changes -> should download photos + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // download back to apply the changes -> should download photos QFile fextra( photoPathClient2 + "/" + "photoC2-extra.png" ); fextra.open( QIODevice::WriteOnly ); fextra.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); // check that all clients have photos QStringList photos; @@ -2062,15 +2046,15 @@ void TestMerginApi::testSelectiveSyncDisabledInConfig() { "input-selective-sync-dir", "photos" } } ) ); - uploadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); QFile f( photoPathClient2 + "/" + "photoC2-should-not-download.png" ); f.open( QIODevice::WriteOnly ); f.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); // File should be on server mirror and should not be on client 1 QFile fverify( serverMirrorDataPath + "/" + projectName + "/" + "photos" + "/" + "photoC2-should-not-download.png" ); @@ -2107,8 +2091,8 @@ void TestMerginApi::testSelectiveSyncChangeSyncFolder() QString projectClient2 = mApiExtra->projectsPath() + "/" + projectName; QString projectServer = serverMirror->projectsPath() + "/" + projectName; - createRemoteProject( mApi, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + createRemoteProject( mApi, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Create photo files QDir dir; @@ -2124,8 +2108,8 @@ void TestMerginApi::testSelectiveSyncChangeSyncFolder() file1.open( QIODevice::WriteOnly ); file1.close(); - uploadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); QString configFilePath = projectServer + "/" + "mergin-config.json"; QVERIFY( createJsonFile( configFilePath, @@ -2134,8 +2118,8 @@ void TestMerginApi::testSelectiveSyncChangeSyncFolder() { "input-selective-sync-dir", "" } } ) ); - uploadRemoteProject( serverMirror, mUsername, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // client 2 adds photos to project root, client 1 to photos subfolder QString photoPathClient2( projectClient2 ); @@ -2161,11 +2145,11 @@ void TestMerginApi::testSelectiveSyncChangeSyncFolder() f2.open( QIODevice::WriteOnly ); f2.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - uploadRemoteProject( mApi, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); } - downloadRemoteProject( serverMirror, mUsername, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); // Let's change selective sync folder only to photos subfolder InputUtils::removeFile( configFilePath ); @@ -2178,18 +2162,18 @@ void TestMerginApi::testSelectiveSyncChangeSyncFolder() } ) ); // Sync changed config - uploadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); // Client 1 should now download all missing files from project root directory - downloadRemoteProject( mApi, mUsername, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); QFile fextra( photoPathClient2 + "/" + "photoC2-extra.png" ); fextra.open( QIODevice::WriteOnly ); fextra.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); /* * Check that: @@ -2232,15 +2216,15 @@ void TestMerginApi::testSelectiveSyncChangeSyncFolder() { "input-selective-sync-dir", "" } } ) ); - uploadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); QFile f( photoPathClient2 + "/" + "photoC2-should-not-download.png" ); f.open( QIODevice::WriteOnly ); f.close(); - uploadRemoteProject( mApiExtra, mUsername, projectName ); - downloadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApiExtra, mWorkspaceName, projectName ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); // File should be on server mirror and should not be on client 1 QFile fverify( serverMirrorProjectPath + "/" + "photoC2-should-not-download.png" ); @@ -2278,8 +2262,8 @@ void TestMerginApi::testSelectiveSyncCorruptedFormat() QString projectClient2 = mApiExtra->projectsPath() + "/" + projectName; QString projectServer = serverMirror->projectsPath() + "/" + projectName; - createRemoteProject( mApi, mUsername, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectName ); + createRemoteProject( mApi, mWorkspaceName, projectName, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectName ); // Create photo files QDir dir; @@ -2295,15 +2279,15 @@ void TestMerginApi::testSelectiveSyncCorruptedFormat() file1.open( QIODevice::WriteOnly ); file1.close(); - uploadRemoteProject( mApi, mUsername, projectName ); - downloadRemoteProject( serverMirror, mUsername, projectName ); + uploadRemoteProject( mApi, mWorkspaceName, projectName ); + downloadRemoteProject( serverMirror, mWorkspaceName, projectName ); // add corrupted config file QString configFilePath = projectServer + "/" + "mergin-config.json"; QVERIFY( QFile::copy( mTestDataPath + "/mergin-config-corrupted.json", configFilePath ) ); - uploadRemoteProject( serverMirror, mUsername, projectName ); - downloadRemoteProject( mApiExtra, mUsername, projectName ); + uploadRemoteProject( serverMirror, mWorkspaceName, projectName ); + downloadRemoteProject( mApiExtra, mWorkspaceName, projectName ); // client 2 should have all photos from client 1 QString photoPathClient2( projectClient2 + "/" + "photos" ); @@ -2328,14 +2312,14 @@ void TestMerginApi::testSynchronizationViaManager() SynchronizationManager syncmanager( mApi ); QString projectname( QStringLiteral( "testSynchronizationViaManager" ) ); - QString projectfullname = MerginApi::getFullProjectName( mUsername, projectname ); + QString projectfullname = MerginApi::getFullProjectName( mWorkspaceName, projectname ); - createRemoteProject( mApiExtra, mUsername, projectname, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); - downloadRemoteProject( mApi, mUsername, projectname ); + createRemoteProject( mApiExtra, mWorkspaceName, projectname, mTestDataPath + "/" + TEST_PROJECT_NAME + "/" ); + downloadRemoteProject( mApi, mWorkspaceName, projectname ); refreshProjectsModel( ProjectsModel::LocalProjectsModel ); - Project project = mLocalProjectsModel->projectFromId( mUsername + '/' + projectname ); + Project project = mLocalProjectsModel->projectFromId( mWorkspaceName + '/' + projectname ); QFile::copy( mTestDataPath + "/" + TEST_PROJECT_NAME + "/test1.txt", project.local.projectDir + "/data.txt" ); @@ -2426,6 +2410,10 @@ void TestMerginApi::testAutosyncFailure() void TestMerginApi::testRegisterAndDelete() { +#if defined(USE_MERGIN_DUMMY_API_KEY) + QSKIP( "testRegisterAndDelete requires USE_MM_SERVER_API_KEY" ); +#endif + QString password = mApi->userAuth()->password(); QString quiteRandom = CoreUtils::uuidWithoutBraces( QUuid::createUuid() ).right( 15 ).replace( "-", "" ); @@ -2437,8 +2425,15 @@ void TestMerginApi::testRegisterAndDelete() mApi->clearAuth(); QSignalSpy spy( mApi, &MerginApi::registrationSucceeded ); + QSignalSpy spy2( mApi, &MerginApi::registrationFailed ); mApi->registerUser( username, email, password, password, true ); - QVERIFY( spy.wait( TestUtils::LONG_REPLY ) ); + bool success = spy.wait( TestUtils::LONG_REPLY ); + if ( !success ) + { + qDebug() << "Failed registration" << spy2.takeFirst(); + QVERIFY( false ); + } + QSignalSpy spyAuth( mApi->userAuth(), &MerginUserAuth::authChanged ); mApi->authorize( username, password ); @@ -2452,8 +2447,60 @@ void TestMerginApi::testRegisterAndDelete() QVERIFY( arguments.at( 0 ).toBool() == true ); } +void TestMerginApi::testCreateWorkspace() +{ +#if defined(USE_MERGIN_DUMMY_API_KEY) + QSKIP( "testCreateWorkspace requires USE_MM_SERVER_API_KEY" ); +#endif + // we need to register new user for tests and assign its credentials to env vars + QString username = TestUtils::generateUsername(); + QString password = TestUtils::generatePassword(); + QString email = TestUtils::generateEmail(); + + qDebug() << "REGISTERING NEW TEST USER:" << username; + + QSignalSpy spy( mApi, &MerginApi::registrationSucceeded ); + QSignalSpy spy2( mApi, &MerginApi::registrationFailed ); + mApi->registerUser( username, email, password, password, true ); + bool success = spy.wait( TestUtils::LONG_REPLY ); + if ( !success ) + { + qDebug() << "Failed registration" << spy2.takeFirst(); + QVERIFY( false ); + } + + QSignalSpy authSpy( mApi, &MerginApi::authChanged ); + mApi->authorize( username, password ); + QVERIFY( authSpy.wait( TestUtils::LONG_REPLY ) ); + QVERIFY( !authSpy.isEmpty() ); + + // we also need to create a workspace for this user + QSignalSpy wsSpy( mApi, &MerginApi::workspaceCreated ); + mApi->createWorkspace( username ); + QVERIFY( wsSpy.wait( TestUtils::LONG_REPLY ) ); + QCOMPARE( wsSpy.takeFirst().at( 1 ), true ); + + qDebug() << "CREATED NEW WORKSPACE:" << username; + + // call userInfo to set active workspace + QSignalSpy infoSpy( mApi, &MerginApi::userInfoReplyFinished ); + mApi->getUserInfo(); + QVERIFY( infoSpy.wait( TestUtils::LONG_REPLY ) ); + + QVERIFY( mApi->userInfo()->activeWorkspaceId() >= 0 ); + + // not possible to delete user because it has workspace + QSignalSpy spyDelete( mApi, &MerginApi::accountDeleted ); + mApi->deleteAccount(); + QVERIFY( spyDelete.wait( TestUtils::LONG_REPLY ) ); + QList arguments = spyDelete.takeFirst(); + QVERIFY( arguments.at( 0 ).toBool() == false ); +} + void TestMerginApi::testExcludeFromSync() { + deleteLocalDir( mApi, "testExcludeFromSync" ); + // Set selective sync directory QString selectiveSyncDir( mApi->projectsPath() + "/testExcludeFromSync" ); @@ -2539,8 +2586,13 @@ int TestMerginApi::serverVersionFromSpy( QSignalSpy &spy ) return serverVersion; } -void TestMerginApi::createRemoteProject( MerginApi *api, const QString &projectNamespace, const QString &projectName, const QString &sourcePath ) +void TestMerginApi::createRemoteProject( MerginApi *api, const QString &projectNamespace, const QString &projectName, const QString &sourcePath, bool force ) { + if ( force ) + { + deleteRemoteProjectNow( api, projectNamespace, projectName ); + } + // create a project QSignalSpy spy( api, &MerginApi::projectCreated ); api->createProject( projectNamespace, projectName, true ); @@ -2577,13 +2629,63 @@ void TestMerginApi::createRemoteProject( MerginApi *api, const QString &projectN QVERIFY( QDir( projectDir ).isEmpty() ); } -void TestMerginApi::deleteRemoteProject( MerginApi *api, const QString &projectNamespace, const QString &projectName ) +QString TestMerginApi::projectIdFromProjectFullName( MerginApi *api, const QString &projectNamespace, const QString &projectName ) { - QSignalSpy spy( api, &MerginApi::serverProjectDeleted ); - api->deleteProject( projectNamespace, projectName ); + QString ret; + if ( !api->validateAuth() || api->mApiVersionStatus != MerginApiStatus::OK || api->mServerType == MerginServerType::OLD ) + { + return ret; + } + + QString projectFullName = api->getFullProjectName( projectNamespace, projectName ); + + QNetworkReply *r = api->getProjectInfo( projectFullName ); + Q_ASSERT( r ); + QSignalSpy spy( r, &QNetworkReply::finished ); spy.wait( TestUtils::SHORT_REPLY ); + + if ( r->error() == QNetworkReply::NoError ) + { + QByteArray data = r->readAll(); + MerginProjectMetadata serverProject = MerginProjectMetadata::fromJson( data ); + ret = serverProject.projectId; + } + else + { + qDebug() << "Project " << projectFullName << " probably does not exists on remote server"; + } + + r->deleteLater(); + return ret; +} + +void TestMerginApi::deleteRemoteProjectNow( MerginApi *api, const QString &projectNamespace, const QString &projectName ) +{ + if ( !api->validateAuth() || api->mApiVersionStatus != MerginApiStatus::OK || api->mServerType == MerginServerType::OLD ) + { + return; + } + + QString projectId = projectIdFromProjectFullName( api, projectNamespace, projectName ); + if ( projectId.isEmpty() ) + { + // probably no such project exist on server + return; + } + + QNetworkRequest request = api->getDefaultRequest(); + 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 ); + QSignalSpy spy( r, &QNetworkReply::finished ); + spy.wait( TestUtils::SHORT_REPLY ); + + QCOMPARE( r->error(), QNetworkReply::NoError ); + r->deleteLater(); } + void TestMerginApi::deleteLocalProject( MerginApi *api, const QString &projectNamespace, const QString &projectName ) { LocalProject project = api->getLocalProject( MerginApi::getFullProjectName( projectNamespace, projectName ) ); @@ -2641,7 +2743,11 @@ void TestMerginApi::writeFileContent( const QString &filename, const QByteArray QByteArray TestMerginApi::readFileContent( const QString &filename ) { QFile f( filename ); - Q_ASSERT( f.exists() ); + if ( !f.exists() ) + { + qDebug() << "Filename " << filename << " does not exist"; + Q_ASSERT( false ); + } bool ok = f.open( QIODeviceBase::ReadOnly ); Q_ASSERT( ok ); QByteArray data = f.readAll(); diff --git a/app/test/testmerginapi.h b/app/test/testmerginapi.h index 0ccd9c804..21d14e734 100644 --- a/app/test/testmerginapi.h +++ b/app/test/testmerginapi.h @@ -18,17 +18,14 @@ #include #include #include "project.h" -#include "testingpurchasingbackend.h" #include -class Purchasing; - class TestMerginApi: public QObject { Q_OBJECT public: - explicit TestMerginApi( MerginApi *api, Purchasing *purchasing ); + explicit TestMerginApi( MerginApi *api ); ~TestMerginApi(); static const QString TEST_PROJECT_NAME; @@ -84,6 +81,7 @@ class TestMerginApi: public QObject void testAutosyncFailure(); void testRegisterAndDelete(); + void testCreateWorkspace(); // mergin functions void testExcludeFromSync(); @@ -98,12 +96,12 @@ class TestMerginApi: public QObject private: MerginApi *mApi = nullptr; - Purchasing *mPurchasing = nullptr; std::unique_ptr mLocalProjectsModel; std::unique_ptr mCreatedProjectsModel; std::unique_ptr mSyncManager; QString mUsername; + QString mWorkspaceName; QString mTestDataPath; //! extra API to do requests we are not testing (as if some other user did those) MerginApi *mApiExtra = nullptr; @@ -113,10 +111,23 @@ class TestMerginApi: public QObject MerginProjectsList projectListFromSpy( QSignalSpy &spy ); int serverVersionFromSpy( QSignalSpy &spy ); - //! Creates a project on the server and pushes an initial version and removes the local copy. - void createRemoteProject( MerginApi *api, const QString &projectNamespace, const QString &projectName, const QString &sourcePath ); - //! Deletes a project on the server - void deleteRemoteProject( MerginApi *api, const QString &projectNamespace, const QString &projectName ); + /** + * Creates a project on the server and pushes an initial version and removes the local copy. + * If force is used, the project is first deleted by deleteRemoteProjectNow from remote server + */ + void createRemoteProject( MerginApi *api, const QString &projectNamespace, const QString &projectName, const QString &sourcePath, bool force = true ); + + /** + * Gets project id from project full name + * Returns empty string if project is not found on server + */ + QString projectIdFromProjectFullName( MerginApi *api, const QString &projectNamespace, const QString &projectName ); + + /** + * Immediately deletes a project on the server + * If project does not exists, it does nothing. + */ + void deleteRemoteProjectNow( MerginApi *api, const QString &projectNamespace, const QString &projectName ); //! Downloads a remote project to the local drive, extended version also sets server version void downloadRemoteProject( MerginApi *api, const QString &projectNamespace, const QString &projectName, int &serverVersion ); diff --git a/app/test/testpurchasing.cpp b/app/test/testpurchasing.cpp deleted file mode 100644 index b5667c1dd..000000000 --- a/app/test/testpurchasing.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include -#include -#include -#include - -#include "testpurchasing.h" -#include "merginapi.h" -#include "merginapistatus.h" -#include "merginuserauth.h" -#include "merginuserinfo.h" -#include "merginworkspaceinfo.h" -#include "merginsubscriptioninfo.h" -#include "testutils.h" -#include "test/testingpurchasingbackend.h" - -TestPurchasing::TestPurchasing( MerginApi *api, Purchasing *purchasing ) -{ - mApi = api; - Q_ASSERT( mApi ); // does not make sense to run without API - - Q_ASSERT( purchasing ); - mPurchasing = purchasing; -} - -void TestPurchasing::initTestCase() -{ - QString apiRoot, username, password; - TestUtils::mergin_setup_auth( mApi, apiRoot, username, password ); - TestUtils::mergin_setup_pro_subscription( mApi, mPurchasing ); -} - -void TestPurchasing::cleanupTestCase() -{ -} - -void TestPurchasing::testUserBuyTier01() -{ - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::ValidSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserBuyTier12() -{ - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyProfessionalPlan, TestUtils::TIER02_PLAN_ID ); - - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER02_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER02_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::ValidSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserUnsubscribed() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateUnsubscribed, TestUtils::TIER01_PLAN_ID, false ); - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::SubscriptionUnsubscribed ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserInGracePeriod() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateGracePeriod, TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::SubscriptionInGracePeriod ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserCancelledSubscription() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - - QCOMPARE( mApi->subscriptionInfo()->planProductId(), "" ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::FREE_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), false ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::NoneSubscriptionType ); -} - -void TestPurchasing::testUserCancelledTransaction() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - Q_ASSERT( mPurchasing ); - TestingPurchasingBackend *purchasingBackend = qobject_cast( mPurchasing->backend() ); - Q_ASSERT( purchasingBackend ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - int oldStatus = mApi->subscriptionInfo()->subscriptionStatus(); - purchasingBackend->setNextPurchaseResult( TestingPurchasingBackend::NonInteractiveUserCancelled ); - - QSignalSpy spy0( purchasingBackend, &PurchasingBackend::transactionCreationFailed ); - mPurchasing->purchase( TestUtils::TIER01_PLAN_ID ); - // immediate action without server for testbackend - QCOMPARE( spy0.count(), 1 ); - - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), oldStatus ); -} - -void TestPurchasing::testUserSendsBadReceipt() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - Q_ASSERT( mPurchasing ); - TestingPurchasingBackend *purchasingBackend = qobject_cast( mPurchasing->backend() ); - Q_ASSERT( purchasingBackend ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - int oldStatus = mApi->subscriptionInfo()->subscriptionStatus(); - purchasingBackend->setNextPurchaseResult( TestingPurchasingBackend::NonInteractiveBadReceipt ); - - QSignalSpy spy0( mApi, &MerginApi::networkErrorOccurred ); - mPurchasing->purchase( TestUtils::TIER01_PLAN_ID ); - QVERIFY( spy0.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy0.count(), 1 ); - - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), oldStatus ); -} - -void TestPurchasing::testUserRestore() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - QSignalSpy spy0( mApi->subscriptionInfo(), &MerginSubscriptionInfo::subscriptionInfoChanged ); - QSignalSpy spy1( mApi->userInfo(), &MerginUserInfo::userInfoChanged ); - mPurchasing->restore(); - QVERIFY( spy0.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy0.count(), 1 ); - QVERIFY( spy1.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy1.count(), 1 ); - - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::ValidSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} diff --git a/app/test/testpurchasing.h b/app/test/testpurchasing.h deleted file mode 100644 index efa5d9198..000000000 --- a/app/test/testpurchasing.h +++ /dev/null @@ -1,45 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef TESTPURCHASING_H -#define TESTPURCHASING_H - -#include - -#include "inputconfig.h" - -class MerginApi; -class Purchasing; - -class TestPurchasing: public QObject -{ - Q_OBJECT - public: - explicit TestPurchasing( MerginApi *api, Purchasing *purchasing ); - ~TestPurchasing() = default; - - private slots: - void initTestCase(); - void cleanupTestCase(); - - void testUserBuyTier01(); - void testUserBuyTier12(); - void testUserCancelledTransaction(); - void testUserUnsubscribed(); - void testUserInGracePeriod(); - void testUserCancelledSubscription(); - void testUserSendsBadReceipt(); - void testUserRestore(); - - private: - MerginApi *mApi = nullptr; - Purchasing *mPurchasing = nullptr; -}; - -# endif // TESTPURCHASING_H diff --git a/app/test/testutils.cpp b/app/test/testutils.cpp index f58cd7850..966e3cfb6 100644 --- a/app/test/testutils.cpp +++ b/app/test/testutils.cpp @@ -16,10 +16,8 @@ #include "coreutils.h" #include "inpututils.h" #include "merginapi.h" -#include "purchasing.h" -#include "testingpurchasingbackend.h" -void TestUtils::mergin_setup_auth( MerginApi *api, QString &apiRoot, QString &username, QString &password ) +void TestUtils::merginGetAuthCredentials( MerginApi *api, QString &apiRoot, QString &username, QString &password ) { Q_ASSERT( api ); @@ -33,99 +31,76 @@ void TestUtils::mergin_setup_auth( MerginApi *api, QString &apiRoot, QString &us // let's make sure we do not mess with the public instance Q_ASSERT( apiRoot != MerginApi::sDefaultApiRoot ); - username = ::getenv( "TEST_API_USERNAME" ); - password = ::getenv( "TEST_API_PASSWORD" ); - - if ( username.isEmpty() ) - { - // we need to register new user for tests and assign its credentials to env vars - username = generateUsername(); - password = generatePassword(); - QString email = generateEmail(); - - qDebug() << "REGISTERING NEW TEST USER:" << username; - - QSignalSpy spy( api, &MerginApi::registrationSucceeded ); - api->registerUser( username, email, password, password, true ); - QVERIFY( spy.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy.count(), 1 ); - - // put it so in next local test run we can take it from - // the environment and we do not create another user - qputenv( "TEST_API_USERNAME", username.toLatin1() ); - qputenv( "TEST_API_PASSWORD", password.toLatin1() ); + // Test user needs to be set + Q_ASSERT( ::getenv( "TEST_API_USERNAME" ) ); - QSignalSpy authSpy( api, &MerginApi::authChanged ); - api->authorize( username, password ); - QVERIFY( authSpy.wait( TestUtils::LONG_REPLY ) ); - QVERIFY( !authSpy.isEmpty() ); + // Test password needs to be set + Q_ASSERT( ::getenv( "TEST_API_PASSWORD" ) ); - // we also need to create a workspace for this user - QSignalSpy wsSpy( api, &MerginApi::workspaceCreated ); - api->createWorkspace( username ); - QVERIFY( wsSpy.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( wsSpy.takeFirst().at( 1 ), true ); + username = ::getenv( "TEST_API_USERNAME" ); + password = ::getenv( "TEST_API_PASSWORD" ); +} - qDebug() << "CREATED NEW WORKSPACE:" << username; +void TestUtils::authorizeUser( MerginApi *api, const QString &username, const QString &password ) +{ + // Auth this user + QSignalSpy spyExtra( api, &MerginApi::authChanged ); + api->authorize( username, password ); + QVERIFY( spyExtra.wait( TestUtils::LONG_REPLY ) ); + QCOMPARE( spyExtra.count(), 1 ); +} - // call userInfo to set active workspace - QSignalSpy infoSpy( api, &MerginApi::userInfoReplyFinished ); - api->getUserInfo(); - QVERIFY( infoSpy.wait( TestUtils::LONG_REPLY ) ); +void TestUtils::selectFirstWorkspace( MerginApi *api, QString &workspace ) +{ + // Gets his workspaces + QSignalSpy spyExtraWs( api, &MerginApi::listWorkspacesFinished ); + api->listWorkspaces(); + QVERIFY( spyExtraWs.wait( TestUtils::LONG_REPLY ) ); + QCOMPARE( spyExtraWs.count(), 1 ); - QVERIFY( api->userInfo()->activeWorkspaceId() >= 0 ); + // Sets active workspace + Q_ASSERT( !api->userInfo()->workspaces().isEmpty() ); + api->userInfo()->setActiveWorkspace( api->userInfo()->workspaces().firstKey() ); - } + // This user needs to have active workspace + Q_ASSERT( !api->userInfo()->activeWorkspaceName().isEmpty() ); - Q_ASSERT( ::getenv( "TEST_API_USERNAME" ) ); - Q_ASSERT( ::getenv( "TEST_API_PASSWORD" ) ); + workspace = api->userInfo()->activeWorkspaceName(); - qDebug() << "MERGIN USERNAME:" << username; - qDebug() << "MERGIN WORKSPACE:" << api->userInfo()->activeWorkspaceName() << api->userInfo()->activeWorkspaceId(); } -void TestUtils::mergin_setup_pro_subscription( MerginApi *api, Purchasing *purchasing ) +bool TestUtils::needsToAuthorizeAgain( MerginApi *api, const QString &username ) { - QSignalSpy spy2( api, &MerginApi::subscriptionInfoChanged ); - api->getServiceInfo(); - QVERIFY( spy2.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy2.count(), 1 ); - - Q_ASSERT( ! purchasing->transactionPending() ); - - if ( api->subscriptionInfo()->planProductId() != TIER02_PLAN_ID ) + Q_ASSERT( api ); + // no auth at all + if ( !api->userAuth()->hasAuthData() ) { - // always start from PRO subscription - qDebug() << "PURCHASE PRO subscription:" << api->userInfo()->activeWorkspaceName(); - runPurchasingCommand( api, purchasing, TestingPurchasingBackend::NonInteractiveBuyProfessionalPlan, TIER02_PLAN_ID ); + return true; } - Q_ASSERT( api->subscriptionInfo()->planProductId() == TIER02_PLAN_ID ); - qDebug() << "MERGIN SUBSCRIPTION:" << api->subscriptionInfo()->planProductId(); -} - -void TestUtils::runPurchasingCommand( MerginApi *api, Purchasing *purchasing, TestingPurchasingBackend::NextPurchaseResult result, const QString &planId, bool waitForWorkspaceInfoChanged ) -{ - Q_ASSERT( purchasing ); - TestingPurchasingBackend *purchasingBackend = qobject_cast( purchasing->backend() ); - Q_ASSERT( purchasingBackend ); - - purchasingBackend->setNextPurchaseResult( result ); - - QSignalSpy spy0( api, &MerginApi::subscriptionInfoChanged ); - QVERIFY( !planId.isEmpty() ); - QSignalSpy spy1( api->workspaceInfo(), &MerginWorkspaceInfo::workspaceInfoChanged ); + // wrong user + if ( api->userAuth()->username() != username ) + { + return true; + } - purchasing->purchase( planId ); - QVERIFY( spy0.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy0.count(), 1 ); + // no workspace + if ( api->userInfo()->activeWorkspaceName().isEmpty() ) + { + return true; + } - if ( waitForWorkspaceInfoChanged ) + // invalid token + if ( api->userAuth()->authToken().isEmpty() || api->userAuth()->tokenExpiration() < QDateTime().currentDateTime().toUTC() ) { - QVERIFY( spy1.wait( TestUtils::LONG_REPLY ) ); + return true; } + + // we are OK + return false; } + QString TestUtils::generateUsername() { QDateTime time = QDateTime::currentDateTime(); diff --git a/app/test/testutils.h b/app/test/testutils.h index d25cad6df..2f11654fc 100644 --- a/app/test/testutils.h +++ b/app/test/testutils.h @@ -15,30 +15,25 @@ #include "inputconfig.h" #include "qgsproject.h" -#include "testingpurchasingbackend.h" class MerginApi; -class Purchasing; -class TestingPurchasingBackend; namespace TestUtils { const int SHORT_REPLY = 5000; const int LONG_REPLY = 90000; - const double FREE_STORAGE = 104857600.0; // 100 MB + //! authorize user and select the active workspace + void authorizeUser( MerginApi *api, const QString &username, const QString &password ); - const QString TIER01_PLAN_ID = "test_mergin_tier_1_1"; - const double TIER01_STORAGE = 1073741824.0; // 1GB + //! select the first workspace as active workspace + void selectFirstWorkspace( MerginApi *api, QString &workspace ); - const QString TIER02_PLAN_ID = "test_mergin_tier_1_2"; - const double TIER02_STORAGE = 10737418240.0; // 10 GB + //! Get TEST user credentials from env variables + void merginGetAuthCredentials( MerginApi *api, QString &apiRoot, QString &username, QString &password ); - //! Use credentials from env variables if they are set, otherwise register new user and set its credentials to env var - void mergin_setup_auth( MerginApi *api, QString &apiRoot, QString &username, QString &password ); - - //! Setup professional plan for active workspace - void mergin_setup_pro_subscription( MerginApi *api, Purchasing *purchasing ); + //! Whether we need to auth again + bool needsToAuthorizeAgain( MerginApi *api, const QString &username ); QString generateUsername(); QString generateEmail(); @@ -56,9 +51,6 @@ namespace TestUtils * Returns true if files were successfully created */ bool generateProjectFolder( const QString &rootPath, const QJsonDocument &structure ); - - //! Test util function to invoke purchasing function and wait for the replies. - void runPurchasingCommand( MerginApi *api, Purchasing *purchasing, TestingPurchasingBackend::NextPurchaseResult result, const QString &planId, bool waitForWorkspaceInfoChanged = true ); } #define COMPARENEAR(actual, expected, epsilon) \ diff --git a/cmake/FindAppleFrameworks.cmake b/cmake/FindAppleFrameworks.cmake index b083f2aa4..d81d5001e 100644 --- a/cmake/FindAppleFrameworks.cmake +++ b/cmake/FindAppleFrameworks.cmake @@ -20,10 +20,6 @@ set(APPLE_FRAMEWORKS CoreLocation ) -if (HAVE_APPLE_PURCHASING) - set(APPLE_FRAMEWORKS ${APPLE_FRAMEWORKS} Foundation StoreKit) -endif () - foreach (framework ${APPLE_FRAMEWORKS}) find_library(APPLE_${framework}_LIBRARY NAMES ${framework}) set(APPLE_REQUIRED_VARS ${APPLE_REQUIRED_VARS} APPLE_${framework}_LIBRARY) diff --git a/cmake_templates/inputconfig.h.in b/cmake_templates/inputconfig.h.in index 0de9c322c..20e6c5c62 100644 --- a/cmake_templates/inputconfig.h.in +++ b/cmake_templates/inputconfig.h.in @@ -14,9 +14,5 @@ #cmakedefine HAVE_BLUETOOTH -#cmakedefine PURCHASING - -#cmakedefine APPLE_PURCHASING - #endif diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index b9a72fe0c..732d8327a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -4,7 +4,6 @@ set(MM_CORE_SRCS merginapistatus.cpp merginsubscriptioninfo.cpp merginsubscriptionstatus.cpp - merginsubscriptiontype.cpp merginservertype.cpp merginprojectstatusmodel.cpp merginuserauth.cpp @@ -23,7 +22,6 @@ set(MM_CORE_HDRS merginapistatus.h merginsubscriptioninfo.h merginsubscriptionstatus.h - merginsubscriptiontype.h merginservertype.h merginprojectstatusmodel.h merginuserauth.h diff --git a/core/merginapi.h b/core/merginapi.h index eedcbebef..ac7c450b4 100644 --- a/core/merginapi.h +++ b/core/merginapi.h @@ -35,8 +35,6 @@ #include "merginworkspaceinfo.h" #include "merginuserauth.h" -class Purchasing; - class RegistrationError { Q_GADGET @@ -407,6 +405,12 @@ class MerginApi: public QObject // Test functions /** * Deletes the project of given namespace and name on Mergin server. + * Note that this deletion is not immediately done, + * but only scheduled to be deleted in few days. + * + * TODO - we should use DEL /v2/projects/ if possible, see + * TestMerginApi::deleteRemoteProjectNow() + * * \param projectNamespace * \param projectName */ @@ -806,8 +810,6 @@ class MerginApi: public QObject MerginServerType::ServerType mServerType = MerginServerType::ServerType::OLD; friend class TestMerginApi; - friend class Purchasing; - friend class PurchasingTransaction; }; #endif // MERGINAPI_H diff --git a/core/merginprojectmetadata.cpp b/core/merginprojectmetadata.cpp index f86042644..fc53f07b9 100644 --- a/core/merginprojectmetadata.cpp +++ b/core/merginprojectmetadata.cpp @@ -121,6 +121,15 @@ MerginProjectMetadata MerginProjectMetadata::fromJson( const QByteArray &data ) project.version = versionStr.toInt(); } + if ( docObj.contains( QStringLiteral( "id" ) ) ) + { + project.projectId = docObj.value( QStringLiteral( "id" ) ).toString(); + } + else + { + project.projectId.clear(); + } + return project; } diff --git a/core/merginprojectmetadata.h b/core/merginprojectmetadata.h index a76815c8d..188555a37 100644 --- a/core/merginprojectmetadata.h +++ b/core/merginprojectmetadata.h @@ -62,6 +62,7 @@ struct MerginProjectMetadata QList writersnames; int version = -1; QList files; + QString projectId; //!< unique project ID (only available in API that supports project IDs) // no project dir, no sync state, ... diff --git a/core/merginsubscriptioninfo.cpp b/core/merginsubscriptioninfo.cpp index a2f5ab7e9..e6b54c79d 100644 --- a/core/merginsubscriptioninfo.cpp +++ b/core/merginsubscriptioninfo.cpp @@ -30,7 +30,6 @@ void MerginSubscriptionInfo::clearSubscriptionData() void MerginSubscriptionInfo::clearPlanInfo() { mPlanAlias = ""; - mPlanProvider = MerginSubscriptionType::NoneSubscriptionType; // plan product Id might change from several sources, we need to emit its signal only when it has really changed if ( !mPlanProductId.isEmpty() ) @@ -38,8 +37,6 @@ void MerginSubscriptionInfo::clearPlanInfo() mPlanProductId = ""; emit planProductIdChanged(); } - - emit planProviderChanged(); } void MerginSubscriptionInfo::clear() @@ -110,13 +107,6 @@ void MerginSubscriptionInfo::setFromJson( QJsonObject docObj ) mOwnsActiveSubscription = planObj.value( QStringLiteral( "is_paid_plan" ) ).toBool(); mPlanAlias = planObj.value( QStringLiteral( "alias" ) ).toString(); - MerginSubscriptionType::SubscriptionType planProvider = MerginSubscriptionType::fromString( planObj.value( QStringLiteral( "type" ) ).toString() ); - if ( planProvider != mPlanProvider ) - { - mPlanProvider = planProvider; - emit planProviderChanged(); - } - QString planProductId = planObj.value( QStringLiteral( "product_id" ) ).toString(); if ( planProductId != mPlanProductId ) { @@ -158,12 +148,6 @@ int MerginSubscriptionInfo::subscriptionId() const return mSubscriptionId; } - -MerginSubscriptionType::SubscriptionType MerginSubscriptionInfo::planProvider() const -{ - return mPlanProvider; -} - QString MerginSubscriptionInfo::planProductId() const { return mPlanProductId; diff --git a/core/merginsubscriptioninfo.h b/core/merginsubscriptioninfo.h index 0054e48b0..89ce8a7d7 100644 --- a/core/merginsubscriptioninfo.h +++ b/core/merginsubscriptioninfo.h @@ -15,14 +15,12 @@ #include #include "merginsubscriptionstatus.h" -#include "merginsubscriptiontype.h" class MerginSubscriptionInfo: public QObject { Q_OBJECT Q_PROPERTY( QString planAlias READ planAlias NOTIFY subscriptionInfoChanged ) // see PurchasingPlan::alias() - Q_PROPERTY( /*MerginSubscriptionType::SubscriptionType*/ int planProvider READ planProvider NOTIFY subscriptionInfoChanged ) // see PurchasingTransaction::provider() Q_PROPERTY( QString planProductId READ planProductId NOTIFY subscriptionInfoChanged ) // see PurchasingPlan::id() Q_PROPERTY( /*MerginSubscriptionStatus::SubscriptionStatus*/ int subscriptionStatus READ subscriptionStatus NOTIFY subscriptionInfoChanged ) Q_PROPERTY( QString subscriptionTimestamp READ subscriptionTimestamp NOTIFY subscriptionInfoChanged ) @@ -42,7 +40,6 @@ class MerginSubscriptionInfo: public QObject QString planAlias() const; int subscriptionId() const; - MerginSubscriptionType::SubscriptionType planProvider() const; QString planProductId() const; QString nextBillPrice() const; /*MerginSubscriptionStatus::SubscriptionStatus*/ int subscriptionStatus() const; @@ -59,7 +56,6 @@ class MerginSubscriptionInfo: public QObject signals: void subscriptionInfoChanged(); void planProductIdChanged(); - void planProviderChanged(); void storageChanged( double storage ); void canAccessSubscriptionChanged( bool canAccessSubscription ); @@ -67,7 +63,6 @@ class MerginSubscriptionInfo: public QObject private: QString mPlanAlias; int mSubscriptionId = -1; - MerginSubscriptionType::SubscriptionType mPlanProvider = MerginSubscriptionType::NoneSubscriptionType; QString mPlanProductId; bool mOwnsActiveSubscription = false; QString mNextBillPrice; diff --git a/core/merginsubscriptiontype.cpp b/core/merginsubscriptiontype.cpp deleted file mode 100644 index 27e706d2f..000000000 --- a/core/merginsubscriptiontype.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "merginsubscriptiontype.h" - -MerginSubscriptionType::MerginSubscriptionType() -{ -} - -MerginSubscriptionType::SubscriptionType MerginSubscriptionType::fromString( const QString &name ) -{ - if ( name == "apple" ) - return MerginSubscriptionType::AppleSubscriptionType; - - if ( name == "stripe" ) - return MerginSubscriptionType::StripeSubscriptionType; - - if ( name == "test" ) - return MerginSubscriptionType::TestSubscriptionType; - - return MerginSubscriptionType::NoneSubscriptionType; -} - -QString MerginSubscriptionType::toString( const MerginSubscriptionType::SubscriptionType &type ) -{ - if ( type == MerginSubscriptionType::AppleSubscriptionType ) - return "apple"; - - if ( type == MerginSubscriptionType::StripeSubscriptionType ) - return "stripe"; - - if ( type == MerginSubscriptionType::TestSubscriptionType ) - return "test"; - - return ""; -} diff --git a/core/merginsubscriptiontype.h b/core/merginsubscriptiontype.h deleted file mode 100644 index 43b678e93..000000000 --- a/core/merginsubscriptiontype.h +++ /dev/null @@ -1,35 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef MERGINSUBSCRIPTIONTYPE_H -#define MERGINSUBSCRIPTIONTYPE_H - -#include -#include - -class MerginSubscriptionType -{ - Q_GADGET - public: - explicit MerginSubscriptionType(); - - enum SubscriptionType - { - NoneSubscriptionType, - AppleSubscriptionType, // in-app purchases (apple provider) - StripeSubscriptionType, // stripe provider - TestSubscriptionType, // testing (mock) provider - }; - Q_ENUMS( SubscriptionType ) - - static SubscriptionType fromString( const QString &name ); - static QString toString( const SubscriptionType &type ); -}; - -#endif // MERGINSUBSCRIPTIONTYPE_H diff --git a/core/merginuserinfo.h b/core/merginuserinfo.h index 68d2801e9..52efedfb4 100644 --- a/core/merginuserinfo.h +++ b/core/merginuserinfo.h @@ -16,8 +16,6 @@ #include #include "merginsubscriptionstatus.h" -#include "merginsubscriptiontype.h" - struct MerginInvitation { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4e1c7cf5d..50085091f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,7 +11,6 @@ set(MM_TESTS testUtils testAttributePreviewController testMerginApi - testPurchasing testAttributeController testIdentifyKit testPosition