public void HandleModifiedFile_NoLocalBook_DoesNothing() { // Setup // // Simulate that a book appeared remotely. We should eventually get a created notice. // Sometimes, for reasons we don't fully understand, we get a modified notice // first. Or the book might be modified again before we fetch it. In any case, // we don't need modify messages until we fetch a local copy. const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); // Writes the book to disk based on the above specified values string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); // pretending we had nothing local before the change. RobustIO.DeleteDirectory(bookFolderPath, true); // Anticipate verification var prevMessages = _tcLog.Messages.Count; _mockTcManager.Setup(m => m.RaiseBookStatusChanged(It.IsAny <BookStatusChangeEventArgs>())) .Throws(new ArgumentException("RaiseBookStatus should not be called")); // System Under Test // _collection.HandleModifiedFile(new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); // Verification Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages)); }
public void HandleDeletedFile_BookDeletedButNoTombstone_DoesNothing() { // Simulate that a book is reported as deleted in the repo (and it is), but there is no tombstone const string bookFolderName = "My other book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); // Delete but do NOT make tombstone _collection.DeleteBookFromRepo(bookFolderPath, false); var prevMessages = _tcLog.Messages.Count; var prevInvocations = _mockTcManager.Invocations.Count; // System Under Test: a valid notification, but one we want to ignore // _collection.HandleDeletedRepoFile($"{bookFolderName}.bloom"); // Verification Assert.That(_mockTcManager.Invocations.Count, Is.EqualTo(prevInvocations)); Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages)); Assert.That(Directory.Exists(bookFolderPath), Is.True, "The local book should not have been deleted"); }
public void HandleDeletedFile_BookSelected_LogsProblem() { // Simulate that a book which is currently selected was just deleted in the repo const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); // But, although we have all the status to indicate it is in the repo, it's gone! _collection.DeleteBookFromRepo(bookFolderPath); // But it can't go away locally...it's the selected book! var book = new Mock <Bloom.Book.Book>(); book.Setup(m => m.FolderPath).Returns(bookFolderPath); var selection = new Mock <BookSelection>(); selection.Setup(m => m.CurrentSelection).Returns(book.Object); _mockTcManager.Setup(m => m.BookSelection).Returns(selection.Object); var prevMessages = _tcLog.Messages.Count; var prevInvocations = _mockTcManager.Invocations.Count; // System Under Test // _collection.HandleDeletedRepoFile($"{bookFolderName}.bloom"); // Verification Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages + 1)); Assert.That(_tcLog.Messages[prevMessages].MessageType, Is.EqualTo(MessageAndMilestoneType.Error)); Assert.That(Directory.Exists(bookFolderPath), Is.True, "The local book should not have been deleted"); }
public void HandleDeletedFile_ConflictBookDeleted_LogsProblem() { // Simulate that a book which is checked out here was just deleted in the repo const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); _collection.AttemptLock(bookFolderName); // But, in spite of that, it's gone! _collection.DeleteBookFromRepo(bookFolderPath); var prevMessages = _tcLog.Messages.Count; var prevInvocations = _mockTcManager.Invocations.Count; // System Under Test // _collection.HandleDeletedRepoFile($"{bookFolderName}.bloom"); // Verification Assert.That(_mockTcManager.Invocations.Count, Is.EqualTo(prevInvocations)); Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages + 1)); Assert.That(_tcLog.Messages[prevMessages].MessageType, Is.EqualTo(MessageAndMilestoneType.ErrorNoReload)); Assert.That(Directory.Exists(bookFolderPath), Is.True, "The local book should not have been deleted"); }
public void HandleModifiedFile_NoConflictBookCheckedOutRemotely_RaisesCheckedOutByOtherButNoNewStuffMessage() { // Setup // // Simulate (sort of) that a book was just overwritten with the following new contents, // including that book.status indicates a remote checkout const string bookFolderName = "My other book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; _collection.PutBook(bookFolderPath); _collection.AttemptLock("My other book", "*****@*****.**"); // Enhance: to make it more realistic, we could write a not-checked-out-here local status, // but it's not necessary for producing the effects we want to test here. var prevMessages = _tcLog.Messages.Count; // System Under Test // _collection.HandleModifiedFile(new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); // Verification var eventArgs = (BookStatusChangeEventArgs)_mockTcManager.Invocations.Last().Arguments[0]; Assert.That(eventArgs.CheckedOutByWhom, Is.EqualTo(CheckedOutBy.Other)); Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages)); // checksums didn't change }
public void HandleDeletedFile_NoConflictBook_DeletesAndRaisesCheckedOutByDeletedButNoMessage(string checkedOutTo) { // Simulate that a book was just deleted in the repo const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); if (checkedOutTo != null) { _collection.AttemptLock(bookFolderName, checkedOutTo); } _collection.DeleteBookFromRepo(bookFolderPath); var prevMessages = _tcLog.Messages.Count; var prevInvocations = _mockTcManager.Invocations.Count; // System Under Test // _collection.HandleDeletedRepoFile($"{bookFolderName}.bloom"); // Verification // (This is a bit fragile, as it depends on how many times the method calls ANY function in the // mock TC manager, and in what order. Currently we call it once to ask for a book selection, // and then, (the call we're interested in) to raise book status changed. Assert.That(_mockTcManager.Invocations.Count, Is.EqualTo(prevInvocations + 2)); var eventArgs = (BookStatusChangeEventArgs)_mockTcManager.Invocations[prevInvocations + 1].Arguments[0]; Assert.That(eventArgs.CheckedOutByWhom, Is.EqualTo(CheckedOutBy.Deleted)); Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages)); Assert.That(Directory.Exists(bookFolderPath), Is.False, "The local book should have been deleted"); }
public void HandleModifiedFile_NoConflictBookChangedNotCheckedOut_RaisesCheckedOutByNoneAndNewStuffMessage() { // Simulate (sort of) that a book was just overwritten with the following new contents, // including that book.status does not indicate it's checked out const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .WithHtm("This is pretending to be new content from remote") .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); RobustFile.WriteAllText(bookBuilder.BuiltBookHtmPath, "This is pretending to be old content"); // pretending this is what it was before the change. _collection.WriteLocalStatus(bookFolderName, status.WithChecksum(Bloom.TeamCollection.TeamCollection.MakeChecksum(bookFolderPath))); var prevMessages = _tcLog.Messages.Count; // System Under Test // _collection.HandleModifiedFile(new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); // Verification var eventArgs = (BookStatusChangeEventArgs)_mockTcManager.Invocations[0].Arguments[0]; Assert.That(eventArgs.CheckedOutByWhom, Is.EqualTo(CheckedOutBy.None)); Assert.That(_tcLog.Messages[prevMessages].MessageType, Is.EqualTo(MessageAndMilestoneType.NewStuff)); }
public void HandleModifiedFile_NoConflictBookNotChangedCheckedOutRemoved_RaisesCheckedOutByNoneButNoNewStuffMessage() { // Setup // // Simulate (sort of) that a book was just overwritten with the following new contents, // including that book.status does not indicate it's checked out const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); // Writes the book to disk based on the above specified values string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); // pretending this is what it was before the change. _collection.WriteLocalStatus(bookFolderName, status.WithLockedBy("*****@*****.**")); var prevMessages = _tcLog.Messages.Count; // System Under Test // _collection.HandleModifiedFile(new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); // Verification var eventArgs = (BookStatusChangeEventArgs)_mockTcManager.Invocations[0].Arguments[0]; Assert.That(eventArgs.CheckedOutByWhom, Is.EqualTo(CheckedOutBy.None)); Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages)); }
public void HandleBookRename_CaseChangeOnly_WorksRight() { // Setup // const string originalBookName = "A new book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(originalBookName) .WithHtm("<html><body>This is just a dummy</body></html>") .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; string htmlPath = bookBuilder.BuiltBookHtmPath; TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _collection.PutBook(bookFolderPath); var locked = _collection.AttemptLock(originalBookName); Assert.That(locked, Is.True, "successfully checked out book to [email protected]"); // SUT: rename changes status in local collection folder, but not in shared repo folder const string newBookName = "A New Book"; var newBookFolderPath = Path.Combine(_collectionFolder.FolderPath, newBookName); File.Move(htmlPath, Path.Combine(bookFolderPath, newBookName + ".htm")); // renaming directory doesn't work when names are 'the same' var tempPath = Path.Combine(_collectionFolder.FolderPath, "tempxxyy"); Directory.Move(bookFolderPath, tempPath); Directory.Move(tempPath, newBookFolderPath); _collection.HandleBookRename(originalBookName, newBookName); _collection.PutBook(newBookFolderPath, true); var newRepoPath = Path.Combine(_sharedFolder.FolderPath, "Books", newBookName + ".bloom"); // It should not have been deleted! This is a regression test for BL-10156. // The danger is that Windows considers the old and new names the same, so after // we move the file to the new name, if we go to delete the old name, we get rid of the new one. Assert.That(File.Exists(newRepoPath)); // Did it get renamed? var matchingFiles = Directory.EnumerateFiles(Path.Combine(_sharedFolder.FolderPath, "Books"), newBookName + ".bloom").ToArray(); Assert.That(matchingFiles[0], Is.EqualTo(Path.Combine(_sharedFolder.FolderPath, "Books", newBookName + ".bloom"))); var newStatus = _collection.GetLocalStatus(newBookName); var repoStatus = _collection.GetStatus(newBookName); Assert.That(newStatus, Is.Not.Null, "local status of renamed book is not null"); Assert.That(repoStatus, Is.Not.Null, "repo status of renamed book is not null"); Assert.That(newStatus.checksum, Is.EqualTo(repoStatus.checksum), "checksums of local and remote match after rename"); Assert.That(newStatus.lockedBy, Is.EqualTo(null), "lockedBy of local and remote match after rename"); Assert.That(newStatus.oldName, Is.Null, "local status has original name cleared after commit"); }
// Make a very trivial fake book. Not nearly good enough to make a Book object from, // but enough for most purposes of testing TeamCollection. public static string MakeFakeBook(string collectionFolder, string name, string content, string folderNameIfDifferent = null) { var bookBuilder = new BookFolderBuilder() .WithRootFolder(collectionFolder) .WithBookFolderName(folderNameIfDifferent) .WithTitle(name) .WithHtm("<html><body>" + content + "</body></html>") .Build(); return(bookBuilder.BuiltBookFolderPath); }
public void DeleteBookFromRepo_CreatesTombstone() { const string bookFolderName = "Delete away"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); _collection.DeleteBookFromRepo(bookFolderPath); Assert.That(_collection.KnownToHaveBeenDeleted("Delete away"), Is.True); }
public void HandleModifiedFile_CheckedOutToMe_ContentChangedRemotely_RaisesCheckedOutByNoneAndErrorMessage() { // Setup // // Simulate a book that was checked out and modified by me, but then we get a remote change // notification. const string bookFolderName = "My conflicting change book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .WithHtm("We will be simulating a remote change to this.") .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; string bookPath = bookBuilder.BuiltBookHtmPath; _collection.PutBook(bookFolderPath); var pathToBookFileInRepo = _collection.GetPathToBookFileInRepo(bookFolderName); // Save the data we will eventually write back to the .bloom file to simulate the remote change. var remoteContent = RobustFile.ReadAllBytes(pathToBookFileInRepo); _collection.AttemptLock(bookFolderName); RobustFile.WriteAllText(bookPath, "Pretend this was the state when we checked it out."); _collection.PutBook(bookFolderPath); RobustFile.WriteAllText(bookPath, "This is a further change locally, not checked in anywhere"); // But now it's been changed remotely to the other state. (Ignore the fact that it was a previous local state; // that was just a trick to get a valid alternative state.) RobustFile.WriteAllBytes(pathToBookFileInRepo, remoteContent); var prevMessages = _tcLog.Messages.Count; // System Under Test // _collection.HandleModifiedFile(new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); // Verification var eventArgs = (BookStatusChangeEventArgs)_mockTcManager.Invocations[0].Arguments[0]; Assert.That(eventArgs.CheckedOutByWhom, Is.EqualTo(CheckedOutBy.None)); Assert.That(_tcLog.Messages[prevMessages].MessageType, Is.EqualTo(MessageAndMilestoneType.Error)); Assert.That(_tcLog.Messages[prevMessages].L10NId, Is.EqualTo("TeamCollection.EditedFileChangedRemotely")); }
public void HandleModifiedFile_CheckedOutToMe_RemotelyToOther_RaisesCheckedOutByOtherAndErrorMessage() { // Setup // // Simulate a book was just overwritten with contents indicating a remote checkout, // while locally it is checked out to me. const string bookFolderName = "My conflict book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _collection.PutBook(bookFolderPath); // Temporarily, it looks locked by Nancy in both places. _collection.AttemptLock("My conflict book", "*****@*****.**"); var status = _collection.GetStatus("My conflict book").WithLockedBy(TeamCollectionManager.CurrentUser); // Now it is locally checked out to me. (The state changes are in the opposite order to what // we're trying to simulate, because we don't have an easy way to change remote checkout status without // changing local status to match at the same time.) _collection.WriteLocalStatus("My conflict book", status); var prevMessages = _tcLog.Messages.Count; // System Under Test...basically HandleModifiedFile, but this is a convenient place to // make sure we take the right path through this calling method. _collection.QueuePendingBookChange( new BookRepoChangeEventArgs() { BookFileName = $"{bookFolderName}.bloom" }); _collection.HandleRemoteBookChangesOnIdle(null, new EventArgs()); // Verification var eventArgs = (BookStatusChangeEventArgs)_mockTcManager.Invocations[2].Arguments[0]; Assert.That(eventArgs.CheckedOutByWhom, Is.EqualTo(CheckedOutBy.Other)); Assert.That(_tcLog.Messages[prevMessages].MessageType, Is.EqualTo(MessageAndMilestoneType.Error)); Assert.That(_tcLog.Messages[prevMessages].L10NId, Is.EqualTo("TeamCollection.ConflictingCheckout")); TeamCollectionManager.ForceCurrentUserForTests(null); }
public void HandleNewBook_CreatesNewStuffMessage() { var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle("My new book") .Build(); // Writes the book to disk based on the above specified values string bookFolderPath = bookBuilder.BuiltBookFolderPath; // Gets the location the book was written to _collection.PutBook(bookFolderPath); SIL.IO.RobustIO.DeleteDirectoryAndContents(bookFolderPath); var prevMessages = _tcLog.Messages.Count; // SUT: We're mainly testing HandleNewbook, but it's convenient to check that this method calls it properly. _collection.QueuePendingBookChange(new NewBookEventArgs() { BookFileName = "My new book.bloom" }); _collection.HandleRemoteBookChangesOnIdle(null, new EventArgs()); Assert.That(_tcLog.Messages[prevMessages].MessageType, Is.EqualTo(MessageAndMilestoneType.NewStuff)); }
public void HandleBookRename_CheckedOutToMe_FixesStatusProperly() { // Setup // const string originalBookName = "Hello. Goodbye!"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(originalBookName) .WithHtm("<html><body>This is just a dummy</body></html>") .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; string htmlPath = bookBuilder.BuiltBookHtmPath; TeamCollectionManager.ForceCurrentUserForTests("*****@*****.**"); _collection.PutBook(bookFolderPath); var locked = _collection.AttemptLock(originalBookName); Assert.That(locked, Is.True, "successfully checked out book to [email protected]"); // SUT: rename changes status in local collection folder, but not in shared repo folder const string newBookName = "Testing is Fun. Sometimes"; var newBookFolderPath = Path.Combine(_collectionFolder.FolderPath, newBookName); File.Move(htmlPath, Path.Combine(bookFolderPath, newBookName + ".htm")); Directory.Move(bookFolderPath, newBookFolderPath); _collection.HandleBookRename(originalBookName, newBookName); var newStatus = _collection.GetLocalStatus(newBookName); var repoStatus = _collection.GetStatus(newBookName); Assert.That(newStatus, Is.Not.Null, "local status of renamed book is not null"); Assert.That(repoStatus, Is.Not.Null, "repo status of renamed book is not null"); Assert.That(newStatus.checksum, Is.EqualTo(repoStatus.checksum), "checksums of local and remote match after rename"); Assert.That(newStatus.lockedBy, Is.EqualTo(repoStatus.lockedBy), "lockedBy of local and remote match after rename"); Assert.That(newStatus.lockedWhen, Is.EqualTo(repoStatus.lockedWhen), "lockedWhen of local and remote match after rename"); Assert.That(newStatus.lockedWhere, Is.EqualTo(repoStatus.lockedWhere), "lockedWhere of local and remote match after rename"); Assert.That(newStatus.oldName, Is.EqualTo(originalBookName), "local status has original name in oldName field after rename"); Assert.That(repoStatus.oldName, Is.Null, "repo status still has null oldName field after rename"); }
public void HandleDeletedFile_BookNotDeleted_DoesNothing() { // Simulate that a book was reported as deleted in the repo, but actually, it's still there const string bookFolderName = "My book"; var bookBuilder = new BookFolderBuilder() .WithRootFolder(_collectionFolder.FolderPath) .WithTitle(bookFolderName) .Build(); string bookFolderPath = bookBuilder.BuiltBookFolderPath; var status = _collection.PutBook(bookFolderPath); var prevMessages = _tcLog.Messages.Count; var prevInvocations = _mockTcManager.Invocations.Count; // System Under Test: a spurious notification // _collection.HandleDeletedRepoFile($"{bookFolderName}.bloom"); // Verification Assert.That(_mockTcManager.Invocations.Count, Is.EqualTo(prevInvocations)); Assert.That(_tcLog.Messages.Count, Is.EqualTo(prevMessages)); Assert.That(Directory.Exists(bookFolderPath), Is.True, "The local book should not have been deleted"); }