Index: Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj =================================================================== diff -u -r8502d1a9cd739b227208f2f6ae6ea00fab758023 -r82b198560cfaa4d3e1d882dca9fd7859f169554e --- Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj (.../Core.Common.Gui.csproj) (revision 8502d1a9cd739b227208f2f6ae6ea00fab758023) +++ Core/Common/src/Core.Common.Gui/Core.Common.Gui.csproj (.../Core.Common.Gui.csproj) (revision 82b198560cfaa4d3e1d882dca9fd7859f169554e) @@ -162,6 +162,7 @@ + Index: Core/Common/src/Core.Common.Gui/Properties/Resources.Designer.cs =================================================================== diff -u -r71c4ff4caedb39679cd6642e2faf46ebd969dc9b -r82b198560cfaa4d3e1d882dca9fd7859f169554e --- Core/Common/src/Core.Common.Gui/Properties/Resources.Designer.cs (.../Resources.Designer.cs) (revision 71c4ff4caedb39679cd6642e2faf46ebd969dc9b) +++ Core/Common/src/Core.Common.Gui/Properties/Resources.Designer.cs (.../Resources.Designer.cs) (revision 82b198560cfaa4d3e1d882dca9fd7859f169554e) @@ -1427,6 +1427,51 @@ } /// + /// Looks up a localized string similar to Opgeslagen project initialiseren. + /// + public static string SaveProjectActivity_ProgressTextStepName_InitializeSavedProject { + get { + return ResourceManager.GetString("SaveProjectActivity_ProgressTextStepName_InitializeSavedProject", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Project opslaan. + /// + public static string SaveProjectActivity_ProgressTextStepName_SavingProject { + get { + return ResourceManager.GetString("SaveProjectActivity_ProgressTextStepName_SavingProject", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Voorbereiding opslaan. + /// + public static string SaveProjectActivity_ProgressTextStepName_StagingProject { + get { + return ResourceManager.GetString("SaveProjectActivity_ProgressTextStepName_StagingProject", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Opslaan van bestaand project. + /// + public static string SaveProjectActivity_Save_existing_project { + get { + return ResourceManager.GetString("SaveProjectActivity_Save_existing_project", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Opslaan van project. + /// + public static string SaveProjectActivity_Save_project { + get { + return ResourceManager.GetString("SaveProjectActivity_Save_project", resourceCulture); + } + } + + /// /// Looks up a localized string similar to Fout. /// public static string SelectItemDialog_buttonOk_Click_Error { Index: Core/Common/src/Core.Common.Gui/Properties/Resources.resx =================================================================== diff -u -r71c4ff4caedb39679cd6642e2faf46ebd969dc9b -r82b198560cfaa4d3e1d882dca9fd7859f169554e --- Core/Common/src/Core.Common.Gui/Properties/Resources.resx (.../Resources.resx) (revision 71c4ff4caedb39679cd6642e2faf46ebd969dc9b) +++ Core/Common/src/Core.Common.Gui/Properties/Resources.resx (.../Resources.resx) (revision 82b198560cfaa4d3e1d882dca9fd7859f169554e) @@ -619,4 +619,19 @@ Stap {0} van {1} | {2} + + Opslaan van bestaand project + + + Opslaan van project + + + Opgeslagen project initialiseren + + + Voorbereiding opslaan + + + Project opslaan + \ No newline at end of file Index: Core/Common/src/Core.Common.Gui/SaveProjectActivity.cs =================================================================== diff -u --- Core/Common/src/Core.Common.Gui/SaveProjectActivity.cs (revision 0) +++ Core/Common/src/Core.Common.Gui/SaveProjectActivity.cs (revision 82b198560cfaa4d3e1d882dca9fd7859f169554e) @@ -0,0 +1,174 @@ +// Copyright (C) Stichting Deltares 2016. All rights reserved. +// +// This file is part of Ringtoets. +// +// Ringtoets is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// +// All names, logos, and references to "Deltares" are registered trademarks of +// Stichting Deltares and remain full property of Stichting Deltares at all times. +// All rights reserved. + +using System; +using System.IO; +using Core.Common.Base.Data; +using Core.Common.Base.Service; +using Core.Common.Base.Storage; +using Core.Common.Gui.Properties; +using log4net; + +namespace Core.Common.Gui +{ + /// + /// Activity to save a . + /// + public class SaveProjectActivity : Activity + { + private readonly ILog log = LogManager.GetLogger(typeof(SaveProjectActivity)); + private readonly bool savingExistingProject; + private readonly IProject project; + private readonly string filePath; + private readonly IStoreProject storeProject; + private readonly IProjectOwner projectOwner; + private int totalNumberOfSteps; + + private bool cancel; + + /// + /// Creates a new instance of . + /// + /// The project to be saved. + /// The location to save the project to. + /// When true it indicates that + /// is already located at . When false then + /// is not already located at . + /// The object responsible for saving . + /// The object responsible for hosting . + public SaveProjectActivity(IProject project, string filePath, bool savingExistingProject, IStoreProject storeProject, IProjectOwner projectOwner) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + if (filePath == null) + { + throw new ArgumentNullException(nameof(filePath)); + } + if (storeProject == null) + { + throw new ArgumentNullException(nameof(storeProject)); + } + if (projectOwner == null) + { + throw new ArgumentNullException(nameof(projectOwner)); + } + + this.savingExistingProject = savingExistingProject; + this.project = project; + this.filePath = filePath; + this.storeProject = storeProject; + this.projectOwner = projectOwner; + + Name = savingExistingProject ? + Resources.SaveProjectActivity_Save_existing_project : + Resources.SaveProjectActivity_Save_project; + } + + protected override void OnRun() + { + cancel = false; + totalNumberOfSteps = savingExistingProject ? 1 : 2; + int currentStep = 1; + + if (!storeProject.HasStagedProject) + { + totalNumberOfSteps++; + UpdateProgressText(Resources.SaveProjectActivity_ProgressTextStepName_StagingProject, + currentStep++, + totalNumberOfSteps); + + storeProject.StageProject(project); + } + + if (cancel) + { + return; + } + + SaveProjectUncancellable(currentStep); + } + + protected override void OnCancel() + { + cancel = true; + } + + protected override void OnFinish() + { + if (State == ActivityState.Executed && !savingExistingProject) + { + InitializeProjectForNewLocation(); + } + } + + private void SaveProjectUncancellable(int currentStep) + { + try + { + UpdateProgressText(Resources.SaveProjectActivity_ProgressTextStepName_SavingProject, + currentStep, + totalNumberOfSteps); + + storeProject.SaveProjectAs(filePath); + } + catch (StorageException e) + { + log.Error(e.Message, e.InnerException); + State = ActivityState.Failed; + return; + } + catch (ArgumentException e) + { + log.Error(e.Message, e); + State = ActivityState.Failed; + return; + } + + // Override State (might be Cancelled) due to cancelling not possible + State = ActivityState.Executed; + } + + private void InitializeProjectForNewLocation() + { + UpdateProgressText(Resources.SaveProjectActivity_ProgressTextStepName_InitializeSavedProject, + totalNumberOfSteps, + totalNumberOfSteps); + + projectOwner.SetProject(project, filePath); + project.Name = Path.GetFileNameWithoutExtension(filePath); + project.NotifyObservers(); + } + + /// + /// Updates the progress text. + /// + /// A short description of the current step. + /// The number of the current step. + /// The total numbers of steps. + private void UpdateProgressText(string currentStepName, int currentStep, int totalSteps) + { + ProgressText = string.Format(Resources.Activity_UpdateProgressText_CurrentStepNumber_0_of_TotalStepsNumber_1_StepDescriptionName_2_, + currentStep, totalSteps, currentStepName); + } + } +} \ No newline at end of file Index: Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj =================================================================== diff -u -r8502d1a9cd739b227208f2f6ae6ea00fab758023 -r82b198560cfaa4d3e1d882dca9fd7859f169554e --- Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj (.../Core.Common.Gui.Test.csproj) (revision 8502d1a9cd739b227208f2f6ae6ea00fab758023) +++ Core/Common/test/Core.Common.Gui.Test/Core.Common.Gui.Test.csproj (.../Core.Common.Gui.Test.csproj) (revision 82b198560cfaa4d3e1d882dca9fd7859f169554e) @@ -138,6 +138,7 @@ + Index: Core/Common/test/Core.Common.Gui.Test/SaveProjectActivityTest.cs =================================================================== diff -u --- Core/Common/test/Core.Common.Gui.Test/SaveProjectActivityTest.cs (revision 0) +++ Core/Common/test/Core.Common.Gui.Test/SaveProjectActivityTest.cs (revision 82b198560cfaa4d3e1d882dca9fd7859f169554e) @@ -0,0 +1,541 @@ +// Copyright (C) Stichting Deltares 2016. All rights reserved. +// +// This file is part of Ringtoets. +// +// Ringtoets is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// +// All names, logos, and references to "Deltares" are registered trademarks of +// Stichting Deltares and remain full property of Stichting Deltares at all times. +// All rights reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Core.Common.Base.Data; +using Core.Common.Base.Service; +using Core.Common.Base.Storage; +using Core.Common.TestUtil; +using NUnit.Framework; +using Rhino.Mocks; + +namespace Core.Common.Gui.Test +{ + [TestFixture] + public class SaveProjectActivityTest + { + [Test] + [TestCase(true)] + [TestCase(false)] + public void Constructor_ExpectedValues(bool savingExistingProject) + { + // Setup + var mocks = new MockRepository(); + var storeProject = mocks.Stub(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + mocks.ReplayAll(); + + // Call + var activity = new SaveProjectActivity(project, "", savingExistingProject, storeProject, projectOwner); + + // Assert + Assert.IsInstanceOf(activity); + CollectionAssert.IsEmpty(activity.LogMessages); + Assert.IsNull(activity.ProgressText); + Assert.AreEqual(ActivityState.None, activity.State); + + string exitingPrefix = savingExistingProject ? "bestaand " : ""; + string expectedName = $"Opslaan van {exitingPrefix}project"; + Assert.AreEqual(expectedName, activity.Name); + + mocks.VerifyAll(); + } + + [Test] + public void Constructor_FilePathNull_ThrowsArgumentNullException() + { + // Setup + var mocks = new MockRepository(); + var storeProject = mocks.Stub(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + mocks.ReplayAll(); + + // Call + TestDelegate call = () => new SaveProjectActivity(project, null, true, storeProject, projectOwner); + + // Assert + string paramName = Assert.Throws(call).ParamName; + Assert.AreEqual("filePath", paramName); + mocks.VerifyAll(); + } + + [Test] + public void Constructor_ProjectNull_ThrowsArgumentNullException() + { + // Setup + var mocks = new MockRepository(); + var storeProject = mocks.Stub(); + var projectOwner = mocks.Stub(); + mocks.ReplayAll(); + + // Call + TestDelegate call = () => new SaveProjectActivity(null, "", true, storeProject, projectOwner); + + // Assert + string paramName = Assert.Throws(call).ParamName; + Assert.AreEqual("project", paramName); + mocks.VerifyAll(); + } + + [Test] + public void Constructor_StoreProjectNull_ThrowsArgumentNullException() + { + // Setup + var mocks = new MockRepository(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + mocks.ReplayAll(); + + // Call + TestDelegate call = () => new SaveProjectActivity(project, "", true, null, projectOwner); + + // Assert + string paramName = Assert.Throws(call).ParamName; + Assert.AreEqual("storeProject", paramName); + } + + [Test] + public void Constructor_ProjectOwnerNull_ThrowsArgumentNullException() + { + // Setup + var mocks = new MockRepository(); + var project = mocks.Stub(); + var storeProject = mocks.Stub(); + mocks.ReplayAll(); + + // Call + TestDelegate call = () => new SaveProjectActivity(project, "", true, storeProject, null); + + // Assert + string paramName = Assert.Throws(call).ParamName; + Assert.AreEqual("projectOwner", paramName); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Run_UnstagedProject_StageAndSaveProjectWithoutLogMessages(bool saveExistingProject) + { + // Setup + const string filePath = "A"; + var mocks = new MockRepository(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + var storeProject = mocks.StrictMock(); + storeProject.Stub(sp => sp.HasStagedProject) + .Return(false); + using (mocks.Ordered()) + { + storeProject.Expect(sp => sp.StageProject(project)); + storeProject.Expect(sp => sp.SaveProjectAs(filePath)); + } + mocks.ReplayAll(); + + var activity = new SaveProjectActivity(project, filePath, saveExistingProject, storeProject, projectOwner); + + // Call + Action call = () => activity.Run(); + + // Assert + TestHelper.AssertLogMessagesCount(call, 0); + Assert.AreEqual(ActivityState.Executed, activity.State); + mocks.VerifyAll(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Run_AlreadyStagedProject_SaveProjectWithoutLogMessages(bool saveExistingProject) + { + // Setup + const string filePath = "A"; + var mocks = new MockRepository(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + var storeProject = mocks.StrictMock(); + storeProject.Stub(sp => sp.HasStagedProject) + .Return(true); + storeProject.Expect(sp => sp.SaveProjectAs(filePath)); + mocks.ReplayAll(); + + var activity = new SaveProjectActivity(project, filePath, saveExistingProject, storeProject, projectOwner); + + // Call + Action call = () => activity.Run(); + + // Assert + TestHelper.AssertLogMessagesCount(call, 0); + Assert.AreEqual(ActivityState.Executed, activity.State); + mocks.VerifyAll(); + } + + [Test] + [Combinatorial] + public void Run_SaveProjectAsThrowsException_FailedWithLogMessage( + [Values(true, false)] bool saveExistingProject, + [Values(SaveProjectAsExceptionType.StorageException, + SaveProjectAsExceptionType.CouldNotConnectException, + SaveProjectAsExceptionType.ArgumentException)] SaveProjectAsExceptionType exceptionType) + { + // Setup + const string filePath = "A"; + const string message = ""; + + Exception exception = CreateException(exceptionType, message); + + var mocks = new MockRepository(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + var storeProject = mocks.Stub(); + storeProject.Stub(sp => sp.HasStagedProject) + .Return(true); + storeProject.Stub(sp => sp.SaveProjectAs(filePath)) + .Throw(exception); + mocks.ReplayAll(); + + var activity = new SaveProjectActivity(project, filePath, saveExistingProject, storeProject, projectOwner); + + // Call + Action call = () => activity.Run(); + + // Assert + TestHelper.AssertLogMessageWithLevelIsGenerated(call, Tuple.Create(message, LogLevelConstant.Error), 1); + Assert.AreEqual(ActivityState.Failed, activity.State); + mocks.VerifyAll(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Run_UnstagedProject_ExpectedProgressMessages(bool saveExistingProject) + { + // Setup + const string filePath = "A"; + var mocks = new MockRepository(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + var storeProject = mocks.StrictMock(); + storeProject.Stub(sp => sp.HasStagedProject) + .Return(false); + using (mocks.Ordered()) + { + storeProject.Expect(sp => sp.StageProject(project)); + storeProject.Expect(sp => sp.SaveProjectAs(filePath)); + } + mocks.ReplayAll(); + + var progressMessages = new List(); + + var activity = new SaveProjectActivity(project, filePath, saveExistingProject, storeProject, projectOwner); + activity.ProgressChanged += (sender, args) => + { + Assert.AreSame(activity, sender); + Assert.AreEqual(EventArgs.Empty, args); + + progressMessages.Add(activity.ProgressText); + }; + + // Call + activity.Run(); + + // Assert + int totalSteps = saveExistingProject ? 2 : 3; + var expectedProgressMessages = new[] + { + $"Stap 1 van {totalSteps} | Voorbereiding opslaan", + $"Stap 2 van {totalSteps} | Project opslaan" + }; + CollectionAssert.AreEqual(expectedProgressMessages, progressMessages); + mocks.VerifyAll(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Run_AlreadyStagedProject_ExpectedProgressMessages(bool saveExistingProject) + { + // Setup + const string filePath = "A"; + var mocks = new MockRepository(); + var project = mocks.Stub(); + var projectOwner = mocks.Stub(); + var storeProject = mocks.StrictMock(); + storeProject.Stub(sp => sp.HasStagedProject) + .Return(true); + storeProject.Expect(sp => sp.SaveProjectAs(filePath)); + mocks.ReplayAll(); + + var progressMessages = new List(); + + var activity = new SaveProjectActivity(project, filePath, saveExistingProject, storeProject, projectOwner); + activity.ProgressChanged += (sender, args) => + { + Assert.AreSame(activity, sender); + Assert.AreEqual(EventArgs.Empty, args); + + progressMessages.Add(activity.ProgressText); + }; + + // Call + activity.Run(); + + // Assert + int totalSteps = saveExistingProject ? 1 : 2; + var expectedProgressMessages = new[] + { + $"Stap 1 van {totalSteps} | Project opslaan" + }; + CollectionAssert.AreEqual(expectedProgressMessages, progressMessages); + mocks.VerifyAll(); + } + + [Test] + public void Finish_SuccessfullySavedNewProject_UpdateProjectAndProjectOwnerWithMessage() + { + // Setup + const string fileName = "A"; + string filePath = $@"C:\\folder\{fileName}.rtd"; + + var mocks = new MockRepository(); + var storeProject = mocks.Stub(); + + var project = mocks.Stub(); + project.Expect(p => p.NotifyObservers()); + + var projectOwner = mocks.StrictMock(); + projectOwner.Expect(po => po.SetProject(project, filePath)); + mocks.ReplayAll(); + + var activity = new SaveProjectActivity(project, filePath, false, storeProject, projectOwner); + activity.Run(); + + // Precondition + Assert.AreEqual(ActivityState.Executed, activity.State); + + // Call + Action call = () => activity.Finish(); + + // Assert + Tuple expectedMessage = Tuple.Create("Uitvoeren van 'Opslaan van project' is gelukt.", + LogLevelConstant.Info); + TestHelper.AssertLogMessageWithLevelIsGenerated(call, expectedMessage, 1); + Assert.AreEqual(ActivityState.Finished, activity.State); + Assert.AreEqual(fileName, project.Name); + mocks.VerifyAll(); + } + + [Test] + public void Finish_SuccessfullySavedExistingProject_DoNotUpdateProjectAndProjectOwnerWithMessage() + { + // Setup + const string fileName = "A"; + string filePath = $@"C:\\folder\{fileName}.rtd"; + + var mocks = new MockRepository(); + var storeProject = mocks.Stub(); + var project = mocks.StrictMock(); + var projectOwner = mocks.StrictMock(); + mocks.ReplayAll(); + + var activity = new SaveProjectActivity(project, filePath, true, storeProject, projectOwner); + activity.Run(); + + // Precondition + Assert.AreEqual(ActivityState.Executed, activity.State); + + // Call + Action call = () => activity.Finish(); + + // Assert + Tuple expectedMessage = Tuple.Create("Uitvoeren van 'Opslaan van bestaand project' is gelukt.", + LogLevelConstant.Info); + TestHelper.AssertLogMessageWithLevelIsGenerated(call, expectedMessage, 1); + Assert.AreEqual(ActivityState.Finished, activity.State); + mocks.VerifyAll(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Finish_SuccessfullySavedNewProject_ExpectedProgressMessages(bool hasStagedProject) + { + // Setup + const string fileName = "A"; + string filePath = $@"C:\\folder\{fileName}.rtd"; + + var mocks = new MockRepository(); + var project = mocks.Stub(); + var storeProject = mocks.Stub(); + storeProject.Stub(sp => sp.HasStagedProject).Return(hasStagedProject); + storeProject.Stub(sp => sp.StageProject(project)); + storeProject.Stub(sp => sp.SaveProjectAs(filePath)); + + var projectOwner = mocks.Stub(); + mocks.ReplayAll(); + + var activity = new SaveProjectActivity(project, filePath, false, storeProject, projectOwner); + activity.Run(); + + // Precondition + Assert.AreEqual(ActivityState.Executed, activity.State); + + var progressMessages = new List(); + activity.ProgressChanged += (sender, args) => + { + Assert.AreSame(activity, sender); + Assert.AreEqual(EventArgs.Empty, args); + + progressMessages.Add(activity.ProgressText); + }; + + // Call + activity.Finish(); + + // Assert + int totalSteps = hasStagedProject ? 2 : 3; + var expectedProgressMessages = new[] + { + $"Stap {totalSteps} van {totalSteps} | Opgeslagen project initialiseren" + }; + CollectionAssert.AreEqual(expectedProgressMessages, progressMessages); + mocks.VerifyAll(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void GivenActivityStagingProject_WhenCancelling_ThenProjectNotSavedWithLogMessage(bool saveExistingProject) + { + // Given + const string filePath = "A"; + var mocks = new MockRepository(); + var project = mocks.Stub(); + var projectOwner = mocks.StrictMock(); + var storeProject = mocks.StrictMock(); + storeProject.Stub(sp => sp.HasStagedProject) + .Return(false); + using (mocks.Ordered()) + { + storeProject.Expect(sp => sp.StageProject(project)); + storeProject.Expect(sp => sp.SaveProjectAs(filePath)) + .Repeat.Never(); + } + mocks.ReplayAll(); + + var activity = new SaveProjectActivity(project, filePath, saveExistingProject, storeProject, projectOwner); + activity.ProgressChanged += (sender, args) => activity.Cancel(); + + // When + Action call = () => + { + activity.Run(); // Cancel called mid-progress + activity.Finish(); + }; + + // Then + string prefix = saveExistingProject ? "bestaand " : ""; + TestHelper.AssertLogMessageWithLevelIsGenerated(call, + Tuple.Create($"Uitvoeren van 'Opslaan van {prefix}project' is geannuleerd.", + LogLevelConstant.Warn), + 1); + + Assert.AreEqual(ActivityState.Canceled, activity.State); + mocks.VerifyAll(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void GivenActivitySavingStagedProject_WhenCancelling_ThenProjectSavedWithLogMessage(bool saveExistingProject) + { + // Given + const string filePath = "A"; + var mocks = new MockRepository(); + var project = mocks.Stub(); + + var projectOwner = mocks.Stub(); + if (!saveExistingProject) + { + projectOwner.Expect(po => po.SetProject(project, filePath)); + } + + var storeProject = mocks.StrictMock(); + storeProject.Stub(sp => sp.HasStagedProject) + .Return(true); + storeProject.Expect(sp => sp.SaveProjectAs(filePath)); + mocks.ReplayAll(); + + bool calledCancel = false; + var activity = new SaveProjectActivity(project, filePath, saveExistingProject, storeProject, projectOwner); + activity.ProgressChanged += (sender, args) => + { + if (calledCancel) + { + activity.Cancel(); + calledCancel = true; + } + }; + + // When + Action call = () => + { + activity.Run(); // Cancel called mid-progress but beyond 'point of no return' + activity.Finish(); + }; + + // Then + string prefix = saveExistingProject ? "bestaand " : ""; + TestHelper.AssertLogMessageWithLevelIsGenerated(call, + Tuple.Create($"Uitvoeren van 'Opslaan van {prefix}project' is gelukt.", + LogLevelConstant.Info), + 1); + + Assert.AreEqual(ActivityState.Finished, activity.State); + mocks.VerifyAll(); + } + + private static Exception CreateException(SaveProjectAsExceptionType exceptionType, string message) + { + switch (exceptionType) + { + case SaveProjectAsExceptionType.ArgumentException: + return new ArgumentException(message); + case SaveProjectAsExceptionType.CouldNotConnectException: + return new CouldNotConnectException(message); + case SaveProjectAsExceptionType.StorageException: + return new StorageException(message); + default: + throw new InvalidEnumArgumentException(); + } + } + + public enum SaveProjectAsExceptionType + { + CouldNotConnectException, + StorageException, + ArgumentException + } + } +} \ No newline at end of file