// 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 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 General Public License for more details. // // You should have received a copy of the GNU 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.Data; using System.Data.SQLite; using System.IO; using Application.Ringtoets.Storage.Properties; using Application.Ringtoets.Storage.TestUtil; using Core.Common.Base.Data; using Core.Common.Base.Storage; using Core.Common.TestUtil; using NUnit.Framework; using Rhino.Mocks; using UtilsResources = Core.Common.Utils.Properties.Resources; namespace Application.Ringtoets.Storage.Test { [TestFixture] public class StorageSqLiteTest { private readonly string testDataPath = TestHelper.GetTestDataPath(TestDataPath.Application.Ringtoets.Storage, "DatabaseFiles"); private readonly string tempRingtoetsFile = Path.Combine(TestHelper.GetTestDataPath(TestDataPath.Application.Ringtoets.Storage, "DatabaseFiles"), "tempProjectFile.rtd"); [Test] public void DefaultConstructor_ExpectedValues() { // Call var storage = new StorageSqLite(); // Assert Assert.IsInstanceOf(storage); Assert.AreEqual("Ringtoetsproject (*.rtd)|*.rtd", storage.FileFilter); } [Test] [TestCase(null)] [TestCase("")] [TestCase(" ")] public void LoadProject_InvalidPath_ThrowsArgumentException(string invalidPath) { // Setup string expectedMessage = String.Format("Fout bij het lezen van bestand '{0}': {1}", invalidPath, UtilsResources.Error_Path_must_be_specified); // Call TestDelegate test = () => new StorageSqLite().LoadProject(invalidPath); // Assert ArgumentException exception = Assert.Throws(test); Assert.IsInstanceOf(exception); Assert.AreEqual(expectedMessage, exception.Message); } [Test] public void LoadProject_NonExistingPath_ThrowsStorageExceptionAndCouldNotConnectException() { // Setup string nonExistingPath = "fileDoesNotExist"; string expectedMessage = String.Format(@"Fout bij het lezen van bestand '{0}': ", nonExistingPath); string expectedInnerMessage = "Het bestand bestaat niet."; // Call TestDelegate test = () => new StorageSqLite().LoadProject(nonExistingPath); // Assert StorageException exception = Assert.Throws(test); Assert.IsInstanceOf(exception); Assert.AreEqual(expectedMessage, exception.Message); Assert.IsInstanceOf(exception.InnerException); Assert.AreEqual(expectedInnerMessage, exception.InnerException.Message); } [Test] public void LoadProject_RingtoetsFileWithoutSchema_ThrowsStorageExceptionAndStorageValidationException() { // Setup string validPath = "empty.rtd"; var tempFile = Path.Combine(testDataPath, validPath); var expectedInnerMessage = String.Format(@"Het bestand '{0}' is geen geldig Ringtoets bestand.", tempFile); // Call TestDelegate test = () => new StorageSqLite().LoadProject(tempFile); // Assert StorageException exception = Assert.Throws(test); Assert.IsInstanceOf(exception.InnerException); Assert.AreEqual(expectedInnerMessage, exception.InnerException.Message); } [Test] public void LoadProject_RingtoetsFileWithTwoProjects_ThrowsStorageExceptionAndStorageValidationException() { // Setup FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { TestDelegate precondition = () => SqLiteDatabaseHelper.CreateDatabaseFile(tempRingtoetsFile, SqLiteDatabaseHelper.GetCompleteSchema()); Assert.DoesNotThrow(precondition, "Precondition failed: creating corrupt database file failed"); // Call TestDelegate test = () => new StorageSqLite().LoadProject(tempRingtoetsFile); // Assert var expectedMessage = String.Format(@"Fout bij het lezen van bestand '{0}': {1}", tempRingtoetsFile, Resources.StorageSqLite_LoadProject_Invalid_Ringtoets_database_file); StorageException exception = Assert.Throws(test); Assert.IsInstanceOf(exception); Assert.AreEqual(expectedMessage, exception.Message); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void LoadProject_CorruptRingtoetsFileThatPassesValidation_ThrowsStorageExceptionWithFullStackTrace() { // Setup var expectedMessage = String.Format(@"Fout bij het lezen van bestand '{0}': ", tempRingtoetsFile); var expectedInnerExceptionMessage = "An error occurred while executing the command definition. See the inner exception for details."; var expectedInnerExceptionInnerExceptionMessage = "SQL logic error or missing database" + Environment.NewLine + "no such table: ProjectEntity"; FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { TestDelegate precondition = () => SqLiteDatabaseHelper.CreateDatabaseFile(tempRingtoetsFile, SqLiteDatabaseHelper.GetCorruptSchema()); Assert.DoesNotThrow(precondition, "Precondition failed: creating corrupt database file failed"); // Call TestDelegate test = () => new StorageSqLite().LoadProject(tempRingtoetsFile); // Assert StorageException exception = Assert.Throws(test); Assert.IsInstanceOf(exception); Assert.AreEqual(expectedMessage, exception.Message); Assert.IsInstanceOf(exception.InnerException); Assert.AreEqual(expectedInnerExceptionMessage, exception.InnerException.Message); Assert.IsInstanceOf(exception.InnerException.InnerException); Assert.AreEqual(expectedInnerExceptionInnerExceptionMessage, exception.InnerException.InnerException.Message); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void LoadProject_ValidDatabase_ReturnsProject() { // Setup var projectName = Path.GetFileNameWithoutExtension(tempRingtoetsFile); var storage = new StorageSqLite(); var mockRepository = new MockRepository(); var projectMock = mockRepository.StrictMock(); projectMock.Description = ""; projectMock.StorageId = 1L; FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Precondition SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, projectMock); // Call Project loadedProject = storage.LoadProject(tempRingtoetsFile); // Assert Assert.IsInstanceOf(loadedProject); Assert.AreEqual(1L, loadedProject.StorageId); Assert.AreEqual(projectName, loadedProject.Name); Assert.AreEqual(projectMock.Description, loadedProject.Description); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] [TestCase(null)] [TestCase("")] [TestCase(" ")] public void SaveProjectAs_InvalidPath_ThrowsArgumentException(string invalidPath) { // Setup Project project = new Project(); var expectedMessage = String.Format("Fout bij het lezen van bestand '{0}': {1}", invalidPath, UtilsResources.Error_Path_must_be_specified); // Call TestDelegate test = () => new StorageSqLite().SaveProjectAs(invalidPath, project); // Assert ArgumentException exception = Assert.Throws(test); Assert.AreEqual(expectedMessage, exception.Message); } [Test] public void SaveProjectAs_InvalidProject_ThrowsArgumentNullException() { // Setup var storage = new StorageSqLite(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Call TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile, null); // Assert Assert.Throws(test); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void SaveProjectAs_ValidPathToNonExistingFile_DoesNotThrowException() { // Setup var project = new Project(); var storage = new StorageSqLite(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Call TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile, project); // Assert Assert.DoesNotThrow(test); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void SaveProjectAs_ValidPathToExistingFile_DoesNotThrowException() { // Setup var project = new Project(); var storage = new StorageSqLite(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Call TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile, project); // Assert Assert.DoesNotThrow(test); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void SaveProjectAs_ValidPathToLockedFile_ThrowsUpdateStorageException() { // Setup var expectedMessage = String.Format(@"Fout bij het schrijven naar bestand '{0}': Een fout is opgetreden met schrijven naar het nieuwe Ringtoets bestand.", tempRingtoetsFile); var project = new Project(); var storage = new StorageSqLite(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Call TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile, project); StorageException exception; using (File.Create(tempRingtoetsFile)) // Locks file { exception = Assert.Throws(test); } // Assert Assert.IsInstanceOf(exception); Assert.IsInstanceOf(exception.InnerException); Assert.IsInstanceOf(exception); Assert.AreEqual(expectedMessage, exception.Message); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] [TestCase(null)] [TestCase("")] [TestCase(" ")] public void SaveProject_InvalidPath_ThrowsArgumentException(string invalidPath) { // Setup Project project = new Project(); var expectedMessage = String.Format("Fout bij het lezen van bestand '{0}': {1}", invalidPath, UtilsResources.Error_Path_must_be_specified); // Call TestDelegate test = () => new StorageSqLite().SaveProject(invalidPath, project); // Assert ArgumentException exception = Assert.Throws(test); Assert.AreEqual(expectedMessage, exception.Message); } [Test] public void SaveProject_InvalidProject_ThrowsArgumentNullException() { // Setup var storage = new StorageSqLite(); Project storedProject = new Project(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Precondition SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject); // Call TestDelegate test = () => storage.SaveProject(tempRingtoetsFile, null); // Assert Assert.Throws(test); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void SaveProject_ValidProjectNonExistingPath_ThrowsStorageExceptionAndCouldNotConnectException() { // Setup var project = new Project { StorageId = 1234L }; var tempFile = Path.Combine(testDataPath, "DoesNotExist.rtd"); var expectedMessage = String.Format(@"Fout bij het lezen van bestand '{0}': ", tempFile); var expectedInnerMessage = "Het bestand bestaat niet."; var storage = new StorageSqLite(); // Call TestDelegate test = () => storage.SaveProject(tempFile, project); // Assert StorageException exception = Assert.Throws(test); Assert.IsInstanceOf(exception); Assert.AreEqual(expectedMessage, exception.Message); Assert.IsInstanceOf(exception.InnerException); Assert.AreEqual(expectedInnerMessage, exception.InnerException.Message); } [Test] public void SaveProject_EmptyDatabaseFile_ThrowsStorageException() { // Setup var savedProject = new Project { StorageId = 1L }; var projectWithIncorrectStorageId = new Project { StorageId = 1234L }; var storage = new StorageSqLite(); var expectedMessage = String.Format(@"Fout bij het schrijven naar bestand '{0}'{1}: {2}", tempRingtoetsFile, "", String.Format("Het object '{0}' met id '{1}' is niet gevonden.", "ProjectEntity", projectWithIncorrectStorageId.StorageId)); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Precondition SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, savedProject); // Call TestDelegate test = () => storage.SaveProject(tempRingtoetsFile, projectWithIncorrectStorageId); // Assert StorageException exception = Assert.Throws(test); Assert.AreEqual(expectedMessage, exception.Message); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void SaveProject_CorruptRingtoetsFileThatPassesValidation_ThrowsStorageExceptionWithFullStackTrace() { // Setup var project = new Project { StorageId = 1234L }; var storage = new StorageSqLite(); var expectedMessage = String.Format(@"Fout bij het schrijven naar bestand '{0}'{1}: {2}", tempRingtoetsFile, "", "Een fout is opgetreden met het updaten van het Ringtoets bestand."); var expectedInnerExceptionMessage = "An error occurred while executing the command definition. See the inner exception for details."; var expectedInnerExceptionInnerExceptionMessage = "SQL logic error or missing database" + Environment.NewLine + "no such table: ProjectEntity"; FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { TestDelegate precondition = () => SqLiteDatabaseHelper.CreateDatabaseFile(tempRingtoetsFile, SqLiteDatabaseHelper.GetCorruptSchema()); Assert.DoesNotThrow(precondition, "Precondition failed: creating corrupt database file failed"); // Call TestDelegate test = () => storage.SaveProject(tempRingtoetsFile, project); // Assert StorageException exception = Assert.Throws(test); Assert.IsInstanceOf(exception); Assert.AreEqual(expectedMessage, exception.Message); Assert.IsInstanceOf(exception.InnerException); Assert.AreEqual(expectedInnerExceptionMessage, exception.InnerException.Message); Assert.IsInstanceOf(exception.InnerException.InnerException); Assert.AreEqual(expectedInnerExceptionInnerExceptionMessage, exception.InnerException.InnerException.Message); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void SaveProject_ValidPathToSetFilePath_DoesNotThrowException() { // Setup long projectId = 1234L; var projectName = Path.GetFileNameWithoutExtension(tempRingtoetsFile); var project = new Project() { StorageId = projectId }; var storage = new StorageSqLite(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { TestDelegate precondition = () => storage.SaveProjectAs(tempRingtoetsFile, project); Assert.DoesNotThrow(precondition, String.Format("Precondition: file '{0}' must be a valid Ringtoets database file.", tempRingtoetsFile)); // Call TestDelegate test = () => storage.SaveProject(tempRingtoetsFile, project); // Assert Assert.DoesNotThrow(test); Assert.AreEqual(projectName, project.Name); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void HasChanges_NoConnectionSet_ReturnsTrue() { // Setup StorageSqLite storageSqLite = new StorageSqLite(); // Call bool hasChanges = storageSqLite.HasChanges(new Project()); // Assert Assert.IsTrue(hasChanges); } [Test] public void HasChanges_ValidProjectLoaded_ReturnsFalse() { // Setup StorageSqLite storageSqLite = new StorageSqLite(); Project storedProject = new Project(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject); Project loadedProject = storageSqLite.LoadProject(tempRingtoetsFile); // Call bool hasChanges = storageSqLite.HasChanges(loadedProject); // Assert Assert.IsFalse(hasChanges); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void HasChanges_ValidProjectLoadedAndThenClosed_ReturnsTrue() { // Setup StorageSqLite storageSqLite = new StorageSqLite(); Project storedProject = new Project(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject); Project loadedProject = storageSqLite.LoadProject(tempRingtoetsFile); storageSqLite.CloseProject(); // Call bool hasChanges = storageSqLite.HasChanges(loadedProject); // Assert Assert.IsTrue(hasChanges); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void HasChanges_ValidProjectLoadedWithUnaffectedChange_ReturnsFalse() { // Setup StorageSqLite storageSqLite = new StorageSqLite(); Project storedProject = new Project(); var changedName = "some name"; FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject); Project loadedProject = storageSqLite.LoadProject(tempRingtoetsFile); // Call loadedProject.Name = changedName; bool hasChanges = storageSqLite.HasChanges(loadedProject); // Assert Assert.IsFalse(hasChanges); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void HasChanges_ValidProjectLoadedWithAffectedChange_ReturnsTrue() { // Setup StorageSqLite storageSqLite = new StorageSqLite(); Project storedProject = new Project(); var changedDescription = "some description"; FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject); Project loadedProject = storageSqLite.LoadProject(tempRingtoetsFile); // Call loadedProject.Description = changedDescription; bool hasChanges = storageSqLite.HasChanges(loadedProject); // Assert Assert.IsTrue(hasChanges); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } } [Test] public void HasChanges_SavedToEmptyDatabaseFile_ReturnsFalse() { // Setup var mockRepository = new MockRepository(); var projectMock = mockRepository.StrictMock(); projectMock.StorageId = 1234L; mockRepository.ReplayAll(); var storage = new StorageSqLite(); FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile); try { // Precondition, required to set the connection string TestDelegate precondition = () => storage.SaveProjectAs(tempRingtoetsFile, projectMock); Assert.DoesNotThrow(precondition, "Precondition failed: creating database file failed"); // Call var hasChanges = storage.HasChanges(projectMock); // Assert Assert.IsFalse(hasChanges); } finally { CallGarbageCollector(); fileDisposeHelper.Dispose(); } mockRepository.VerifyAll(); } private static void CallGarbageCollector() { GC.Collect(); GC.WaitForPendingFinalizers(); } } }