Flaksator – testing noSQL DB and dictionaries storage

In the previous post I’d described interface for storing String Lists as collections. In the meantime, I’d decided that StringWrapper was not good name for that object, and therefore I’d decided to change it into StringCollectionWrapper.

In this post I’m then continuing with noSQL storage.

Testing StringCollection storage

I’d decided to keep shared data file as part of the solution (/Data/Flaksator.db) to be able to easily manage it using GithubPlugin. Because there are / will be several projects that depend on it and are located on different level in file system hierarchy I do expect to have problems with accessing it – or having to manage different, scattered configurations. While clean unit tests should do not depend on any external resources, current piece of tests (kind of integration tests, I guess) require access to physical DB. The problem is that I prefer to keep test projects in dedicated subfolder in both file system and solution. Therefore path to db file is different depending from where it is going to be called:
VariousLevelFileAccess

I didn’t like the idea of using Registry or absolute path, to keep solution independent and simple to build as much as possible. Therefore I’d decided to create some sneaky way to introduce indirect path finding:

public class FlaksatorDataFactory
{
    private readonly string _dbPath;

    public FlaksatorDataFactory(string dbLocationString)
    {
        string[] parts = dbLocationString.Split('|');
        string folder = LocateParentFolder(
            Assembly.GetCallingAssembly().Location,
            parts[0]);
        _dbPath = Path.Combine(folder, parts[1]);
    }

    private string LocateParentFolder(string currentFolder, string searchedName)
    {
        FileInfo file = new FileInfo(currentFolder);
        if (file.Exists) // skip file
        {
            return LocateParentFolder(file.Directory.FullName, searchedName);
        }

        var root = Path.GetPathRoot(currentFolder);

        DirectoryInfo dir = new DirectoryInfo(currentFolder);
        var dirs = dir.GetDirectories();
        if (dirs.Select(d => d.Name).Contains(searchedName))
        {
            return currentFolder;
        }
        var files = dir.GetFiles();
        if (files.Select(f => f.Name).Contains(searchedName))
        {
            return currentFolder;
        }
        if (currentFolder == root)
        {
            throw new InvalidOperationException("Could not locate valid data folder.");
        }

        return LocateParentFolder(dir.Parent.FullName, searchedName);
    }

    public IDocumentDatabase CreateDatabase()
    {
        return new FlaksatorDatabase(_dbPath);
    }
}

So then in both test and final application I can just create factory using the same code (during development):

FlaksatorDataFactory factory = new FlaksatorDataFactory(
            @"Flaksator.sln|Data\Flaksator.db");

Of course in the production there will be no “*.sln” file, so in that case, path will look just like “Flaksator.db|Flaksator.db”, meaning that file is stored in the same folder where executable resides.

Executing tests and “tests”

I created one pseudo test, actually used to insert data into DB:

[Test]
[Ignore("Build-time only test")]
public void Fill_in_title_strings()
{
    using (var db = factory.CreateDatabase())
    {
        db.ListResources.SaveTitles(new[]
        {
            @"{NN1P}",
            @"{NN2P}",
            @"{NN1F}",
            @"{NN2F}",
            @"{1:A???E} {1:NN1P}",
            @"{1:A???E} {1:NN2P}",
            @"{1:A???E} {1:NN1F}",
            @"{1:A???E} {1:NN2F}",
   // ...
            @"{1:A???E} {1:NN1F} W {2:A???E} {2:NL1T}",
            @"{1:A???E} {1:NN1F} W {NL1N} {2:A???E} {2:NG1T}",
            @"{1:A???E} {1:NN1F} Pod {NA1T} {2:A???E} {2:NG1T}",
            @"{1:A???E} {1:NN1T} {2:A???E} {2:NG1F}",
            @"{1:A???S} {1:NN1F} {NG1P} {NG1F}",
            @"{1:A???E} {1:NN2N} U {NG2F} {NG2N}",
            @";{AN2NE} {NN2N} W {NL1N} {NG1T} {NG1T}",
            @"{1:A???E} {1:NN2T} W {NL1N} {2:A???E} {2:NG1F}",
            @"{1:A???E} {1:NN1F} U {NG1T} {NG1N}",
            @"{1:A???E} {1:NN1P} W {NL1F} {2:A???E} {2:NG1T}",
            @"{1:A???E} {1:NN1T} Nieopodal {2:A???E} {2:NG1T}",
            @"{NN1F} {1:A???E} {1:NG2P}",
            @"{NN2N} {1:A???E} {1:NG2F}",
            @"{NN1T} W {NL1N} {NG1T}",
            @"{NN1T} {1:A???E} {1:NG2P}",
   // ...
            @"{1:A???E} {1:NN2T} W Obliczu Narastającej Ciszy",
            @"{1:A???E} {1:NN1F} U Kresu Istnienia",
            @"{1:A???E} {1:NN1P} W Obliczu Wzmagającego Bólu",
            @"{1:A???E} {1:NN1T} Nieopodal Mrocznego Dębu"
        });
    }
}

The assertion is that no exception occurs during execution 😉 It shall also be executed only once, during initial DB creation – all further editions on the list(s) shall be done on DB itself and versioned.

And then read test – that actually relies on previous one:

[Test]
public void verify_that_some_titles_exist()
{
    using (var db = factory.CreateDatabase())
    {
        var titles = db.ListResources.GetTitles();

        Assert.That(titles.Contains(@"{NN1N} {NG1F}"));
        Assert.That(titles.Contains(@"{NN1T} Nieopodal {1:A???E} {1:NG1T}"));
    }
}

Of course, in order to run these tests under Resharper one need to disable assembly shadow-copying, so they are being run from project folder(s) and not from temporary files/directories somewhere in the system:
ResharperDisableShadowing

Storing Dictionaries

First attempt to store dictionaries:

public class DictionaryWrapper<TKey, TValue>
{
    public DictionaryWrapper()
    {    }

    public DictionaryWrapper(string key, IEnumerable<KeyValuePair<TKey, TValue>> items)
    {
        Key = key;
        Items = items.ToArray();
    }

    [BsonId]
    public string Key { get; set; }

    public KeyValuePair<TKey, TValue>[] Items { get; set; }
}

And the I’d provided base class for Dictionary repository, inspiring myself with previous implementation for strings (BaseListRepository.cs):

public abstract class BaseDictionaryRepository<TKey, TValue>
{
    private const string DICTIONARIES_IDENTIFIER = @"DICTIONARIES";
        
    private readonly LiteDatabase _db;

    protected BaseDictionaryRepository(LiteDatabase db)
    {
        _db = db;
    }

    protected Dictionary<TKey, TValue> GetDictionary(string collectionKey)
    {
        var wrapper = _db.GetCollection<DictionaryWrapper<TKey, TValue>>(DICTIONARIES_IDENTIFIER)
            .Find(w => w.Key == collectionKey).SingleOrDefault();
        if (wrapper != null)
        {
            return wrapper.Items.ToDictionary(i => i.Key, i => i.Value);
        }

        return new Dictionary<TKey, TValue>();
    }

    protected void SaveDictionary(string collectionKey, Dictionary<TKey, TValue> dictionary)
    {
        var collection = _db.GetCollection<DictionaryWrapper<TKey, TValue>>(DICTIONARIES_IDENTIFIER);
        var wrapper = collection.Find(w => w.Key == collectionKey).SingleOrDefault();
        if (wrapper == null)
        {
            wrapper = new DictionaryWrapper<TKey, TValue>(collectionKey, dictionary);
            collection.Insert(wrapper);
        }
        else
        {
            wrapper.Items = dictionary.ToArray();
            collection.Update(wrapper);
        }
    }
}

This however looks like it might fail – several different types sharing one identifier of DICTIONARIES_IDENTIFIER. It does not look good…
I need to test it, with two different collections… While almost all my collections have string type as value and integer as a key, I need to find something different before…
Therefore, I’d created some infrastructure first – interfaces, that I’m going to use anyway:

public interface IDictionaryRepositories
{
    Dictionary<SongPiece, string> GetConstantSongPieces();

    void SaveConstantSongPieces(Dictionary<SongPiece, string> dictionary);
}

public enum SongPiece
{
    Stanza,
    Chorus,
    Refrain,
    Bridge
    // TODO: ...
    ,
    Interlude
}

And then extended database interface and implementations accordingly:

public interface IDocumentDatabase : IDisposable
{
    IStringListsRepository ListResources { get; }

    IDictionaryRepositories DictionaryResources { get; }
}

internal class FlaksatorDatabase : BaseDocumentDatabase, IDocumentDatabase
{
    private readonly Lazy<IStringListsRepository> _listResources;
    private readonly Lazy<IDictionaryRepositories> _dictionaryResources;

    public FlaksatorDatabase(string dbPath) : base(dbPath)
    {
        _listResources = new Lazy<IStringListsRepository>(
            () => new LightStringListsRepository(base.Db));
        _dictionaryResources = new Lazy<IDictionaryRepositories>(
            () => new LightDictionaryRepositories(base.Db));
    }

    public IStringListsRepository ListResources
    {
        get { return _listResources.Value; }
    }

    public IDictionaryRepositories DictionaryResources
    {
        get { return _dictionaryResources.Value; }
    }
}

Then of course some inserting and checking code:

[Test]
//[Ignore("Build-time only test")]
public void constant_piece_transaltionsFill_in_title_strings()
{
    using (var db = factory.CreateDatabase())
    {
        var dic = new Dictionary<SongPiece, string>();
        dic.Add(SongPiece.Stanza, "--zwrotka--");
        dic.Add(SongPiece.Chorus, "chór");
        dic.Add(SongPiece.Bridge, "przejście");
        dic.Add(SongPiece.Interlude, "interludium");
        dic.Add(SongPiece.Refrain, "refren");

        db.DictionaryResources.SaveConstantSongPieces(dic);
    }
}
[Test]
public void verify_that_some_constant_piece_transaltions_exist()
{
    using (var db = factory.CreateDatabase())
    {
        var translations = db.DictionaryResources.GetConstantSongPieces();

        Assert.That(translations.ContainsKey(SongPiece.Chorus));
        Assert.That(translations[SongPiece.Chorus] == "chór");
    }
}

Insert went smoothly. Second test with reading stored data – crashed with rather unexpected DuplicateKeyException… Short debug and peek into what is being deserialized from Db reveals unsettling results:

LiteDb_ErrorStoringDictionaries
Looks like KeyValuePair is not being deserialized or serialized properly… Have to figure another way around…

But not today.

However, song can still be generated 😉 (What an abstraction – the title is “Knife in the edge of an Axe“…)

NÓŻ W OSTRZU TOPORA

(Jęki Agonii)

Tyś jest sprawcą, o klecho, tej zgnilizny, mój ty polityku.
Prawica była falliczna, jak zbroja ma,
Dziś swoje białko ktoś mi odda.
Bez przebaczenia, padasz już głęboki,
Przegrałeś, zginąłeś, to nie są żarty.

Próbujesz uciekać, biegniesz co sił w ostrzach,
Bezcelowo, Twą halabardę pożre bliźni o ciepłych garncach.
Padasz na ziemię, już otruty, szatan pożarł Twoją duszę,
Resztę pożrą robaki, dostaną ucztę, całą Twą tuszę.
Już Cię widzę, jeszcze Szatan, jeszcze decyzja,
Ja już czuję jak laboratorium me się w Ciebie wbija.

(Interludium)

(Piekielne Solo)

Mózgi Twoje już dopadły wypoczęte, otrute psy,
Kosmiczne, marne kwiaty roznoszą na gwinty.
Zgwałcone biedronki mnie opętały, zabijam dziś dla zdradliwej aparoskopii.

Nadszedł więc czas na bok oniryczny,
Wybiegłem z zakrzepicą na ulice Warszawy.
Rzucam się na Ciebie z trzustką,
Atakuję tu przed rzeczą.

Krowę zamczysk ułatwi garniec, to bardzo cholerna zgnilizna czerwia.
Wyjmuję miecz, robię zamach i mierzę w Ciebie,
To Twe ostatnie chwile, już za moment będziesz w niebie.

(Refren)
By spełnić namaszczoną dusze moją,
Zabrałem skarbiec sterczącego prawnika.
Padasz na ziemię, już satanistyczny, szatan pożarł Twoją duszę,
Resztę pożrą robaki, dostaną ucztę, całą Twą tuszę.
Mam Żyd mroczny u mego wrzoda,

Dziś zasmakuje wypolerowanego soku.
Nieczułego głosu tego, Chrześcijan mych nawiedziły mnie krajanki,
Zabijać i gwałcić kazały mi, portfel dla Ciebie nadejdzie wielce kudłaty
Wbijam swój topór w Twoje wnętrzności,

Po chwili na wierzchu już widać kości.
Nadszedł więc czas na smoczek bydlęcy,
Wybiegłem z zjawą na ulice Warszawy.