public void HandleNewBook_RenamedBook_AddsRenameMessage() { using (var collectionFolder = new TemporaryFolder("HandleNewBook_RenamedBook_AddsRenameMessage_Collection")) { using (var repoFolder = new TemporaryFolder("HandleNewBook_RenamedBook_AddsRenameMessage_Shared")) { var bookFolderName1 = "Renamed book"; var localBookFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, bookFolderName1, "Something"); var mockTcManager = new Mock <ITeamCollectionManager>(); var tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(collectionFolder.FolderPath)); var tc = new TestFolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath, tcLog); tc.PutBook(localBookFolderPath); SIL.IO.RobustIO.MoveDirectory(localBookFolderPath, Path.Combine(collectionFolder.FolderPath, "old name")); // We could rename the book file too, but it doesn't matter for the current SUT tc.HandleNewBook(new NewBookEventArgs() { BookFileName = "Renamed book.bloom" }); var msg = tcLog.Messages[0]; Assert.That(msg.RawEnglishMessageTemplate, Is.EqualTo("The book \"{0}\" has been renamed to \"{1}\" by a teammate.")); Assert.That(msg.Param0, Is.EqualTo("old name")); Assert.That(msg.Param1, Is.EqualTo("Renamed book")); } } }
public void HandleNewBook_AddsMessage_IffReallyNew(bool reallyNew) { using (var collectionFolder = new TemporaryFolder("HandleNewBook_NewBook_AddsMessage_Collection")) { using (var repoFolder = new TemporaryFolder("HandleNewBook_NewBook_AddsMessage_Shared")) { var bookFolderName1 = "New book"; var localBookFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, bookFolderName1, "Something"); var mockTcManager = new Mock <ITeamCollectionManager>(); var tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(collectionFolder.FolderPath)); var tc = new TestFolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath, tcLog); tc.PutBook(localBookFolderPath); if (reallyNew) { SIL.IO.RobustIO.DeleteDirectory(localBookFolderPath, true); } tc.HandleNewBook(new NewBookEventArgs() { BookFileName = "New book.bloom" }); if (reallyNew) { var msg = tcLog.Messages[0]; Assert.That(msg.RawEnglishMessageTemplate, Is.EqualTo("A new book called '{0}' was added by a teammate.")); } else { Assert.That(tcLog.Messages.Count, Is.EqualTo(0)); } } } }
public void AnyBooksCheckedOutHereByCurrentUser_TrueOnlyForRealCheckouts() { using (var collectionFolder = new TemporaryFolder("AnyBooksCheckedOutHereByCurrentUser_TrueOnlyForRealCheckouts")) { using (var repoFolder = new TemporaryFolder("AnyBooksCheckedOutHereByCurrentUser_TrueOnlyForRealCheckouts")) { TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); var bookFolderName1 = "A very nice book book"; var localBookFolderPath = SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, bookFolderName1, "Something"); var mockTcManager = new Mock <ITeamCollectionManager>(); var tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(collectionFolder.FolderPath)); var tc = new TestFolderTeamCollection(mockTcManager.Object, collectionFolder.FolderPath, repoFolder.FolderPath, tcLog); SyncAtStartupTests.MakeFakeBook(collectionFolder.FolderPath, "Another nice book", "Something"); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.False); // both currently local-only tc.PutBook(localBookFolderPath); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.False); // one local-only, one checked in tc.AttemptLock(bookFolderName1, TeamCollectionManager.CurrentUser); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.True); // one local-only, one checked out tc.PutBook(localBookFolderPath, checkin: true); tc.AttemptLock(bookFolderName1, "someoneElse.somewhere.org"); Assert.That(tc.AnyBooksCheckedOutHereByCurrentUser, Is.False); // one local-only, one checked out but to someone else. } } }
public void Setup() { TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _sharedFolder = new TemporaryFolder("TeamCollection_Shared"); _collectionFolder = new TemporaryFolder("TeamCollection_Local"); _tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(_collectionFolder.FolderPath)); FolderTeamCollection.CreateTeamCollectionLinkFile(_collectionFolder.FolderPath, _sharedFolder.FolderPath); _mockTcManager = new Mock <ITeamCollectionManager>(); _collection = new FolderTeamCollection(_mockTcManager.Object, _collectionFolder.FolderPath, _sharedFolder.FolderPath, _tcLog); _collection.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); }
public void Setup() { _logFile = new TempFile(); _messageLog = new TeamCollectionMessageLog(_logFile.Path); }
public void OneTimeSetup() { _repoFolder = new TemporaryFolder("SyncAtStartup_Repo"); _collectionFolder = new TemporaryFolder("SyncAtStartup_Local"); FolderTeamCollection.CreateTeamCollectionLinkFile(_collectionFolder.FolderPath, _repoFolder.FolderPath); _mockTcManager = new Mock <ITeamCollectionManager>(); _tcLog = new TeamCollectionMessageLog(TeamCollectionManager.GetTcLogPathFromLcPath(_collectionFolder.FolderPath)); _collection = new FolderTeamCollection(_mockTcManager.Object, _collectionFolder.FolderPath, _repoFolder.FolderPath, tcLog: _tcLog); _collection.CollectionId = Bloom.TeamCollection.TeamCollection.GenerateCollectionId(); TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); // Simulate a book that was once shared, but has been deleted from the repo folder (has a tombstone). MakeBook("Should be deleted", "This should be deleted as it has local status but is not shared", true); var bookFolderPath = Path.Combine(_collectionFolder.FolderPath, "Should be deleted"); _collection.DeleteBookFromRepo(bookFolderPath); // Simulate a book that was once shared, but has been deleted from the repo folder. But there is no tombstone. // (Despite the name, it is only converted to a new local in the default case. When we do a First Time Join, // it just gets copied into the repo.) MakeBook("Should be converted to new local", "This should become a new local (no status) book as it has local status but is not in the repo", true); var delPath = Path.Combine(_repoFolder.FolderPath, "Books", "Should be converted to new local.bloom"); RobustFile.Delete(delPath); // Simulate a book newly created locally. Not in repo, but should not be deleted. MakeBook("A book", "This should survive as it has no local status", false); // By the way, like most new books, it got renamed early in life...twice SimulateRename(_collection, "A book", "An early name"); SimulateRename(_collection, "An early name", "New book"); // Simulate a book that needs nothing done to it. It's the same locally and on the repo. MakeBook("Keep me", "This needs nothing done to it"); // Simulate a book that is checked out locally to the current user, but the file has // been deleted on the repo. MakeBook("Keep me too", "This also needs nothing done", false); _collection.WriteLocalStatus("Keep me too", new BookStatus().WithLockedBy("*****@*****.**")); // Simlulate a book that is only in the team repo MakeBook("Add me", "Fetch to local"); var delPathAddMe = Path.Combine(_collectionFolder.FolderPath, "Add me"); SIL.IO.RobustIO.DeleteDirectoryAndContents(delPathAddMe); // Simulate a book that was checked in, then checked out again and renamed, // but not yet checked in. Both "A renamed book" folder and content and "An old name.bloom" // should survive. (Except for an obscure reason when joining a TC...see comment in the test.) MakeBook("An old name", "Should be kept in both places with different names"); _collection.AttemptLock("An old name", "*****@*****.**"); SimulateRename(_collection, "An old name", "an intermediate name"); SimulateRename(_collection, "an intermediate name", "A renamed book"); // Simulate a book that is not checked out locally and has been modified elsewhere MakeBook("Update me", "Needs to be become this locally"); UpdateLocalBook("Update me", "This is supposed to be an older value, not edited locally"); // Simulate a book that is checked out locally but not in the repo, and where the saved local // checksum equals the repo checksum, and it is not checked out in the repo. This would // typically indicate that someone remote forced a checkout, perhaps while this user was // offline, but checked in again without making changes. // Also pretend it has been modified locally. // Test result: collection is updated to indicate the local checkout. Local changes are not lost. MakeBook("Check me out", "Local and remote checksums correspond to this"); UpdateLocalBook("Check me out", "This is supposed to be a newer value from local editing", false); var oldLocalStatus = _collection.GetLocalStatus("Check me out"); var newLocalStatus = oldLocalStatus.WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser); _checkMeOutOriginalChecksum = oldLocalStatus.checksum; _collection.WriteLocalStatus("Check me out", newLocalStatus); // Simulate a book that appears newly-created locally (no local status) but is also in the // repo. This would indicate two people coincidentally creating a book with the same name. // Test result: the local book should get renamed (both folder and htm). // When merging while joining a new TC, this case is treated as a conflict and the // local book is moved to Lost and Found. MakeBook("Rename local", "This content is on the server"); _collection.AttemptLock("Rename local", "*****@*****.**"); UpdateLocalBook("Rename local", "This is a new book created independently"); var statusFilePath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath("Rename local", _collectionFolder.FolderPath); RobustFile.Delete(statusFilePath); // Simulate a book that is checked out locally but also checked out, to a different user // or machine, on the repo. This would indicate some sort of manual intervention, perhaps // while this user was long offline. The book has not been modified locally, but the local // status is out of date. // Test result: local status is updated to reflect the remote checkout, book content updated to repo. MakeBook("Update and undo checkout", "This content is everywhere"); _collection.AttemptLock("Update and undo checkout", "*****@*****.**"); _collection.WriteLocalStatus("Update and undo checkout", _collection.GetStatus("Update and undo checkout").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser)); // Simulate a book that is checked out locally and not on the server, but the repo and (old) // local checksums are different. The book has not been edited locally. // Test result: book is updated to match repo. Local and remote status should match...review: which wins? MakeBook("Update and checkout", "This content is on the server"); UpdateLocalBook("Update and checkout", "This simulates older content changed remotely but not locally"); _collection.WriteLocalStatus("Update and checkout", _collection.GetLocalStatus("Update and checkout").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser)); // Simulate a book that is checked out and modified locally, but has also been modified // remotely. // Test result: current local state is saved in lost-and-found. Repo version of book and state // copied to local. Warning to user. MakeBook("Update content and status and warn", "This simulates new content on server"); _collection.AttemptLock("Update content and status and warn", "*****@*****.**"); UpdateLocalBook("Update content and status and warn", "This is supposed to be the newest value from local editing"); var newStatus = _collection.GetStatus("Update content and status and warn").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser) .WithChecksum("different from either"); _collection.WriteLocalStatus("Update content and status and warn", newStatus); // Simulate a book that is checked out and modified locally, but is also checked out by another // user or machine in the repo. It has not (yet) been modified remotely. // Test result: current local state is saved in lost-and-found. Repo version of book and state // copied to local. Warning to user. MakeBook("Update content and status and warn2", "This simulates new content on server"); _collection.AttemptLock("Update content and status and warn2", "*****@*****.**"); UpdateLocalBook("Update content and status and warn2", "This is supposed to be the newest value from local editing", false); newStatus = _collection.GetStatus("Update content and status and warn2").WithLockedBy(Bloom.TeamCollection.TeamCollectionManager.CurrentUser); _collection.WriteLocalStatus("Update content and status and warn2", newStatus); // Simulate a book which has no local status, but for which the computed checksum matches // the repo one. This could happen if a user obtained the same book independently, // or during initial merging of a local and team collection, where much of the material // was previously duplicated. // Test result: status is copied to local MakeBook("copy status", "Same content in both places"); _collection.AttemptLock("copy status", "*****@*****.**"); statusFilePath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath("copy status", _collectionFolder.FolderPath); RobustFile.Delete(statusFilePath); // Simulate a book that was copied from another TC, using File Explorer. // It therefore has a book.status file, but with a different guid. // Test result: it should survive, and on a new collection sync get copied into the repo var copiedEx = "copied with Explorer"; MakeBook(copiedEx, "This content is only local", false); _collection.WriteLocalStatus(copiedEx, new BookStatus(), collectionId: Bloom.TeamCollection.TeamCollection.GenerateCollectionId()); // Simulate a book that appeared in DropBox when their software found a conflict. // It should NOT be copied locally, but instead moved to Lost and Found, with a report. MakeBook(kConflictName, "This content is only on the repo, apart from conflicting copies"); var conflictFolderPath = Path.Combine(_collectionFolder.FolderPath, kConflictName); SIL.IO.RobustIO.DeleteDirectoryAndContents(conflictFolderPath); _collection.WriteLocalStatus(copiedEx, new BookStatus(), collectionId: Bloom.TeamCollection.TeamCollection.GenerateCollectionId()); // Simulate a corrupt zip file, only in the repo File.WriteAllText(Path.Combine(_repoFolder.FolderPath, "Books", "new corrupt book.bloom"), "This is not a valid zip!"); // Simulate a corrupt zip file that corresponds to a local book. var badZip = "has a bad zip in repo"; MakeBook(badZip, "This book seems to be in both places, but the repo is corrupt"); File.WriteAllText(Path.Combine(_repoFolder.FolderPath, "Books", badZip + ".bloom"), "This is also not a valid zip!"); // Simulate a book that was renamed remotely. That is, there's a local book Old Name, with local status, // and there's no repo book by that name, but there's a repo book New Name (and no such local book). // The book's meta.json indicates they are the same book. // We'll initially make both, with the new name and new content. MakeBook(kNewNameForRemoteRename, "This is the new book content after remote editing and rename"); var oldFolder = Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely); var newFolder = Path.Combine(_collectionFolder.FolderPath, kNewNameForRemoteRename); RobustIO.MoveDirectory(newFolder, oldFolder); // made at new path, simulate still at old. var oldPath = Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely, kBookRenamedRemotely + ".htm"); // simulate old book name and content RobustFile.WriteAllText(oldPath, "This is the simulated original book content"); RobustFile.Delete(Path.Combine(_collectionFolder.FolderPath, kBookRenamedRemotely, kNewNameForRemoteRename + ".htm")); // get rid of the 'new' content // Simulate a book that is in the repo, where there is a local book that has no status, a different name, // and the same ID. This might indicate (a) that it was renamed by someone else after this user's pre-TC // copy of the collection diverged; (b) that it was renamed by this user after the divergence; // (c) that they were independently copied from some common-ID source. // We will treat this as a conflict, moving the local version to lost and found, even on a first time join. MakeBook(kRepoNameForIdConflict, "This is the repo version of a book that has a no-status local book with the same ID."); // Move the local version to a new folder var oldFolder2 = Path.Combine(_collectionFolder.FolderPath, kRepoNameForIdConflict); var newFolder2 = Path.Combine(_collectionFolder.FolderPath, kLocalNameForIdConflict); RobustIO.MoveDirectory(oldFolder2, newFolder2); var localStatusPath = Bloom.TeamCollection.TeamCollection.GetStatusFilePath(kLocalNameForIdConflict, _collectionFolder.FolderPath); RobustFile.Delete(localStatusPath); // Make a couple of folders that are legitimately present, but not books. var allowedWords = Path.Combine(_collectionFolder.FolderPath, "Allowed Words"); Directory.CreateDirectory(allowedWords); File.WriteAllText(Path.Combine(allowedWords, "some sample.txt"), "This a fake word list"); var sampleTexts = Path.Combine(_collectionFolder.FolderPath, "Sample Texts"); Directory.CreateDirectory(sampleTexts); File.WriteAllText(Path.Combine(sampleTexts, "a sample.txt"), "This a fake sample text"); _progressSpy = new ProgressSpy(); // sut for the whole suite! Assert.That(_collection.SyncAtStartup(_progressSpy, FirstTimeJoin()), Is.True); }
public TestFolderTeamCollection(ITeamCollectionManager tcManager, string localCollectionFolder, string repoFolderPath, TeamCollectionMessageLog tcLog = null) : base(tcManager, localCollectionFolder, repoFolderPath, tcLog) { }