Piranha CMS Initialization & Test Flow Maps
This document provides a comprehensive overview of how Piranha CMS initializes and runs its unit tests. It is designed for agent orchestration workflows.
Piranha CMS Initialization Map
Unit Test Initialization Map
1. Piranha CMS Initialization Map
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ASP.NET Core Host β
β WebApplicationBuilder β AddPiranha() β builder.Build() β UsePiranha() β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DI Service Registration β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PiranhaStartupExtensions.AddPiranha() β β
β β βββ services.AddSingleton<IContentFactory, ContentFactory>() β β
β β βββ services.AddScoped<IApi, Api>() β β
β β βββ services.AddScoped<Config>() β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PiranhaRavenDbExtensions.UseRavenDb() / AddPiranhaRavenDb() β β
β β βββ services.AddSingleton<IDocumentStore>(store) β β
β β βββ Registers 14 Repositories + IAsyncDocumentSession + Factory β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Application Initialization β
β β
β App.Init(options.Api) ββββββββββββββββββββββΊ Uses IApi to: β
β β β’ Load ContentGroups β
β β ββββββββββββββββββββββββββββββββββββββββββΊ β’ Load ContentTypes β
β β β β’ Load PageTypes β
β β β β’ Load PostTypes β
β β β β’ Load SiteTypes β
β β β β’ Call Init() on Modules β
β β β β
β β βββΊ App (Static Singleton) β
β β β’ Registers default Block Types β
β β β’ Registers default Field Types β
β β β’ Registers Serializers β
β β β’ Registers Permissions β
β β β’ Creates Markdown Converter β
β β β
β ContentTypeBuilder.Build() βββΊ Scans assemblies for [PageType], β
β [PostType] attributes β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1. Startup Registration (Program.cs)
builder . AddPiranha ( options =>
{
options . UseCms ( ) ; // ASP.NET Core services
options . UseManager ( ) ; // Admin UI
options . UseFileStorage ( ) ; // File-based media storage
options . UseImageSharp ( ) ; // Image processing
options . UseMemoryCache ( ) ; // Caching
//options.UseRavenDb(store); // RavenDB DocumentStore
} ) ;
2. DI Service Registration Chain
PiranhaStartupExtensions.AddPiranha()
β
ββββΊ Registers IApi β Api class (scoped)
ββββΊ Registers IContentFactory β ContentFactory (singleton)
ββββΊ Registers Config (scoped)
PiranhaRavenDbExtensions.UseRavenDb()
β
ββββΊ Adds IDocumentStore (singleton) and IAsyncDocumentSession (scoped)
ββββΊ RegisterServices()
β
ββββΊ Registers 14 Repositories (scoped):
β βββ IAliasRepository β AliasRepository
β βββ IArchiveRepository β ArchiveRepository
β βββ IContentRepository β ContentRepository
β βββ IContentGroupRepository β ContentGroupRepository
β βββ IContentTypeRepository β ContentTypeRepository
β βββ ILanguageRepository β LanguageRepository
β βββ IMediaRepository β MediaRepository
β βββ IPageRepository β PageRepository
β βββ IPageTypeRepository β PageTypeRepository
β βββ IParamRepository β ParamRepository
β βββ IPostRepository β PostRepository
β βββ IPostTypeRepository β PostTypeRepository
β βββ ISiteRepository β SiteRepository
β βββ ISiteTypeRepository β SiteTypeRepository
β
ββββΊ Registers IDb β T (scoped) β The RavenDB session (DbRaven)
ββββΊ Registers IContentServiceFactory β ContentServiceFactory
3. IApi Construction (Composition Root)
When IApi is resolved from DI:
β
ββββΊ Api Constructor receives ALL dependencies:
β
ββββΊ Repositories (14 total)
ββββΊ IContentFactory
ββββΊ ICache (optional)
ββββΊ IStorage (optional)
ββββΊ IImageProcessor (optional)
ββββΊ ISearch (optional)
Then Api creates services:
β
ββββΊ ContentGroupService(contentGroupRepository, cache)
ββββΊ ContentTypeService(contentTypeRepository, cache)
ββββΊ LanguageService(languageRepository, cache)
ββββΊ PageTypeService(pageTypeRepository, cache)
ββββΊ ParamService(paramRepository, cache)
ββββΊ PostTypeService(postTypeRepository, cache)
ββββΊ SiteTypeService(siteTypeRepository, cache)
ββββΊ ContentService(contentRepository, contentFactory, Languages, cache, search)
ββββΊ SiteService(siteRepository, contentFactory, Languages, cache)
ββββΊ AliasService(aliasRepository, Sites, cache)
ββββΊ MediaService(mediaRepository, Params, storage, processor, cache)
ββββΊ PageService(pageRepository, contentFactory, Sites, Params, Media, cache, search)
ββββΊ PostService(postRepository, contentFactory, Sites, Pages, Params, Media, cache, search)
ββββΊ ArchiveService(archiveRepository, Params, Posts)
App.Init(IApi api)
β
ββββΊ App.InitApp(IApi api) [Thread-safe double-check lock]
β
ββββΊ _contentGroups.Init(api.ContentGroups.GetAllAsync())
ββββΊ _contentTypes.Init(api.ContentTypes.GetAllAsync())
ββββΊ _pageTypes.Init(api.PageTypes.GetAllAsync())
ββββΊ _postTypes.Init(api.PostTypes.GetAllAsync())
ββββΊ _siteTypes.Init(api.SiteTypes.GetAllAsync())
β
ββββΊ For each registered module:
module.Instance.Init()
ββββΊ e.g., Piranha.Data.RavenDb.Module.Init()
ββββΊ Registers module with Piranha
HTTP Request
β
βΌ
PiranhaApplicationBuilder / PiranhaApplication
β
ββββΊ Gets IApi from DI (scoped per request)
β
ββββΊ Controller/Service uses IApi.* (e.g., IApi.Pages, IApi.Sites)
β
βΌ
IApi.Pages (PageService)
β
βΌ
IPageRepository
β
βΌ
IDb (DbContext) β RavenDB Session
β
βΌ
Database (RavenDB Document Database)
Interface
Purpose
Implementation
IApi
Main facade providing access to all CMS services
Api class
IAsyncDocumentSession
Database session abstraction
RavenDB IAsyncDocumentSession
IContentFactory
Factory for creating content models
ContentFactory
IRepository<T>
Data access patterns
14 repository implementations
Repository (e.g., SiteRepository)
β
ββββΊ Constructor receives: IDb db (DbRaven)
β
ββββΊ Uses _db for all EF operations:
ββββΊ _db.Sites (IDocumentSession)
ββββΊ _db.SaveChanges()
ββββΊ _db.SaveChangesAsync()
ββββΊ _db.Set<T>() for generic operations
2. Unit Test Initialization Map
Test Architecture Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Test Execution Flow β
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β dotnet β ββββΊ β xUnit β ββββΊ β Piranha β β
β β test β β Test Runner β β Tests β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββ β
β βΌ β
β ββββββββββββββββββ β
β β BaseTests / β β Test Base Classes β
β β BaseTestsAsyncβ β
β ββββββββββββββββββ β
β β β
β ββββββββββββββββΌβββββββββββββββ β
β βΌ βΌ βΌ β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β Create β β App.Init β β ContentTypeβ β
β β Service β β (api) β β Builder β β
β β Collection β β β β β β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β RavenDB TestServer β β
β β (In-Memory Document Store) β β
β βββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Test Infrastructure Components
βββββββββββββββββββββββββββββββββββββββββββ
β Testing Framework β
β β’ xUnit 2.7.0 β
β β’ Microsoft.NET.Test.Sdk 17.9.0 β
β β’ Coverlet (code coverage) β
βββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββ
β Test Base Classes β
β β’ BaseTests (sync) β
β β’ BaseTestsAsync (async) : IAsyncLifetime
βββββββββββββββββββββββββββββββββββββββββββ
2. BaseTests Class Hierarchy
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BaseTests (Synchronous) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β protected IServiceProvider services ββ
β β protected IStorage storage ββ
β β ββ
β β GetSession() β IAsyncDocumentSession ββ
β β βββ Uses: Embedded DocumentStore from Marten TestDriver ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β (inherits)
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BaseTestsAsync : IAsyncLifetime β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β protected ICache _cache ββ
β β protected IServiceProvider _services ββ
β β ββ
β β CreateServiceCollection() β IServiceCollection ββ
β β βββ AddPiranhaRavenDb() ββ
β β βββ AddPiranha() ββ
β β βββ AddMemoryCache() ββ
β β βββ AddDistributedMemoryCache() ββ
β β βββ AddPiranhaFileStorage() ββ
β β ββ
β β CreateApi() β IApi ββ
β β βββ Manually constructs Api with all repositories ββ
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Detailed Test Initialization Flow
Step 1: Service Collection Creation
// In BaseTestsAsync.CreateServiceCollection()
return new ServiceCollection ( )
. AddScoped ( _ => session )
. AddPiranhaStore < DbRaven > ( )
. AddPiranha ( )
. AddMemoryCache ( )
. AddDistributedMemoryCache ( )
. AddPiranhaFileStorage ( ) ;
Step 2: API Creation (Manual Composition)
// In BaseTestsAsync.CreateApi()
var factory = new ContentFactory ( _services ) ;
var serviceFactory = new ContentServiceFactory ( factory ) ;
var db = GetDb ( ) ;
return new Api (
factory ,
new AliasRepository ( db ) ,
new ArchiveRepository ( db ) ,
new ContentRepository ( db , serviceFactory ) ,
new ContentGroupRepository ( db ) ,
new ContentTypeRepository ( db ) ,
new LanguageRepository ( db ) ,
new MediaRepository ( db ) ,
new PageRepository ( db , serviceFactory ) ,
new PageTypeRepository ( db ) ,
new ParamRepository ( db ) ,
new PostRepository ( db , serviceFactory ) ,
new PostTypeRepository ( db ) ,
new SiteRepository ( db , serviceFactory ) ,
new SiteTypeRepository ( db ) ,
cache : _cache , // Optional: MemoryCache or DistributedCache
storage : _storage , // Local.FileStorage
processor : _processor // ImageSharpProcessor
) ;
Step 3: App Initialization
// In test InitializeAsync()
using ( var api = CreateApi ( ) )
{
// 1. Initialize the static App singleton
Piranha . App . Init ( api ) ;
// 2. Register custom test fields
Piranha . App . Fields . Register < MyFourthField > ( ) ;
// 3. Build content types from code attributes
new ContentTypeBuilder ( api )
. AddType ( typeof ( MyPage ) )
. AddType ( typeof ( MyBlogPage ) )
. Build ( ) ;
// 4. Seed test data
var site = new Site { ... } ;
await api . Sites . SaveAsync ( site ) ;
var page = await MyPage . CreateAsync ( api ) ;
await api . Pages . SaveAsync ( page ) ;
}
Test Classes Organization
test/Piranha.Tests/
βββ BaseTests.cs # Sync base class
βββ BaseTestsAsync.cs # Async base class (IAsyncLifetime)
βββ App.cs # App singleton tests
βββ Blocks.cs # Block tests
βββ Fields.cs # Field tests
βββ Serializers.cs # Serializer tests
βββ MemCache.cs # Memory cache tests
βββ Config.cs # Configuration tests
βββ Permissions.cs # Permission tests
β
βββ Services/
β βββ PageTests.cs # + PageTestsMemoryCache
β β # + PageTestsDistributedCache
β βββ PostTests.cs # + PostTestsMemoryCache
β β # + PostTestsDistributedCache
β βββ SiteTests.cs # + SiteTestsMemoryCache
β β # + SiteTestsDistributedCache
β βββ ContentTests.cs
β βββ MediaTests.cs
β βββ ParamTests.cs
β βββ AliasTests.cs
β βββ CommentTests.cs
β βββ PageTypeTests.cs
β βββ PostTypeTests.cs
β βββ SiteTypeTests.cs
β
βββ Hooks/
β βββ SiteHookTests.cs
β βββ ParamHookTests.cs
β βββ AliasHookTests.cs
β
βββ ImageSharp/
β βββ ProcessorTests.cs
β βββ MediaServiceTests.cs
β
βββ AttributeBuilder/
β βββ TypeBuilderTests.cs
β
βββ Utils/
βββ UI.cs
βββ Slug.cs
βββ FormatBytes.cs
βββ ...
Test Execution Pattern (Per Test Class)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Test Execution Pattern β
β β
β [Collection("Integration tests")] β
β public class PageTests : BaseTestsAsync β
β { β
β public readonly Guid SITE_ID = Guid.NewGuid(); β
β public readonly Guid PAGE_1_ID = Guid.NewGuid(); β
β β
β // βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β // PHASE 1: Initialize (IAsyncLifetime) β
β // βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β public override async Task InitializeAsync() β
β { β
β _services = CreateServiceCollection() β
β .AddSingleton<IMyService, MyService>() β
β .BuildServiceProvider(); β
β β
β using (var api = CreateApi()) β
β { β
β Piranha.App.Init(api); β
β new ContentTypeBuilder(api) β
β .AddType(typeof(MyPage)) β
β .Build(); β
β β
β // Seed test data β
β await api.Sites.SaveAsync(site); β
β await api.Pages.SaveAsync(page); β
β } β
β } β
β β
β // βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β // PHASE 2: Run Tests β
β // βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β [Fact] β
β public async Task GetById() β
β { β
β using (var api = CreateApi()) β
β { β
β var page = await api.Pages.GetByIdAsync(PAGE_1_ID); β
β Assert.NotNull(page); β
β } β
β } β
β β
β // βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β // PHASE 3: Cleanup (IAsyncLifetime) β
β // βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β public override async Task DisposeAsync() β
β { β
β using (var api = CreateApi()) β
β { β
β // Delete all test data β
β foreach (var page in await api.Pages.GetAllAsync()) β
β { β
β await api.Pages.DeleteAsync(page); β
β } β
β } β
β } β
β } β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Piranha runs the same tests with different cache implementations :
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Test Collection Variants β
β β
β PageTests β No cache β
β β β
β ββββΊ PageTestsMemoryCache β IMemoryCache β
β β β
β ββββΊ PageTestsDistributedCache β IDistributedCache β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// MemoryCache variant
public class PageTestsMemoryCache : PageTests
{
public override async Task InitializeAsync ( )
{
_cache = new Cache . MemoryCache (
( IMemoryCache ) _services . GetService ( typeof ( IMemoryCache ) )
) ;
await base . InitializeAsync ( ) ;
}
}
// DistributedCache variant
public class PageTestsDistributedCache : PageTests
{
public override async Task InitializeAsync ( )
{
_cache = new Cache . DistributedCache (
( IDistributedCache ) _services . GetService ( typeof ( IDistributedCache ) )
) ;
await base . InitializeAsync ( ) ;
}
}
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Test Database Setup β
β β
β BaseTestsAsync.CreateServiceCollection() β
β β β
β ββββΊ .AddPiranhaRavenDb(store) β
β β β
β βΌ β
β βββββββββββββββββββ β
β β RavenDb β β
β β (IDb + β β
β β IAsyncDocumentSession) β β
β βββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββ β
β β RavenDB TestServer β β DocumentStore β
β β β (created by RavenTestDriver) β
β βββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Summary: How Tests Initialize Piranha
Step
What Happens
Code Location
1
Create ServiceCollection
BaseTestsAsync.CreateServiceCollection()
2
Add RavenDB
.AddRavenStores()
3
Add Piranha services
.AddPiranha()
4
Add caching
.AddMemoryCache(), .AddDistributedMemoryCache()
5
Create Api manually
CreateApi() - constructs with all repositories
6
Initialize App
Piranha.App.Init(api)
7
Build content types
ContentTypeBuilder - scans for [PageType], [PostType]
8
Seed test data
api.Sites.SaveAsync(), api.Pages.SaveAsync()
9
Run tests
Each [Fact] / [Theory]
10
Cleanup
DisposeAsync() - deletes test data
Core Initialization Files
File
Purpose
core/Piranha/App.cs
Static singleton for app state
core/Piranha/IApi.cs
Main API interface
core/Piranha/Api.cs
API implementation
data/Piranha.Data.RavenDb/IDb.cs
Database context interface
data/Piranha.Data.RavenDb/Extensions/PiranhaEFExtensions.cs
Marten registration
core/Piranha/Extensions/PiranhaStartupExtensions.cs
DI registration
core/Piranha/PiranhaServiceBuilder.cs
Service builder
examples/MvcWeb/Program.cs
Example initialization
File
Purpose
test/Piranha.Tests/BaseTests.cs
Sync test base
test/Piranha.Tests/BaseTestsAsync.cs
Async test base
test/Piranha.Tests/Services/PageTests.cs
Example test class
test/Piranha.Tests/App.cs
App initialization tests
Generated for agent orchestration workflows