đź“Ś Add to a backend library a mock data seeder for a custom Cadmus model (part seeder).

  1. core
  2. parts
  3. part seeders
  4. fragments
  5. fragment seeders
  6. services
  7. API

Packages

The part seeder project requires at the Cadmus.Seed package. Typically you add a seeder for each part or fragment.

Part Seeder Templates

Part Seeder

Add a <NAME>PartSeeder.cs for the seeder (replace __NAME__ with the part name, using the proper case, and adjust the namespace).

If the seeder does not require configuration options (as it happens in most cases), remove the __NAME__PartSeederOptions class, the corresponding _options member, and the IConfigurable<T> interface.

using Bogus;
using Cadmus.Core;
using Fusi.Tools.Configuration;
using System;

namespace Cadmus.Seed.__PRJ__.Parts;

/// <summary>
/// Seeder for <see cref="__NAME__Part"/>.
/// Tag: <c>seed.it.vedph.__PRJ__.__NAME__</c>.
/// </summary>
/// <seealso cref="PartSeederBase" />
[Tag("seed.it.vedph.__PRJ__.__NAME__")]
public sealed class __NAME__PartSeeder : PartSeederBase,
    IConfigurable<__NAME__PartSeederOptions>
{
    private __NAME__PartSeederOptions _options;

    /// <summary>
    /// Configures the object with the specified options.
    /// </summary>
    /// <param name="options">The options.</param>
    public void Configure(__NAME__PartSeederOptions options)
    {
        _options = options;
    }

    /// <summary>
    /// Creates and seeds a new part.
    /// </summary>
    /// <param name="item">The item this part should belong to.</param>
    /// <param name="roleId">The optional part role ID.</param>
    /// <param name="factory">The part seeder factory. This is used
    /// for layer parts, which need to seed a set of fragments.</param>
    /// <returns>A new part or null.</returns>
    /// <exception cref="ArgumentNullException">item or factory</exception>
    public override IPart? GetPart(IItem item, string? roleId,
        PartSeederFactory? factory)
    {
        ArgumentNullException.ThrowIfNull(item);
        // for layer parts only:
        // if (factory == null)
        //    throw new ArgumentNullException(nameof(factory));

        // TODO: add more options validation check; if invalid, ret null
        if (_options == null)
        {
            return null;
        }

        __NAME__Part part = new();
        // or with Bogus, which usually is easier:
        // __NAME__Part part = new Faker<__NAME__Part>()
        //    .RuleFor(p => p.X, f => TODO)
        //    .Generate();
        SetPartMetadata(part, roleId, item);

        // TODO: add seed code here if not using Bogus...

        return part;
    }
}

/// <summary>
/// Options for <see cref="__NAME__PartSeeder"/>.
/// </summary>
public sealed class __NAME__PartSeederOptions
{
    // TODO: add options here...
}

Part Seeder Test

This test template requires some infrastructure.

using Cadmus.Core;
using Fusi.Tools.Configuration;
using System;
using System.Reflection;
using Xunit;

namespace Cadmus.Seed.__PRJ__.Parts.Test;

public sealed class __NAME__PartSeederTest
{
    private static readonly PartSeederFactory _factory =
        TestHelper.GetFactory();
    private static readonly SeedOptions _seedOptions =
        _factory.GetSeedOptions();
    private static readonly IItem _item =
        _factory.GetItemSeeder().GetItem(1, "facet");

    [Fact]
    public void TypeHasTagAttribute()
    {
        Type t = typeof(__NAME__PartSeeder);
        TagAttribute? attr = t.GetTypeInfo().GetCustomAttribute<TagAttribute>();
        Assert.NotNull(attr);
        Assert.Equal("seed.it.vedph.__PRJ__.__NAME__", attr!.Tag);
    }

    [Fact]
    public void Seed_Ok()
    {
        __NAME__PartSeeder seeder = new();
        seeder.SetSeedOptions(_seedOptions);

        IPart? part = seeder.GetPart(_item, null, _factory);

        Assert.NotNull(part);

        __NAME__Part? p = part as __NAME__Part;
        Assert.NotNull(p);

        TestHelper.AssertPartMetadata(p!);

        // TODO: assert properties like:
        // Assert.NotEmpty(p!.Entries);
    }
}

Test Helper

This template requires some infrastructure files:

  • a minimalist JSON configuration file for the seeders to be tested: SeedConfig.json (embedded resource) under your test project’s Assets folder.
  • a TestHelper to use this configuration.
  • package Fusi.Microsoft.Extensions.Configuration.InMemoryJson to read the configuration from the embedded SeedConfig.json.

Sample configuration:

{
  "facets": [
    {
      "typeId": "it.vedph.pura.word-forms",
      "name": "forms",
      "description": "Word forms.",
      "colorKey": "31AB54",
      "groupKey": "lexicon",
      "sortKey": "forms"
    }
  ],
  "seed": {
    "options": {
      "seed": 1,
      "baseTextPartTypeId": "it.vedph.token-text",
      "users": [ "zeus" ],
      "partRoles": [],
      "fragmentRoles": []
    },
    "partSeeders": [
      {
        "id": "seed.it.vedph.pura.word-forms"
      }
    ],
    "fragmentSeeders": []
  }
}

In this file, add all the parts to a single facet, and inside it add all the parts (under facets) and their seeders (under seed.partSeeders).

Template for TestHelper:

using System;
using System.IO;
using Cadmus.Core;
using Cadmus.Core.Config;
using Cadmus.__PRJ__.Parts;
using Fusi.Microsoft.Extensions.Configuration.InMemoryJson;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.Hosting;

namespace Cadmus.Seed.__PRJ__.Parts.Test;

static internal class TestHelper
{
    static public Stream GetResourceStream(string name)
    {
        ArgumentNullException.ThrowIfNull(name);

        return Assembly.GetExecutingAssembly().GetManifestResourceStream(
            $"Cadmus.Seed.__PRJ__.Parts.Test.Assets.{name}")!;
    }

    static public string LoadResourceText(string name)
    {
        ArgumentNullException.ThrowIfNull(name);

        using StreamReader reader = new(GetResourceStream(name),
            Encoding.UTF8);
        return reader.ReadToEnd();
    }

    private static IHost GetHost(string config)
    {
        // map
        TagAttributeToTypeMap map = new();
        map.Add(
        [
            // TODO: your parts assemblies here
            // Cadmus.Core
            typeof(StandardItemSortKeyBuilder).Assembly,
            // Cadmus.__PRJ__.Parts
            typeof(YOURPART).Assembly
        ]);

        return new HostBuilder().ConfigureServices((hostContext, services) =>
            {
                PartSeederFactory.ConfigureServices(services,
                    new StandardPartTypeProvider(map),
                        // TODO: your seeder assembly here
                        // Cadmus.Seed.__PRJ__.Parts
                        typeof(YOURSEEDER).Assembly);
            })
            // extension method from Fusi library
            .AddInMemoryJson(config)
            .Build();
    }

    static public PartSeederFactory GetFactory()
    {
        return new PartSeederFactory(GetHost(LoadResourceText("SeedConfig.json")));
    }

    static public void AssertPartMetadata(IPart part)
    {
        Assert.NotNull(part.Id);
        Assert.NotNull(part.ItemId);
        Assert.NotNull(part.UserId);
        Assert.NotNull(part.CreatorId);
    }
}

This template was updated after the component factory upgrade. The old GetFactory() helper method using Fusi.Tools.Config is:

static public PartSeederFactory GetFactory()
{
    // map
    TagAttributeToTypeMap map = new();
    map.Add(new[]
    {
        // Cadmus.Core
        typeof(StandardItemSortKeyBuilder).Assembly,
        // Cadmus.Philology.Parts
        typeof(ApparatusLayerFragment).Assembly
    });

    // container
    Container container = new();
    PartSeederFactory.ConfigureServices(
        container,
        new StandardPartTypeProvider(map),
        new[]
        {
            // Cadmus.Seed.Parts
            typeof(NotePartSeeder).Assembly,
            // Cadmus.Seed.Philology.Parts
            typeof(ApparatusLayerFragmentSeeder).Assembly
        });

    // config
    IConfigurationBuilder builder = new ConfigurationBuilder()
        .AddInMemoryJson(LoadResourceText("SeedConfig.json"));
    var configuration = builder.Build();

    return new PartSeederFactory(container, configuration);
}

🏠 developer’s home

▶️ next: fragments