// SPDX-License-Identifier: GPL-2.0 #include "testgitstorage.h" #include "git2.h" #include "core/divesite.h" #include "core/file.h" #include "core/qthelper.h" #include "core/subsurfacestartup.h" #include "core/settings/qPrefProxy.h" #include "core/settings/qPrefCloudStorage.h" #include "core/trip.h" #include "core/git-access.h" #include #include #include #include #include // this is a local helper function in git-access.c extern "C" char *get_local_dir(const char *remote, const char *branch); QString email("ssrftest@hohndel.org"); QString gitUrl; QString cloudTestRepo; QString localCacheDir; QString localCacheRepo; void TestGitStorage::initTestCase() { // Set UTF8 text codec as in real applications QTextCodec::setCodecForLocale(QTextCodec::codecForMib(106)); // first, setup the preferences an proxy information copy_prefs(&default_prefs, &prefs); QCoreApplication::setOrganizationName("Subsurface"); QCoreApplication::setOrganizationDomain("subsurface.hohndel.org"); QCoreApplication::setApplicationName("Subsurface"); qPrefProxy::load(); qPrefCloudStorage::load(); // setup our cloud test repo / credentials gitUrl = prefs.cloud_base_url; if (gitUrl.right(1) != "/") gitUrl += "/"; gitUrl += "git"; prefs.cloud_git_url = copy_qstring(gitUrl); prefs.cloud_storage_email_encoded = copy_qstring(email); prefs.cloud_storage_password = strdup("geheim"); gitUrl += "/" + email; cloudTestRepo = gitUrl + QStringLiteral("[%1]").arg(email); localCacheDir = get_local_dir(qPrintable(gitUrl), qPrintable(email)); localCacheRepo = localCacheDir + QStringLiteral("[%1]").arg(email); // now cleanup the cache dir in case there's something weird from previous runs QDir localCacheDirectory(localCacheDir); QCOMPARE(localCacheDirectory.removeRecursively(), true); // make sure we deal with any proxy settings that are needed QNetworkProxy proxy; proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); proxy.setHostName(prefs.proxy_host); proxy.setPort(prefs.proxy_port); if (prefs.proxy_auth) { proxy.setUser(prefs.proxy_user); proxy.setPassword(prefs.proxy_pass); } QNetworkProxy::setApplicationProxy(proxy); // make sure that regardless of whether this is a desktop or mobile build, we always check with the cloud git_local_only = false; } void TestGitStorage::cleanup() { clear_dive_file_data(); } void TestGitStorage::testGitStorageLocal_data() { // Test different paths we may encounter (since storage depends on user name) // as well as with and without "file://" URL prefix. QTest::addColumn("testDirName"); QTest::addColumn("prefixRead"); QTest::addColumn("prefixWrite"); QTest::newRow("ASCII path") << "./gittest" << "" << ""; QTest::newRow("Non ASCII path") << "./gittest_éèêôàüäößíñóúäåöø" << "" << ""; QTest::newRow("ASCII path with file:// prefix on read") << "./gittest2" << "file://" << ""; QTest::newRow("Non ASCII path with file:// prefix on read") << "./gittest2_éèêôàüäößíñóúäåöø" << "" << "file://"; QTest::newRow("ASCII path with file:// prefix on write") << "./gittest3" << "file://" << ""; QTest::newRow("Non ASCII path with file:// prefix on write") << "./gittest3_éèêôàüäößíñóúäåöø" << "" << "file://"; } void TestGitStorage::testGitStorageLocal() { // test writing and reading back from local git storage git_repository *repo; git_libgit2_init(); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/SampleDivesV2.ssrf", &dive_table, &trip_table, &dive_site_table), 0); QFETCH(QString, testDirName); QFETCH(QString, prefixRead); QFETCH(QString, prefixWrite); QDir testDir(testDirName); QCOMPARE(testDir.removeRecursively(), true); QCOMPARE(QDir().mkdir(testDirName), true); QString repoNameRead = prefixRead + testDirName; QString repoNameWrite = prefixWrite + testDirName; QCOMPARE(git_repository_init(&repo, qPrintable(testDirName), false), 0); QCOMPARE(save_dives(qPrintable(repoNameWrite + "[test]")), 0); QCOMPARE(save_dives("./SampleDivesV3.ssrf"), 0); clear_dive_file_data(); QCOMPARE(parse_file(qPrintable(repoNameRead + "[test]"), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives("./SampleDivesV3viagit.ssrf"), 0); QFile org("./SampleDivesV3.ssrf"); org.open(QFile::ReadOnly); QFile out("./SampleDivesV3viagit.ssrf"); out.open(QFile::ReadOnly); QTextStream orgS(&org); QTextStream outS(&out); QString readin = orgS.readAll(); QString written = outS.readAll(); QCOMPARE(readin, written); } void TestGitStorage::testGitStorageCloud() { // test writing and reading back from cloud storage // connect to the ssrftest repository on the cloud server // and repeat the same test as before with the local git storage QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/SampleDivesV2.ssrf", &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); clear_dive_file_data(); QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives("./SampleDivesV3viacloud.ssrf"), 0); QFile org("./SampleDivesV3.ssrf"); org.open(QFile::ReadOnly); QFile out("./SampleDivesV3viacloud.ssrf"); out.open(QFile::ReadOnly); QTextStream orgS(&org); QTextStream outS(&out); QString readin = orgS.readAll(); QString written = outS.readAll(); QCOMPARE(readin, written); } void TestGitStorage::testGitStorageCloudOfflineSync() { // make a change to local cache repo (pretending that we did some offline changes) // and then open the remote one again and check that things were propagated correctly // read the local repo from the previous test and add dive 10 QCOMPARE(parse_file(qPrintable(localCacheRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test10.xml", &dive_table, &trip_table, &dive_site_table), 0); // calling process_loaded_dives() sorts the table, but calling add_imported_dives() // causes it to try to update the window title... let's not do that process_loaded_dives(); // now save only to the local cache but not to the remote server QCOMPARE(save_dives(qPrintable(localCacheRepo)), 0); QCOMPARE(save_dives("./SampleDivesV3plus10local.ssrf"), 0); clear_dive_file_data(); // open the cloud storage and compare QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives("./SampleDivesV3plus10viacloud.ssrf"), 0); QFile org("./SampleDivesV3plus10local.ssrf"); org.open(QFile::ReadOnly); QFile out("./SampleDivesV3plus10viacloud.ssrf"); out.open(QFile::ReadOnly); QTextStream orgS(&org); QTextStream outS(&out); QString readin = orgS.readAll(); QString written = outS.readAll(); QCOMPARE(readin, written); // write back out to cloud storage, move away the local cache, open again and compare QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); clear_dive_file_data(); QDir localCacheDirectory(localCacheDir); QDir localCacheDirectorySave(localCacheDir + "save"); QCOMPARE(localCacheDirectorySave.removeRecursively(), true); QCOMPARE(localCacheDirectory.rename(localCacheDir, localCacheDir + "save"), true); QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives("./SampleDivesV3plus10fromcloud.ssrf"), 0); org.close(); org.open(QFile::ReadOnly); QFile out2("./SampleDivesV3plus10fromcloud.ssrf"); out2.open(QFile::ReadOnly); QTextStream orgS2(&org); QTextStream outS2(&out2); readin = orgS2.readAll(); written = outS2.readAll(); QCOMPARE(readin, written); } void TestGitStorage::testGitStorageCloudMerge() { // now we need to mess with the local git repo to get an actual merge // first we add another dive to the "moved away" repository, pretending we did // another offline change there QString localCacheRepoSave = localCacheDir + QStringLiteral("save[%1]").arg(email); QCOMPARE(parse_file(qPrintable(localCacheRepoSave), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test11.xml", &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); QCOMPARE(save_dives(qPrintable(localCacheRepoSave)), 0); clear_dive_file_data(); // now we open the cloud storage repo and add a different dive to it QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test12.xml", &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); clear_dive_file_data(); // now we move the saved local cache into place and try to open the cloud repo // -> this forces a merge QDir localCacheDirectory(localCacheDir); QCOMPARE(localCacheDirectory.removeRecursively(), true); QDir localCacheDirectorySave(localCacheDir + "save"); QCOMPARE(localCacheDirectory.rename(localCacheDir + "save", localCacheDir), true); QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives("./SampleDivesV3plus10-11-12-merged.ssrf"), 0); clear_dive_file_data(); QCOMPARE(parse_file("./SampleDivesV3plus10local.ssrf", &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test11.xml", &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); QCOMPARE(parse_file(SUBSURFACE_TEST_DATA "/dives/test12.xml", &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); QCOMPARE(save_dives("./SampleDivesV3plus10-11-12.ssrf"), 0); QFile org("./SampleDivesV3plus10-11-12-merged.ssrf"); org.open(QFile::ReadOnly); QFile out("./SampleDivesV3plus10-11-12.ssrf"); out.open(QFile::ReadOnly); QTextStream orgS(&org); QTextStream outS(&out); QString readin = orgS.readAll(); QString written = outS.readAll(); QCOMPARE(readin, written); } void TestGitStorage::testGitStorageCloudMerge2() { // delete a dive offline // edit the same dive in the cloud repo // merge QCOMPARE(parse_file(qPrintable(localCacheRepo), &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); struct dive *dive = get_dive(1); delete_single_dive(1); QCOMPARE(save_dives("./SampleDivesMinus1.ssrf"), 0); QCOMPARE(save_dives(qPrintable(localCacheRepo)), 0); clear_dive_file_data(); // move the local cache away { // scope for variables QDir localCacheDirectory(localCacheDir); QDir localCacheDirectorySave(localCacheDir + "save"); QCOMPARE(localCacheDirectorySave.removeRecursively(), true); QCOMPARE(localCacheDirectory.rename(localCacheDir, localCacheDir + "save"), true); } // now we open the cloud storage repo and modify that first dive QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); dive = get_dive(1); QVERIFY(dive != NULL); free(dive->notes); dive->notes = strdup("These notes have been modified by TestGitStorage"); QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); clear_dive_file_data(); // now we move the saved local cache into place and try to open the cloud repo // -> this forces a merge QDir localCacheDirectory(localCacheDir); QDir localCacheDirectorySave(localCacheDir + "save"); QCOMPARE(localCacheDirectory.removeRecursively(), true); QCOMPARE(localCacheDirectorySave.rename(localCacheDir + "save", localCacheDir), true); QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives("./SampleDivesMinus1-merged.ssrf"), 0); QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); QFile org("./SampleDivesMinus1-merged.ssrf"); org.open(QFile::ReadOnly); QFile out("./SampleDivesMinus1.ssrf"); out.open(QFile::ReadOnly); QTextStream orgS(&org); QTextStream outS(&out); QString readin = orgS.readAll(); QString written = outS.readAll(); QCOMPARE(readin, written); } void TestGitStorage::testGitStorageCloudMerge3() { // create multi line notes and store them to the cloud repo and local cache // edit dive notes offline // edit the same dive notes in the cloud repo // merge clear_dive_file_data(); QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); struct dive *dive = get_dive(0); QVERIFY(dive != 0); dive->notes = strdup("Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"); dive = get_dive(1); dive->notes = strdup("Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"); dive = get_dive(2); dive->notes = strdup("Create multi line dive notes\nLine 2\nLine 3\nLine 4\nLine 5\nThat should be enough"); QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); clear_dive_file_data(); QCOMPARE(parse_file(qPrintable(localCacheRepo), &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); dive = get_dive(0); dive->notes = strdup("Create multi line dive notes\nDifferent line 2 and removed 3-5\n\nThat should be enough"); dive = get_dive(1); dive->notes = strdup("Line 2\nLine 3\nLine 4\nLine 5"); // keep the middle, remove first and last"); dive = get_dive(2); dive->notes = strdup("single line dive notes"); QCOMPARE(save_dives(qPrintable(localCacheRepo)), 0); clear_dive_file_data(); // move the local cache away { // scope for variables QDir localCacheDirectory(localCacheDir); QDir localCacheDirectorySave(localCacheDir + "save"); QCOMPARE(localCacheDirectorySave.removeRecursively(), true); QCOMPARE(localCacheDirectory.rename(localCacheDir, localCacheDir + "save"), true); } // now we open the cloud storage repo and modify those first dive notes differently QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); process_loaded_dives(); dive = get_dive(0); dive->notes = strdup("Completely different dive notes\nBut also multi line"); dive = get_dive(1); dive->notes = strdup("single line dive notes"); dive = get_dive(2); dive->notes = strdup("Line 2\nLine 3\nLine 4\nLine 5"); // keep the middle, remove first and last"); QCOMPARE(save_dives(qPrintable(cloudTestRepo)), 0); clear_dive_file_data(); // now we move the saved local cache into place and try to open the cloud repo // -> this forces a merge QDir localCacheDirectory(localCacheDir); QDir localCacheDirectorySave(localCacheDir + "save"); QCOMPARE(localCacheDirectory.removeRecursively(), true); QCOMPARE(localCacheDirectorySave.rename(localCacheDir + "save", localCacheDir), true); QCOMPARE(parse_file(qPrintable(cloudTestRepo), &dive_table, &trip_table, &dive_site_table), 0); QCOMPARE(save_dives("./SampleDivesMerge3.ssrf"), 0); // we are not trying to compare this to a pre-determined result... what this test // checks is that there are no parsing errors with the merge } QTEST_GUILESS_MAIN(TestGitStorage)