// 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 Ringtoets.Integration.Data;
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}': {1}", nonExistingPath, "Het bestand bestaat niet.");
// Call
TestDelegate test = () => new StorageSqLite().LoadProject(nonExistingPath);
// Assert
StorageException exception = Assert.Throws(test);
Assert.AreEqual(expectedMessage, exception.Message);
}
[Test]
public void LoadProject_RingtoetsFileWithoutSchema_ThrowsStorageExceptionAndStorageValidationException()
{
// Setup
string validPath = "empty.rtd";
var tempFile = Path.Combine(testDataPath, validPath);
string expectedMessage = string.Format(@"Fout bij het lezen van bestand '{0}': {1}", tempFile, @"Het bestand is geen geldig Ringtoets bestand.");
// Call
TestDelegate test = () => new StorageSqLite().LoadProject(tempFile);
// Assert
StorageException exception = Assert.Throws(test);
Assert.AreEqual(expectedMessage, exception.Message);
}
[Test]
public void LoadProject_RingtoetsFileWithTwoProjects_ThrowsStorageExceptionAndStorageValidationException()
{
// Setup
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
TestDelegate precondition = () => SqLiteDatabaseHelper.CreateCompleteDatabaseFileWithoutProjectData(tempRingtoetsFile);
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
string expectedMessage = string.Format(@"Fout bij het lezen van bestand '{0}': {1}", tempRingtoetsFile, @"Het bestand is geen geldig 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.CreateCorruptDatabaseFile(tempRingtoetsFile);
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_DatabaseWithoutVersionEntities_ThrowStorageValidationException()
{
// Setup
string expectedMessage = string.Format(@"Fout bij het lezen van bestand '{0}': {1}",
tempRingtoetsFile,
@"Database moet één rij in de VersionEntity tabel hebben.");
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
TestDelegate precondition = () => SqLiteDatabaseHelper.CreateCompleteDatabaseFileEmpty(tempRingtoetsFile);
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);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
public void LoadProject_DatabaseWithMultipleVersionEntities_ThrowStorageValidationException()
{
// Setup
string expectedMessage = string.Format(@"Fout bij het lezen van bestand '{0}': {1}",
tempRingtoetsFile,
@"Database moet één rij in de VersionEntity tabel hebben.");
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
TestDelegate precondition = () =>
{
SqLiteDatabaseHelper.CreateCompleteDatabaseFileEmpty(tempRingtoetsFile);
SqLiteDatabaseHelper.AddVersionEntity(tempRingtoetsFile, currentDatabaseVersion);
SqLiteDatabaseHelper.AddVersionEntity(tempRingtoetsFile, currentDatabaseVersion);
};
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);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
[TestCase(currentDatabaseVersion + 1)]
[TestCase(currentDatabaseVersion + 500)]
public void LoadProject_DatabaseFromFutureVersion_ThrowStorageValidationException(int versionCode)
{
// Setup
string subMessage = string.Format("Database versie '{0}' is hoger dan de huidig ondersteunde versie ('{1}'). Update Ringtoets naar een nieuwere versie.",
versionCode, currentDatabaseVersion);
string expectedMessage = string.Format(@"Fout bij het lezen van bestand '{0}': {1}",
tempRingtoetsFile,
subMessage);
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
TestDelegate precondition = () =>
{
SqLiteDatabaseHelper.CreateCompleteDatabaseFileEmpty(tempRingtoetsFile);
SqLiteDatabaseHelper.AddVersionEntity(tempRingtoetsFile, versionCode);
};
Assert.DoesNotThrow(precondition, "Precondition failed: creating future database file failed");
// Call
TestDelegate test = () => new StorageSqLite().LoadProject(tempRingtoetsFile);
// Assert
StorageException exception = Assert.Throws(test);
Assert.IsInstanceOf(exception);
Assert.AreEqual(expectedMessage, exception.Message);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
[TestCase(0)]
[TestCase(-567)]
public void LoadProject_DatabaseWithInvalidVersionCode_ThrowStorageValidationException(int versionCode)
{
// Setup
string subMessage = string.Format("Database versie '{0}' is niet valide. Database versie dient '1' of hoger te zijn.",
versionCode);
string expectedMessage = string.Format(@"Fout bij het lezen van bestand '{0}': {1}",
tempRingtoetsFile,
subMessage);
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
TestDelegate precondition = () =>
{
SqLiteDatabaseHelper.CreateCompleteDatabaseFileEmpty(tempRingtoetsFile);
SqLiteDatabaseHelper.AddVersionEntity(tempRingtoetsFile, versionCode);
};
Assert.DoesNotThrow(precondition, "Precondition failed: creating future database file failed");
// Call
TestDelegate test = () => new StorageSqLite().LoadProject(tempRingtoetsFile);
// Assert
StorageException exception = Assert.Throws(test);
Assert.IsInstanceOf(exception);
Assert.AreEqual(expectedMessage, exception.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
IProject 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]
public void StageProject_ProjectIsNull_ThrowsArgumentNullException()
{
// Setup
var storage = new StorageSqLite();
// Call
TestDelegate test = () => storage.StageProject(null);
// Assert
Assert.Throws(test);
}
[Test]
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
public void SaveProjectAs_InvalidPath_ThrowsArgumentException(string invalidPath)
{
// Setup
RingtoetsProject project = new RingtoetsProject();
var expectedMessage = string.Format("Fout bij het lezen van bestand '{0}': {1}",
invalidPath, UtilsResources.Error_Path_must_be_specified);
var storage = new StorageSqLite();
storage.StageProject(project);
// Call
TestDelegate test = () => storage.SaveProjectAs(invalidPath);
// Assert
ArgumentException exception = Assert.Throws(test);
Assert.AreEqual(expectedMessage, exception.Message);
}
[Test]
public void SaveProjectAs_ValidPathToNonExistingFile_DoesNotThrowException()
{
// Setup
var project = new RingtoetsProject();
var storage = new StorageSqLite();
storage.StageProject(project);
try
{
// Precondition
Assert.IsFalse(File.Exists(tempRingtoetsFile));
// Call
TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile);
// Assert
Assert.DoesNotThrow(test);
}
finally
{
CallGarbageCollector();
File.Delete(tempRingtoetsFile);
}
}
[Test]
public void SaveProjectAs_ValidPathToExistingFile_DoesNotThrowException()
{
// Setup
var project = new RingtoetsProject();
var storage = new StorageSqLite();
storage.StageProject(project);
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
// Precondition
Assert.IsTrue(File.Exists(tempRingtoetsFile));
// Call
TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile);
// Assert
Assert.DoesNotThrow(test);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
public void SaveProjectAs_ValidPathToLockedFile_ThrowsUpdateStorageException()
{
// Setup
var expectedMessage = string.Format(
@"Kan geen tijdelijk bestand maken van het originele bestand ({0}).",
tempRingtoetsFile);
var project = new RingtoetsProject();
var storage = new StorageSqLite();
storage.StageProject(project);
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
// Call
TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile);
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]
public void SaveProkectAs_NoStagedProject_ThrowInvalidOperationException()
{
// Setup
var storage = new StorageSqLite();
// Precondition
Assert.IsFalse(storage.HasStagedProject);
// Call
TestDelegate call = () => storage.SaveProjectAs(tempRingtoetsFile);
// Assert
string message = Assert.Throws(call).Message;
Assert.AreEqual("Call 'StageProject(IProject)' first before calling this method.", message);
}
[Test]
public void HasStagedProjectChanges_NoProjectStaged_ThrowInvalidOperationException()
{
// Setup
var storage = new StorageSqLite();
// Precondition
Assert.IsFalse(storage.HasStagedProject);
// Call
TestDelegate call = () => storage.HasStagedProjectChanges();
// Assert
string message = Assert.Throws(call).Message;
Assert.AreEqual("Call 'StageProject(IProject)' first before calling this method.", message);
}
[Test]
public void HasStagedProjectChanges_NoConnectionSet_ReturnsTrue()
{
// Setup
StorageSqLite storageSqLite = new StorageSqLite();
storageSqLite.StageProject(new RingtoetsProject());
// Call
bool hasChanges = storageSqLite.HasStagedProjectChanges();
// Assert
Assert.IsTrue(hasChanges);
}
[Test]
public void HasStagedProjectChanges_ValidProjectLoaded_ReturnsFalse()
{
// Setup
StorageSqLite storageSqLite = new StorageSqLite();
RingtoetsProject storedProject = new RingtoetsProject();
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject);
IProject loadedProject = storageSqLite.LoadProject(tempRingtoetsFile);
storageSqLite.StageProject(loadedProject);
// Call
bool hasChanges = storageSqLite.HasStagedProjectChanges();
// Assert
Assert.IsFalse(hasChanges);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
public void HasStagedProjectChanges_ValidProjectLoadedAndThenClosed_ReturnsTrue()
{
// Setup
StorageSqLite storageSqLite = new StorageSqLite();
RingtoetsProject storedProject = new RingtoetsProject();
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject);
IProject loadedProject = storageSqLite.LoadProject(tempRingtoetsFile);
storageSqLite.CloseProject();
storageSqLite.StageProject(loadedProject);
// Call
bool hasChanges = storageSqLite.HasStagedProjectChanges();
// Assert
Assert.IsTrue(hasChanges);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
public void HasStagedProjectChanges_ValidProjectLoadedWithUnaffectedChange_ReturnsFalse()
{
// Setup
StorageSqLite storageSqLite = new StorageSqLite();
RingtoetsProject storedProject = new RingtoetsProject();
var changedName = "some name";
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject);
IProject loadedProject = storageSqLite.LoadProject(tempRingtoetsFile);
storageSqLite.StageProject(loadedProject);
// Call
loadedProject.Name = changedName;
bool hasChanges = storageSqLite.HasStagedProjectChanges();
// Assert
Assert.IsFalse(hasChanges);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
public void HasStagedProjectChanges_ValidProjectLoadedWithAffectedChange_ReturnsTrue()
{
// Setup
StorageSqLite storageSqLite = new StorageSqLite();
RingtoetsProject storedProject = RingtoetsProjectTestHelper.GetFullTestProject(); //new RingtoetsProject();
var changedDescription = "some description";
FileDisposeHelper fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile);
try
{
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject);
IProject loadedProject = storageSqLite.LoadProject(tempRingtoetsFile);
loadedProject.Description = changedDescription;
storageSqLite.StageProject(loadedProject);
// Call
bool hasChanges = storageSqLite.HasStagedProjectChanges();
// Assert
Assert.IsTrue(hasChanges);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
}
[Test]
public void HasStagedProjectChanges_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
storage.StageProject(projectMock);
TestDelegate precondition = () => storage.SaveProjectAs(tempRingtoetsFile);
Assert.DoesNotThrow(precondition, "Precondition failed: creating database file failed");
storage.StageProject(projectMock);
// Call
var hasChanges = storage.HasStagedProjectChanges();
// Assert
Assert.IsFalse(hasChanges);
}
finally
{
CallGarbageCollector();
fileDisposeHelper.Dispose();
}
mockRepository.VerifyAll();
}
private const int currentDatabaseVersion = 2;
private static void CallGarbageCollector()
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}