Added tests and functionality to ensure that caching and syncing works when the remote file is removed.

Added UI strings for sync and cache functionality
This commit is contained in:
Philipp Crocoll 2013-08-06 22:21:58 +02:00
parent 8693dfe9f4
commit 289e10e1c4
9 changed files with 328 additions and 40 deletions

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using Android.App;
@ -57,7 +58,19 @@ namespace keepass2android.Io
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
return IOConnection.OpenRead(ioc);
try
{
return IOConnection.OpenRead(ioc);
}
catch (WebException ex)
{
if ((ex.Response is HttpWebResponse) && (((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.NotFound))
{
throw new FileNotFoundException("404!", ioc.Path, ex);
}
throw;
}
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)

View File

@ -39,6 +39,7 @@ namespace keepass2android
DownloadingRemoteFile,
UploadingFile,
FilesInSync,
SynchronizedDatabaseSuccessfully
SynchronizedDatabaseSuccessfully,
RestoringRemoteFile
}
}

View File

@ -13,6 +13,7 @@ namespace keepass2android
{
private readonly Context _context;
private readonly IKp2aApp _app;
private SaveDb _saveDb;
public SynchronizeCachedDatabase(Context context, IKp2aApp app, OnFinish finish)
: base(finish)
@ -37,10 +38,19 @@ namespace keepass2android
//download file from remote location and calculate hash:
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
string hash;
//todo: catch filenotfound and upload then
MemoryStream remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
//todo: what happens if something fails here?
MemoryStream remoteData;
try
{
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
}
catch (FileNotFoundException)
{
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
return;
}
//check if remote file was modified:
if (cachingFileStorage.GetBaseVersionHash(ioc) != hash)
@ -49,7 +59,7 @@ namespace keepass2android
if (cachingFileStorage.HasLocalChanges(ioc))
{
//conflict! need to merge
SaveDb saveDb = new SaveDb(_context, _app, new ActionOnFinish((success, result) =>
_saveDb = new SaveDb(_context, _app, new ActionOnFinish((success, result) =>
{
if (!success)
{
@ -59,8 +69,9 @@ namespace keepass2android
{
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
}
_saveDb = null;
}), false, remoteData);
saveDb.Run();
_saveDb.Run();
}
else
{
@ -94,5 +105,11 @@ namespace keepass2android
}
}
public void JoinWorkerThread()
{
if (_saveDb != null)
_saveDb.JoinWorkerThread();
}
}
}

View File

@ -20,6 +20,8 @@ namespace Kp2aUnitTests
// Run all tests from this assembly
runner.AddTests(Assembly.GetExecutingAssembly());
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase) });
//runner.AddTests(new List<Type> { typeof(TestLoadDb) });}}
//runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) });
//runner.AddTests(typeof(TestCachingFileStorage).GetMethod("TestSaveToRemote"));
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles"));

View File

@ -168,7 +168,32 @@ namespace Kp2aUnitTests
Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
Assert.AreEqual(newContent, File.ReadAllText(CachingTestFile));
Assert.AreEqual(newContent, File.ReadAllText(CachingTestFile));
}
[TestMethod]
public void TestLoadFromRemoteWhenRemoteDeleted()
{
SetupFileStorage();
//read the file once. Should now be in the cache.
ReadToMemoryStream(_fileStorage, CachingTestFile);
//delete remote file:
_testFileStorage.DeleteFile(IocForCacheFile);
//read again. shouldn't throw and give the same result:
var memStream = ReadToMemoryStream(_fileStorage, CachingTestFile);
//check if we received the correct content:
Assert.AreEqual(_defaultCacheFileContents, MemoryStreamToString(memStream));
Assert.IsTrue(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled);
Assert.IsFalse(_testCacheSupervisor.RestoredRemoteCalled);
}
private void WriteContentToCacheFile(string newContent)

View File

@ -1,10 +1,13 @@
using System.Linq;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using Android.App;
using Android.OS;
using KeePassLib.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using keepass2android;
using keepass2android.Io;
namespace Kp2aUnitTests
{
@ -91,6 +94,135 @@ namespace Kp2aUnitTests
}
[TestMethod]
public void LoadFromRemote1and1()
{
var ioc = RemoteIoc1and1; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
IKp2aApp app = new TestKp2aApp();
app.CreateNewDatabase();
bool loadSuccesful = false;
LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) =>
{
if (!success)
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
loadSuccesful = success;
})
);
ProgressTask pt = new ProgressTask(app, Application.Context, task);
Android.Util.Log.Debug("KP2ATest", "Running ProgressTask");
pt.Run();
pt.JoinWorkerThread();
Android.Util.Log.Debug("KP2ATest", "PT.run finished");
Assert.IsTrue(loadSuccesful, "didn't succesfully load database :-(");
}
[TestMethod]
public void LoadFromRemote1and1NonExisting()
{
var ioc = RemoteIoc1and1NonExisting; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
IKp2aApp app = new TestKp2aApp();
app.CreateNewDatabase();
bool loadSuccesful = false;
bool gotError = false;
LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) =>
{
if (!success)
{
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
gotError = true;
}
loadSuccesful = success;
})
);
ProgressTask pt = new ProgressTask(app, Application.Context, task);
Android.Util.Log.Debug("KP2ATest", "Running ProgressTask");
pt.Run();
pt.JoinWorkerThread();
Android.Util.Log.Debug("KP2ATest", "PT.run finished");
Assert.IsFalse(loadSuccesful);
Assert.IsTrue(gotError);
}
[TestMethod]
public void LoadFromRemote1and1WrongCredentials()
{
var ioc = RemoteIoc1and1WrongCredentials; //note: this property is defined in "TestLoadDbCredentials.cs" which is deliberately excluded from Git because the credentials are not public!
IKp2aApp app = new TestKp2aApp();
app.CreateNewDatabase();
bool loadSuccesful = false;
bool gotError = false;
LoadDb task = new LoadDb(app, ioc, "test", null, new ActionOnFinish((success, message) =>
{
if (!success)
{
Android.Util.Log.Debug("KP2ATest", "error loading db: " + message);
gotError = true;
}
loadSuccesful = success;
})
);
ProgressTask pt = new ProgressTask(app, Application.Context, task);
Android.Util.Log.Debug("KP2ATest", "Running ProgressTask");
pt.Run();
pt.JoinWorkerThread();
Android.Util.Log.Debug("KP2ATest", "PT.run finished");
Assert.IsFalse(loadSuccesful);
Assert.IsTrue(gotError);
}
[TestMethod]
public void FileNotFoundExceptionWithWebDav()
{
var fileStorage = new BuiltInFileStorage();
//should work:
using (var stream = fileStorage.OpenFileForRead(RemoteIoc1and1))
{
stream.CopyTo(new MemoryStream());
}
//shouldn't give FileNotFound:
bool gotException = false;
try
{
using (var stream = fileStorage.OpenFileForRead(RemoteIoc1and1WrongCredentials))
{
stream.CopyTo(new MemoryStream());
}
}
catch (FileNotFoundException)
{
Assert.Fail("shouldn't get FileNotFound with wrong credentials");
}
catch (Exception e)
{
Kp2aLog.Log("received "+e);
gotException = true;
}
Assert.IsTrue(gotException);
//should give FileNotFound:
gotException = false;
try
{
using (var stream = fileStorage.OpenFileForRead(RemoteIoc1and1NonExisting))
{
stream.CopyTo(new MemoryStream());
}
}
catch (FileNotFoundException)
{
gotException = true;
}
Assert.IsTrue(gotException);
}
[TestMethod]
public void TestLoadKdbpWithPasswordOnly()
{

View File

@ -22,15 +22,6 @@ namespace Kp2aUnitTests
private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor();
private TestFileStorage _testFileStorage = new TestFileStorage();
[TestMethod]
public void TestTodos()
{
Assert.IsFalse(true, "Wird immer ManagedTransform benutzt??");
Assert.IsFalse(true, "TODOs in SyncDb");
Assert.IsFalse(true, "FileNotFound");
Assert.IsFalse(true, "Test merge files");
}
protected override TestKp2aApp CreateTestKp2aApp()
{
TestKp2aApp app = base.CreateTestKp2aApp();
@ -100,6 +91,87 @@ namespace Kp2aUnitTests
AssertDatabasesAreEqual(app.GetDb().KpDatabase, appRemoteLoaded.GetDb().KpDatabase);
}
[TestMethod]
public void TestSyncWhenRemoteDeleted()
{
//create the default database:
TestKp2aApp app = SetupAppWithDefaultDatabase();
IOConnection.DeleteFile(new IOConnectionInfo {Path = DefaultFilename});
//save it and reload it so we have a base version ("remote" and in the cache)
SaveDatabase(app);
app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
//delete remote:
IOConnection.DeleteFile(new IOConnectionInfo { Path = DefaultFilename });
string resultMessage;
bool wasSuccessful;
//sync:
Synchronize(app, out wasSuccessful, out resultMessage);
Assert.IsTrue(wasSuccessful);
Assert.AreEqual(resultMessage, app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
//ensure the file is back here:
var app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
AssertDatabasesAreEqual(app.GetDb().KpDatabase, app2.GetDb().KpDatabase);
}
[TestMethod]
public void TestSyncWhenConflict()
{
//create the default database:
TestKp2aApp app = SetupAppWithDefaultDatabase();
IOConnection.DeleteFile(new IOConnectionInfo {Path = DefaultFilename});
//save it and reload it so we have a base version ("remote" and in the cache)
SaveDatabase(app);
app = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
var app2 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
app2.FileStorage = _testFileStorage; //give app2 direct access to the remote file
//go offline:
_testFileStorage.Offline = true;
string resultMessage;
bool wasSuccessful;
//modify the database by adding a group in both apps:
PwGroup newGroup1 = new PwGroup(true, true, "TestGroup", PwIcon.Apple);
app.GetDb().KpDatabase.RootGroup.AddGroup(newGroup1, true);
PwGroup newGroup2 = new PwGroup(true, true, "TestGroupApp2", PwIcon.Apple);
app2.GetDb().KpDatabase.RootGroup.AddGroup(newGroup2, true);
//save the database again (will be saved locally only for "app")
SaveDatabase(app);
Assert.IsTrue(_testCacheSupervisor.CouldntSaveToRemoteCalled);
_testCacheSupervisor.CouldntSaveToRemoteCalled = false;
//go online again:
_testFileStorage.Offline = false;
//...and remote only for "app2":
SaveDatabase(app2);
//try to sync:
Synchronize(app, out wasSuccessful, out resultMessage);
Assert.IsTrue(wasSuccessful);
Assert.AreEqual(UiStringKey.SynchronizedDatabaseSuccessfully.ToString(), resultMessage);
//build app2 with the newGroup1:
app2.GetDb().KpDatabase.RootGroup.AddGroup(newGroup1, true);
var app3 = LoadDatabase(DefaultFilename, DefaultPassword, DefaultKeyfile);
AssertDatabasesAreEqual(app.GetDb().KpDatabase, app2.GetDb().KpDatabase);
AssertDatabasesAreEqual(app.GetDb().KpDatabase, app3.GetDb().KpDatabase);
}
private void Synchronize(TestKp2aApp app, out bool wasSuccessful, out string resultMessage)
{
bool success = false;
@ -110,6 +182,7 @@ namespace Kp2aUnitTests
result = _result;
}));
sync.Run();
sync.JoinWorkerThread();
wasSuccessful = success;
resultMessage = result;
}

View File

@ -1302,32 +1302,32 @@ namespace keepass2android
// aapt resource value: 0x7f0800fa
public const int BinaryDirectory_title = 2131230970;
// aapt resource value: 0x7f080143
public const int ChangeLog = 2131231043;
// aapt resource value: 0x7f080142
public const int ChangeLog_0_7 = 2131231042;
// aapt resource value: 0x7f080140
public const int ChangeLog_0_8 = 2131231040;
// aapt resource value: 0x7f08013f
public const int ChangeLog_0_8_1 = 2131231039;
// aapt resource value: 0x7f08013e
public const int ChangeLog_0_8_2 = 2131231038;
// aapt resource value: 0x7f08013d
public const int ChangeLog = 2131231037;
public const int ChangeLog_0_8_3 = 2131231037;
// aapt resource value: 0x7f08013c
public const int ChangeLog_0_7 = 2131231036;
public const int ChangeLog_0_8_4 = 2131231036;
// aapt resource value: 0x7f08013a
public const int ChangeLog_0_8 = 2131231034;
// aapt resource value: 0x7f080139
public const int ChangeLog_0_8_1 = 2131231033;
// aapt resource value: 0x7f080138
public const int ChangeLog_0_8_2 = 2131231032;
// aapt resource value: 0x7f080137
public const int ChangeLog_0_8_3 = 2131231031;
// aapt resource value: 0x7f080136
public const int ChangeLog_0_8_4 = 2131231030;
// aapt resource value: 0x7f080141
public const int ChangeLog_keptDonate = 2131231041;
// aapt resource value: 0x7f08013b
public const int ChangeLog_keptDonate = 2131231035;
// aapt resource value: 0x7f080135
public const int ChangeLog_title = 2131231029;
public const int ChangeLog_title = 2131231035;
// aapt resource value: 0x7f08002a
public const int CheckForFileChangesOnSave_key = 2131230762;
@ -1359,9 +1359,15 @@ namespace keepass2android
// aapt resource value: 0x7f080129
public const int DeletingGroup = 2131231017;
// aapt resource value: 0x7f080136
public const int DownloadingRemoteFile = 2131231030;
// aapt resource value: 0x7f080083
public const int FileNotFound = 2131230851;
// aapt resource value: 0x7f080139
public const int FilesInSync = 2131231033;
// aapt resource value: 0x7f080096
public const int InvalidPassword = 2131230870;
@ -1437,6 +1443,9 @@ namespace keepass2android
// aapt resource value: 0x7f0800e5
public const int RememberRecentFiles_title = 2131230949;
// aapt resource value: 0x7f080138
public const int RestoringRemoteFile = 2131231032;
// aapt resource value: 0x7f0800ff
public const int SaveAttachmentDialog_open = 2131230975;
@ -1482,6 +1491,12 @@ namespace keepass2android
// aapt resource value: 0x7f08002c
public const int SuggestionsURL = 2131230764;
// aapt resource value: 0x7f08013a
public const int SynchronizedDatabaseSuccessfully = 2131231034;
// aapt resource value: 0x7f080135
public const int SynchronizingCachedDatabase = 2131231029;
// aapt resource value: 0x7f080132
public const int SynchronizingDatabase = 2131231026;
@ -1506,6 +1521,9 @@ namespace keepass2android
// aapt resource value: 0x7f08012b
public const int UndoingChanges = 2131231019;
// aapt resource value: 0x7f080137
public const int UploadingFile = 2131231031;
// aapt resource value: 0x7f080027
public const int UsageCount_key = 2131230759;

View File

@ -264,7 +264,14 @@
<string name="YesSynchronize">Yes, merge</string>
<string name="NoOverwrite">No, overwrite</string>
<string name="ChangeLog_title">Change log</string>
<string name="SynchronizingCachedDatabase">Synchronizing cached database...</string>
<string name="DownloadingRemoteFile">Downloading remote file...</string>
<string name="UploadingFile">Uploading file...</string>
<string name="RestoringRemoteFile">Restoring remote file...</string>
<string name="FilesInSync">Files are in sync.</string>
<string name="SynchronizedDatabaseSuccessfully">Database synchronized successfully!</string>
<string name="ChangeLog_title">Change log</string>
<string name="ChangeLog_0_8_4">
<b>Version 0.8.4</b>\n
* External database changes are detected and merged when saving\n