Skip to content

Latest commit

Β 

History

History
605 lines (537 loc) Β· 33 KB

File metadata and controls

605 lines (537 loc) Β· 33 KB

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.


Table of Contents

  1. Piranha CMS Initialization Map
  2. Unit Test Initialization Map

1. Piranha CMS Initialization Map

High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                           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                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Detailed Component Flow

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)

4. App Initialization

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

5. Runtime Request Flow

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)

Key Interfaces Summary

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

Database Access Pattern

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

1. Test Framework Stack

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           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);                β”‚
β”‚              }                                                      β”‚
β”‚          }                                                          β”‚
β”‚      }                                                              β”‚
β”‚  }                                                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Cache Testing Variants

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();
    }
}

Database Connection

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    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

Key Files Reference

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

Test Files

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