// 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.TestUtil;
using Core.Common.Base.Data;
using Core.Common.Base.Storage;
using Core.Common.TestUtil;
using NUnit.Framework;
using Rhino.Mocks;
using Ringtoets.Common.Utils;
using Ringtoets.Integration.Data;
namespace Application.Ringtoets.Storage.Test
{
[TestFixture]
public class StorageSqLiteTest
{
private readonly string testPath = TestHelper.GetTestDataPath(TestDataPath.Application.Ringtoets.Storage, "DatabaseFiles");
private readonly string workingDirectory = TestHelper.GetScratchPadPath(nameof(StorageSqLiteTest));
private DirectoryDisposeHelper directoryDisposeHelper;
[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)
{
// Call
TestDelegate test = () => new StorageSqLite().LoadProject(invalidPath);
// Assert
ArgumentException exception = Assert.Throws(test);
Assert.IsInstanceOf(exception);
Assert.AreEqual($"Fout bij het lezen van bestand '{invalidPath}': bestandspad mag niet leeg of ongedefinieerd zijn.",
exception.Message);
}
[Test]
public void LoadProject_NonExistingPath_ThrowsStorageExceptionAndCouldNotConnectException()
{
// Setup
const string nonExistingPath = "fileDoesNotExist";
// Call
TestDelegate test = () => new StorageSqLite().LoadProject(nonExistingPath);
// Assert
StorageException exception = Assert.Throws(test);
Assert.AreEqual($@"Fout bij het lezen van bestand '{nonExistingPath}': het bestand bestaat niet.",
exception.Message);
}
[Test]
public void LoadProject_RingtoetsFileWithoutSchema_ThrowsStorageExceptionAndStorageValidationException()
{
// Setup
const string validPath = "empty.rtd";
string tempFile = Path.Combine(testPath, validPath);
// Call
TestDelegate test = () => new StorageSqLite().LoadProject(tempFile);
// Assert
StorageException exception = Assert.Throws(test);
Assert.AreEqual($@"Fout bij het lezen van bestand '{tempFile}': het bestand is geen geldig Ringtoets bestand.",
exception.Message);
}
[Test]
public void LoadProject_RingtoetsFileWithTwoProjects_ThrowsStorageExceptionAndStorageValidationException()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(LoadProject_RingtoetsFileWithTwoProjects_ThrowsStorageExceptionAndStorageValidationException));
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 = $@"Fout bij het lezen van bestand '{tempRingtoetsFile}': het bestand is geen geldig Ringtoets bestand.";
StorageException exception = Assert.Throws(test);
Assert.IsInstanceOf(exception);
Assert.AreEqual(expectedMessage, exception.Message);
}
[Test]
public void LoadProject_CorruptRingtoetsFileThatPassesValidation_ThrowsStorageExceptionWithFullStackTrace()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(LoadProject_CorruptRingtoetsFileThatPassesValidation_ThrowsStorageExceptionWithFullStackTrace));
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($@"Fout bij het lezen van bestand '{tempRingtoetsFile}': het bestand is geen geldig Ringtoets bestand.",
exception.Message);
Assert.IsInstanceOf(exception.InnerException);
Assert.AreEqual("An error occurred while executing the command definition. See the inner exception for details.",
exception.InnerException.Message);
Assert.IsInstanceOf(exception.InnerException.InnerException);
Assert.AreEqual($"SQL logic error or missing database{Environment.NewLine}"
+ "no such table: ProjectEntity", exception.InnerException.InnerException.Message);
}
[Test]
public void LoadProject_DatabaseWithoutVersionEntities_ThrowStorageValidationException()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(LoadProject_DatabaseWithoutVersionEntities_ThrowStorageValidationException));
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($@"Fout bij het lezen van bestand '{tempRingtoetsFile}': database moet één rij in de VersionEntity tabel hebben.",
exception.Message);
Assert.IsInstanceOf(exception.InnerException);
}
[Test]
public void LoadProject_DatabaseWithMultipleVersionEntities_ThrowStorageValidationException()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(LoadProject_DatabaseWithMultipleVersionEntities_ThrowStorageValidationException));
string currentDatabaseVersion = RingtoetsVersionHelper.GetCurrentDatabaseVersion();
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($@"Fout bij het lezen van bestand '{tempRingtoetsFile}': database moet één rij in de VersionEntity tabel hebben.",
exception.Message);
Assert.IsInstanceOf(exception.InnerException);
}
[Test]
[TestCase(1)]
[TestCase(500)]
public void LoadProject_DatabaseFromFutureVersion_ThrowStorageValidationException(int additionalVersionNumber)
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, $"{nameof(LoadProject_DatabaseFromFutureVersion_ThrowStorageValidationException)}_{Path.GetRandomFileName()}");
string currentDatabaseVersion = RingtoetsVersionHelper.GetCurrentDatabaseVersion();
string versionCode = additionalVersionNumber + currentDatabaseVersion;
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($@"Fout bij het lezen van bestand '{tempRingtoetsFile}': ringtoets "
+ $"bestand versie '{versionCode}' is hoger dan de huidig ondersteunde versie "
+ $"('{currentDatabaseVersion}'). Update Ringtoets naar een nieuwere versie.",
exception.Message);
}
[Test]
[TestCase("0")]
[TestCase("-567")]
public void LoadProject_DatabaseWithInvalidVersionCode_ThrowStorageValidationException(string versionCode)
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, $"{nameof(LoadProject_DatabaseWithInvalidVersionCode_ThrowStorageValidationException)}_{Path.GetRandomFileName()}");
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($@"Fout bij het lezen van bestand '{tempRingtoetsFile}': ringtoets "
+ $"bestand versie '{versionCode}' is niet valide. De versie van het Ringtoets projectbestand "
+ "dient '16.4' of hoger te zijn.", exception.Message);
}
[Test]
public void LoadProject_ValidDatabase_ReturnsProject()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(LoadProject_ValidDatabase_ReturnsProject));
string projectName = Path.GetFileNameWithoutExtension(tempRingtoetsFile);
var storage = new StorageSqLite();
var mockRepository = new MockRepository();
var projectMock = mockRepository.StrictMock();
projectMock.Description = "";
// Precondition
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, projectMock);
// Call
IProject loadedProject = storage.LoadProject(tempRingtoetsFile);
// Assert
Assert.IsInstanceOf(loadedProject);
Assert.AreEqual(projectName, loadedProject.Name);
Assert.AreEqual(projectMock.Description, loadedProject.Description);
}
[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
var project = new RingtoetsProject();
var storage = new StorageSqLite();
storage.StageProject(project);
// Call
TestDelegate test = () => storage.SaveProjectAs(invalidPath);
// Assert
ArgumentException exception = Assert.Throws(test);
Assert.AreEqual($"Fout bij het lezen van bestand '{invalidPath}': bestandspad mag niet "
+ "leeg of ongedefinieerd zijn.", exception.Message);
}
[Test]
public void SaveProjectAs_ValidPathToNonExistingFile_DoesNotThrowException()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(SaveProjectAs_ValidPathToNonExistingFile_DoesNotThrowException));
var project = new RingtoetsProject();
var storage = new StorageSqLite();
storage.StageProject(project);
// Precondition
Assert.IsFalse(File.Exists(tempRingtoetsFile));
// Call
TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile);
// Assert
Assert.DoesNotThrow(test);
}
[Test]
public void SaveProjectAs_ValidPathToExistingFile_DoesNotThrowException()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(SaveProjectAs_ValidPathToExistingFile_DoesNotThrowException));
var project = new RingtoetsProject();
var storage = new StorageSqLite();
storage.StageProject(project);
using (File.Create(tempRingtoetsFile)) {}
// Call
TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile);
// Assert
Assert.DoesNotThrow(test);
}
[Test]
public void SaveProjectAs_ValidPathToLockedFile_ThrowsUpdateStorageException()
{
// Setup
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(SaveProjectAs_ValidPathToLockedFile_ThrowsUpdateStorageException));
var project = new RingtoetsProject();
var storage = new StorageSqLite();
storage.StageProject(project);
using (var fileDisposeHelper = new FileDisposeHelper(tempRingtoetsFile))
{
try
{
fileDisposeHelper.LockFiles();
// Call
TestDelegate test = () => storage.SaveProjectAs(tempRingtoetsFile);
// Assert
StorageException exception = Assert.Throws(test);
Assert.IsInstanceOf(exception);
Assert.IsInstanceOf(exception.InnerException);
Assert.IsInstanceOf(exception);
Assert.AreEqual($@"Kan geen tijdelijk bestand maken van het originele bestand ({tempRingtoetsFile}).",
exception.Message);
}
finally
{
CallGarbageCollector();
}
}
}
[Test]
public void SaveProjectAs_NoStagedProject_ThrowInvalidOperationException()
{
// Setup
string tempRingtoetsFile = TestHelper.GetScratchPadPath(nameof(SaveProjectAs_NoStagedProject_ThrowInvalidOperationException));
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_InvalidPath_ThrowsArgumentException()
{
// Setup
var storage = new StorageSqLite();
storage.StageProject(new RingtoetsProject());
string path = Path.Combine(testPath, "ValidCharacteristics.csv");
char[] invalidCharacters = Path.GetInvalidPathChars();
string corruptPath = path.Replace('V', invalidCharacters[0]);
// Call
TestDelegate call = () => storage.HasStagedProjectChanges(corruptPath);
// Assert
Assert.Throws(call);
}
[Test]
public void HasStagedProjectChanges_NoProjectStaged_ThrowInvalidOperationException()
{
// Setup
var storage = new StorageSqLite();
// Precondition
Assert.IsFalse(storage.HasStagedProject);
// Call
TestDelegate call = () => storage.HasStagedProjectChanges(null);
// Assert
string message = Assert.Throws(call).Message;
Assert.AreEqual("Call 'StageProject(IProject)' first before calling this method.", message);
}
[Test]
public void HasStagedProjectChanges_NoPathGiven_ReturnsTrue()
{
// Setup
var storageSqLite = new StorageSqLite();
storageSqLite.StageProject(new RingtoetsProject());
// Call
bool hasChanges = storageSqLite.HasStagedProjectChanges(null);
// Assert
Assert.IsTrue(hasChanges);
}
[Test]
public void HasStagedProjectChanges_ValidProjectLoaded_ReturnsFalse()
{
// Setup
var storageSqLite = new StorageSqLite();
var storedProject = new RingtoetsProject();
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(HasStagedProjectChanges_ValidProjectLoaded_ReturnsFalse));
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject);
IProject loadedProject = storageSqLite.LoadProject(tempRingtoetsFile);
storageSqLite.StageProject(loadedProject);
// Call
bool hasChanges = storageSqLite.HasStagedProjectChanges(tempRingtoetsFile);
// Assert
Assert.IsFalse(hasChanges);
}
[Test]
public void HasStagedProjectChanges_ValidProjectLoadedWithUnaffectedChange_ReturnsFalse()
{
// Setup
var storageSqLite = new StorageSqLite();
var storedProject = new RingtoetsProject();
const string changedName = "some name";
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(HasStagedProjectChanges_ValidProjectLoadedWithUnaffectedChange_ReturnsFalse));
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject);
IProject loadedProject = storageSqLite.LoadProject(tempRingtoetsFile);
storageSqLite.StageProject(loadedProject);
// Call
loadedProject.Name = changedName;
bool hasChanges = storageSqLite.HasStagedProjectChanges(tempRingtoetsFile);
// Assert
Assert.IsFalse(hasChanges);
}
[Test]
public void HasStagedProjectChanges_ValidProjectLoadedWithAffectedChange_ReturnsTrue()
{
// Setup
var storageSqLite = new StorageSqLite();
RingtoetsProject storedProject = RingtoetsProjectTestHelper.GetFullTestProject();
const string changedDescription = "some description";
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(HasStagedProjectChanges_ValidProjectLoadedWithAffectedChange_ReturnsTrue));
SqLiteDatabaseHelper.CreateValidRingtoetsDatabase(tempRingtoetsFile, storedProject);
IProject loadedProject = storageSqLite.LoadProject(tempRingtoetsFile);
loadedProject.Description = changedDescription;
storageSqLite.StageProject(loadedProject);
// Call
bool hasChanges = storageSqLite.HasStagedProjectChanges(tempRingtoetsFile);
// Assert
Assert.IsTrue(hasChanges);
}
[Test]
public void HasStagedProjectChanges_SavedToEmptyDatabaseFile_ReturnsFalse()
{
// Setup
var mockRepository = new MockRepository();
var projectMock = mockRepository.StrictMock();
mockRepository.ReplayAll();
var storage = new StorageSqLite();
string tempRingtoetsFile = Path.Combine(workingDirectory, nameof(HasStagedProjectChanges_SavedToEmptyDatabaseFile_ReturnsFalse));
// 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(tempRingtoetsFile);
// Assert
Assert.IsFalse(hasChanges);
mockRepository.VerifyAll();
}
[OneTimeSetUp]
public void SetUp()
{
directoryDisposeHelper = new DirectoryDisposeHelper(TestHelper.GetScratchPadPath(), nameof(StorageSqLiteTest));
}
[OneTimeTearDown]
public void TearDown()
{
GC.Collect();
GC.WaitForPendingFinalizers();
directoryDisposeHelper.Dispose();
}
private static void CallGarbageCollector()
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}