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.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Text; using System.Text;
using Android.App; using Android.App;
@ -57,7 +58,19 @@ namespace keepass2android.Io
public Stream OpenFileForRead(IOConnectionInfo ioc) 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) public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)

View File

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

View File

@ -13,6 +13,7 @@ namespace keepass2android
{ {
private readonly Context _context; private readonly Context _context;
private readonly IKp2aApp _app; private readonly IKp2aApp _app;
private SaveDb _saveDb;
public SynchronizeCachedDatabase(Context context, IKp2aApp app, OnFinish finish) public SynchronizeCachedDatabase(Context context, IKp2aApp app, OnFinish finish)
: base(finish) : base(finish)
@ -37,10 +38,19 @@ namespace keepass2android
//download file from remote location and calculate hash: //download file from remote location and calculate hash:
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile)); StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.DownloadingRemoteFile));
string hash; string hash;
//todo: catch filenotfound and upload then
MemoryStream remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash); MemoryStream remoteData;
try
//todo: what happens if something fails here? {
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: //check if remote file was modified:
if (cachingFileStorage.GetBaseVersionHash(ioc) != hash) if (cachingFileStorage.GetBaseVersionHash(ioc) != hash)
@ -49,7 +59,7 @@ namespace keepass2android
if (cachingFileStorage.HasLocalChanges(ioc)) if (cachingFileStorage.HasLocalChanges(ioc))
{ {
//conflict! need to merge //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) if (!success)
{ {
@ -59,8 +69,9 @@ namespace keepass2android
{ {
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully)); Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
} }
_saveDb = null;
}), false, remoteData); }), false, remoteData);
saveDb.Run(); _saveDb.Run();
} }
else 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 // Run all tests from this assembly
runner.AddTests(Assembly.GetExecutingAssembly()); runner.AddTests(Assembly.GetExecutingAssembly());
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase) }); //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(TestCachingFileStorage).GetMethod("TestSaveToRemote"));
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly")); //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles")); //runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles"));

View File

@ -168,7 +168,32 @@ namespace Kp2aUnitTests
Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled); Assert.IsFalse(_testCacheSupervisor.CouldntOpenFromRemoteCalled);
Assert.IsFalse(_testCacheSupervisor.CouldntSaveToRemoteCalled); 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) 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 System.Threading;
using Android.App; using Android.App;
using Android.OS; using Android.OS;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using keepass2android; using keepass2android;
using keepass2android.Io;
namespace Kp2aUnitTests 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] [TestMethod]
public void TestLoadKdbpWithPasswordOnly() public void TestLoadKdbpWithPasswordOnly()
{ {

View File

@ -22,15 +22,6 @@ namespace Kp2aUnitTests
private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor(); private TestCacheSupervisor _testCacheSupervisor = new TestCacheSupervisor();
private TestFileStorage _testFileStorage = new TestFileStorage(); 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() protected override TestKp2aApp CreateTestKp2aApp()
{ {
TestKp2aApp app = base.CreateTestKp2aApp(); TestKp2aApp app = base.CreateTestKp2aApp();
@ -100,6 +91,87 @@ namespace Kp2aUnitTests
AssertDatabasesAreEqual(app.GetDb().KpDatabase, appRemoteLoaded.GetDb().KpDatabase); 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) private void Synchronize(TestKp2aApp app, out bool wasSuccessful, out string resultMessage)
{ {
bool success = false; bool success = false;
@ -110,6 +182,7 @@ namespace Kp2aUnitTests
result = _result; result = _result;
})); }));
sync.Run(); sync.Run();
sync.JoinWorkerThread();
wasSuccessful = success; wasSuccessful = success;
resultMessage = result; resultMessage = result;
} }

View File

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

View File

@ -264,7 +264,14 @@
<string name="YesSynchronize">Yes, merge</string> <string name="YesSynchronize">Yes, merge</string>
<string name="NoOverwrite">No, overwrite</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"> <string name="ChangeLog_0_8_4">
<b>Version 0.8.4</b>\n <b>Version 0.8.4</b>\n
* External database changes are detected and merged when saving\n * External database changes are detected and merged when saving\n