Adding Backend Part Seeders
Part seeders are used to generate mock data for the editor. There can be a part seeder class for each part class.
The part seeder project requires at the 📦
Cadmus.Seedpackage. Typically you add a seeder for each part or fragment.
Part Seeder
- add a
<NAME>PartSeeder.csfor the seeder class using the template below (replace__NAME__with the part name, using the proper case, and__PRJ__with the project name). - if the seeder does not require configuration options (as it happens in most cases), remove the
__NAME__PartSeederOptionsclass, the corresponding_optionsmember, and theIConfigurable<T>interface of this template.
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
// TODO: uncomment for configurable seeders, remove otherwise
// , IConfigurable<__NAME__PartSeederOptions>
{
// TODO: uncomment for configurable seeders, remove otherwise
// private __NAME__PartSeederOptions _options;
// TODO: uncomment for configurable seeders, remove otherwise
// /// <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, add this check:
// if (factory == null)
// throw new ArgumentNullException(nameof(factory));
// TODO: uncomment for configurable seeders, remove otherwise
// (add more options validation check; if invalid, ret null)
// if (_options == null)
// {
// return null;
// }
__NAME__Part part = new Faker<__NAME__Part>()
// TODO: add rules for most of the part properties
// (suggestion: use private methods to mock objects nested
// in the part), e.g.:
// .RuleFor(p => p.X, f => TODO: set value or call method)
.Generate();
// alternatively, create and fill the part manually:
// __NAME__Part part = new();
SetPartMetadata(part, roleId, item);
return part;
}
}
/// <summary>
/// Options for <see cref="__NAME__PartSeeder"/>.
/// </summary>
public sealed class __NAME__PartSeederOptions
{
// TODO: add options here...
}
Test Helper
The test template requires some infrastructure files:
- a minimalist JSON configuration file for the seeders to be tested:
SeedConfig.json(embedded resource) under your test project’sAssetsfolder. - a
TestHelperto use this configuration. - package
Fusi.Microsoft.Extensions.Configuration.InMemoryJsonto read the configuration from the embeddedSeedConfig.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);
}
}
Part Seeder Test
- for each part seeder, add a test using the following template (replace
__NAME__with the part name,__PRJ__with the project name). - implement at least
Seed_Okor create more test methods if you are going to test conditioned seeders.
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);
}
}