Bump the testing group with 4 updates - #6
Closed
dependabot[bot] wants to merge 50 commits into
Closed
dependabot[bot] wants to merge 50 commits into
dependabot[bot] wants to merge 50 commits into
Conversation
to support multiple event types per handler.
MagicCSharp was extracted from the backend in June and the two diverged; every shared file differed. This brings the backend's improvements across and splits the packages so a consumer takes only what it uses. Packages Six new: AspNetCore, Scheduling, Data.EntityFramework, Data.Postgres, Testing and Testing.Database. Core drops from six dependencies to three and no longer pulls in ASP.NET Core or distributed locking; Data no longer pulls in Entity Framework, so a domain project can reference the repository interfaces without taking on a persistence library; Testing has no xUnit or Testcontainers, so wanting FakeClock does not mean wanting Docker. Data One IRepository<TEntity, TKey, TEdit, TFilter> replaces the two interfaces that hardcoded long. Soft delete, pagination and search become opt-in interfaces with base classes for both key shapes. Adds GetKeys, batch update by dictionary and by list, delete by filter, untracked reads, and the re-read after write that populates navigation properties. MagicDbContext stores enums by name (including inside JSON columns) so inserting an enum member no longer changes what existing rows mean, and normalizes timestamps to UTC. The Postgres package adds a pooled context factory that fails at startup on a bad connection string, a design-time factory for dotnet ef, and a command interceptor covering the query predicates and ExecuteUpdate/Delete that never reach SaveChanges. Testing New. A clock you move by hand, ids derived from it, an event dispatcher that runs handlers inline so assertions need no sleeping, a re-entrant in-memory lock, and a base class for repository tests against real PostgreSQL in Testcontainers. Fixes - AddMagicUseCases picked implementations with FirstOrDefault, so two implementations meant one was chosen silently by reflection order. Now throws and names both. - The same scan only saw already-loaded assemblies, so use cases in an untouched project were never registered. - LocalEventDispatcher blocked while Kafka and SQS are fire-and-forget, so a handler re-entering its emitter's lock worked locally and deadlocked in production. - ScheduledBackgroundService logged its next run from the wall clock while scheduling from the injected IClock. - Core pinned Microsoft.AspNetCore.Http.Abstractions 2.2.0. Tooling tools/ holds AddEntity, ValidateConventions and SyncAllProjects as single-file programs, keyed off magiccsharp.json rather than hardcoded paths so they work in any repository using the layout. Tests 47, where there were none. Libraries stay on net9.0 so net9 and net10 apps can both consume them. See CHANGELOG.md for the full list and the migration guide.
Port framework improvements from backend and split into eleven packages
The tools documented the Apps/ and Libs/ layout but there was no way to create one — AddEntity assumed projects that nothing could scaffold. This adds the missing half, so the structure is something you can adopt rather than something you have to reproduce by hand. It stays optional. Nothing in the packages reads magiccsharp.json or cares where a file sits; a consumer using the packages in their own structure is unaffected. New tools - InitRepo sets a repository up: magiccsharp.json, Directory.Build.props, central package management, the all-projects solution, Apps/ and Libs/. Skips whatever exists, so it composes with a repository already running. - CreateApp creates a service: host project, its own solution, and the pair of data projects that keep repository contracts separate from their EF implementation. --no-database for a service owning no tables. Picks a port no other service has claimed. - CreateAppLib creates a domain: Default for use cases, Models for entities, Tests. Wires Default to Models and never the reverse, since Models is what the data projects depend on. - CreateLib creates a shared library under Libs/, dots nesting directories. Documentation docs/repository-layout.md covers setup, what each boundary is for, what belongs in Libs/ and what does not, and the tool reference. tools/README.md becomes a short reference pointing at it. The root README gains the layout as an explicitly optional section. Two things the end-to-end test caught - The generated Directory.Build.props applied to the tools themselves, so a repo targeting net9.0 produced scripts the net10 runtime refused to launch. tools/Directory.Build.props now pins the scripts and does not inherit the repository's settings. - Generated repos targeted net9.0. The libraries stay there so net9 apps can consume them, but a newly scaffolded repo should not start a version behind; templates now generate net10.0. publish-all.sh keeps InitRepo's pinned version in step with the release, so scaffolding cannot point at a version predating its own tooling. Verified from an empty directory: InitRepo, CreateApp, CreateAppLib, CreateLib and AddEntity produce a repository that builds against the packed packages, passes ValidateConventions, and serves a request with the request-ID middleware live. Every tool is idempotent — second runs produce no diff.
tools/README.md had been slimmed to a table when the layout guide was added, which left it pointing at the guide for everything and carrying no usage of its own. SyncAllProjects had no worked example anywhere. Each tool now gets a section with its real options and runnable commands, verified against the CommandOption declarations rather than written from memory. SyncAllProjects gains the two occasions you actually run it by hand: a conflicted solution file after a merge, and moving a project outside the tools. The guide keeps the narrative and now points at the reference for options.
Adopting the layout meant cloning this repository and copying tools/ out of
it — for a consumer who otherwise only needs the NuGet packages, with no
version link between the scripts they copied and the packages they
reference, and no way to update.
dotnet new install MagicCSharp.Templates
dotnet new magiccsharp-repo -n Acme
Lays down magiccsharp.json, Directory.Build.props, central package
management, the all-projects solution, Apps/, Libs/ and tools/, with the
prefix substituted everywhere — including the scripts' own --help examples,
so a generated repository's tooling talks about its own namespaces.
--MagicCSharpVersion and --TargetFramework are parameters. publish-all.sh
updates the version default alongside InitRepo's, so a generated repository
cannot pin a version predating its own tooling.
The scripts are copied from tools/ by an MSBuild target when the package is
built, not duplicated into the template, so editing a script updates both.
The copy is gitignored.
InitRepo stays: it is now the path for adding the layout to a repository
that already exists, which the template cannot do.
Two packaging bugs found by installing it: ContentTargetFolders nested the
already-content-rooted paths a second time, and NuGet's default exclusion
of dotfiles dropped the .gitkeep files that create Apps/ and Libs/.
Verified end to end: install the packed template, generate Contoso, then
CreateApp, CreateAppLib and AddEntity produce a repository that builds
against the packed packages, lints clean, and serves a request with the
request-ID middleware live. Namespaces are Contoso throughout.
Adopting the layout meant either cloning this repository and copying tools/
out of it, or committing the tooling via the dotnet new template. Neither
suits someone who just wants the scaffolding available everywhere.
curl -fsSL https://github.com/@raw/MagicDoorInc/MagicCSharp/master/install.sh | bash
mcs init --prefix Acme
mcs create-app --name Shop --database shop
Installs the scripts to ~/.magiccsharp/tools and a dispatcher to
~/.magiccsharp/bin. Not ~/.magicdoor: MagicDoor's own md CLI already keeps
state there, and MagicCSharp is a framework other teams use — including,
eventually, MagicDoor's backend as one consumer among others.
Scripts now resolve templates from MAGICCSHARP_TOOLS_DIR when set, falling
back to tools/Templates, so a repository that vendored the tools keeps
working and both install modes are supported.
Update check
Once a day, in the background, printing a one-line notice on the next
command. It compares commit SHAs rather than package versions: the tools
are installed from a git ref, and scripts/version.txt tracks NuGet releases
on its own schedule — it currently says 0.0.11 while nuget.org has 0.0.13,
which would have made a version comparison lie. Never blocks a command,
only speaks on a terminal, and MAGICCSHARP_NO_UPDATE_CHECK=1 disables it.
The installer checks for SDK 10 before writing anything, since the scripts
are file-based apps and would otherwise fail confusingly on first use.
One bug worth naming: under set -euo pipefail, pending="$(cat missing |
tr ...)" aborts the dispatcher, because cat's failure propagates through
pipefail into the assignment. It only reproduced on a terminal — piped runs
returned earlier at the tty guard. Every state read now goes through one
helper that treats a missing file as empty.
Verified: install into a sandbox home, then init, create-app,
create-domain, add-entity, create-lib, sync and validate in a directory
with no tools/ of its own, producing a repository that builds. The update
check was exercised by faking a stale commit — flags in the background,
notifies on the next run, stays quiet within 24h.
The generators' templates were only reachable inside tools/, so a team that wanted its own DAL shape had no way to say so short of forking the tools. Templates now install to ~/.magiccsharp/templates, beside the scripts rather than inside them, and resolve through three layers with the first match winning: 1. .magiccsharp/templates/ in the repository, committed and shared 2. ~/.magiccsharp/templates/ installed with the tools 3. tools/Templates/ a repository that vendored the tools Resolution is per file. Overriding Entities/dal.cs.hbs leaves the other eighteen built-in and still tracking upstream, so taking one override does not mean owning all of them forever. The directory is the "templates" key of magiccsharp.json, defaulting to .magiccsharp/templates. Point it at a shared submodule, or set it to "" to turn overrides off — eject then refuses rather than writing somewhere that would be ignored. InitRepo writes the key explicitly so the mechanism is discoverable from the config rather than only from documentation. New Templates tool, exposed as `mcs templates`: list [--overridden] every template, and which layer provides it where the layers, in search order eject <path> copy a built-in in to customise Verified: eject a template, edit it, generate an entity, and the generated file carries the edit while a non-overridden template still produces the built-in output — and the result builds. A custom "templates" path is honoured, and "" disables overrides.
The exported line was hardcoded to $HOME/.magiccsharp/bin while the install location comes from MAGICCSHARP_HOME, so a custom install wrote a PATH entry for a directory that does not exist and left mcs unreachable. The "already configured" check had the same assumption and would have appended a second line on every re-run of such an install. Both now derive from the resolved bin directory, still written back through $HOME when it sits under the home directory so the profile stays portable between machines. Found by actually exercising the branch: every previous test passed NO_MODIFY_PATH=1, so the profile-writing path had never run. Now covered for zsh, bash with an existing .bash_profile, an unrecognised shell, a re-run, a custom MAGICCSHARP_HOME, and NO_MODIFY_PATH. Documented what the installer writes and where.
dotnet tool install -g MagicCSharp.Cli
mcs init --prefix Acme
The scripts worked, but each was a standalone file, so shared code had to be
copied between them: TemplateResolver lived in six files, RepoConfig in four,
about 860 of 2,593 lines duplicated. Fixing anything meant editing it six
times — the layered template resolver went in via a scripted patch, which is
the smell showing.
One project instead of eight files. The duplication is gone, and the logic
is testable for the first time: 50 tests covering template resolution,
naming, source edits and solution-file handling, where there were none.
dotnet tool also replaces machinery written by hand. install.sh — 250 lines
of bash doing shell detection, PATH editing, and a daily update check — is
deleted, along with the vendored tools/ directory. `dotnet tool update` is
the update story, and a tool manifest is the team story: pin the version in
.config/dotnet-tools.json, `dotnet tool restore`, `dotnet mcs`. The
dotnet new template now scaffolds that manifest instead of copying scripts.
Templates are embedded in the assembly rather than shipped as loose files,
so there is no path to resolve and nothing to go missing. Overrides are
unchanged — .magiccsharp/templates/ still wins, per file — and now resolve
against the built-ins rather than a third on-disk layer.
One bug worth naming, found because the tests asked for every template by
name: MSBuild infers a culture from the second-to-last extension, so
entity.cs.hbs reads as culture "cs" — Czech — and the eight *.cs.hbs
templates were routed into a cs/ satellite assembly. The build reported all
19 embedded resources; only 11 reached the DLL, and every generator that
writes C# silently produced nothing. WithCulture="false" fixes it, and a
test now asserts all 19 are present.
Verified: pack, install globally, and run init, create-app, create-domain,
add-entity, create-lib, validate and sync in an empty directory, producing a
repository that builds and serves a request. Then again through the template
and a tool manifest with `dotnet mcs`. Template overrides confirmed to apply
per file. Every command idempotent.
scripts/version.txt said 0.0.11 while nuget.org had 0.0.12 and 0.0.13 published, so the file the release script bumps from was two releases behind what consumers could actually install. A --patch release would have tried to publish 0.0.12 again. The dotnet new template pins a MagicCSharp version in the repositories it scaffolds, in both .config/dotnet-tools.json and Directory.Packages.props, and both come from one default in template.json. That sync was lost when install.sh went away, so a generated repository would have referenced whatever version happened to be hardcoded. Restored. Also adds --dry-run. The script bumps the version before it packs, so verifying that a release builds used to leave the bump and thirteen .nupkg files behind; now it restores the worktree and deletes them.
The override mechanism was described in passing — a sentence saying the directory could be "a git submodule shared across repositories" — with no instructions for actually doing it. Once a company has more than one repository that is the case that matters, because copying overrides between them means house style drifts and a fix to one never reaches the others. docs/template-overrides.md covers both: the one-off eject for a single repository, and building a template repository other repositories consume as a submodule at .magiccsharp/templates — creating it, wiring it in, the clone and CI flags a submodule needs, and how changing a shared template rolls out per repository rather than to everyone at once. Two fixes found writing it: - `mcs templates eject` printed a red "magiccsharp.json not found" before succeeding when run outside a repository. The template commands only need the config to locate the override directory, which has a default, so a missing config is ordinary rather than an error. They use a quiet load now, which is what makes it possible to build a template repository in an empty directory — the first step of the documented flow. - The documented template variables were written from memory and were wrong. Extracted them from the templates instead, per family: Repo gets version and target_framework, Apps gets database.enabled and friends, Libraries gets assembly_name, Entities gets the entity names and flags. Also converts the last `dotnet run tools/X.cs` invocations left in the guide and the template README to mcs commands.
It was displaced when the CLI section replaced the installer's, leaving the mechanism described only in a half-sentence. Also corrects the dotnet new template's description, which still said it ships a tools/ directory.
The README only carried a link to the override doc, so someone reading it front to back would not learn the mechanism exists — and it is one of the reasons to adopt the layout at all. Adds a short section: what eject does, that the copy is committed and reverting is deleting it, that resolution is per file so taking one template does not mean owning all nineteen, and the shared template repository for teams with more than one repository. The detail stays in docs/template-overrides.md. Every internal link in the README and both docs verified to resolve.
The docs stated that an override "is committed like any other file" — true, but passive, and easy to read past. Committing that directory is the whole mechanism for sharing templates with a team, so it should be an instruction rather than an observation. `mcs templates eject` now prints the git add command, at the moment the file has just been created and nothing yet suggests it is not shared. Both the README and the override guide give the command rather than describing it. It also checks whether the path is git-ignored and warns instead, because an ignored override works perfectly for whoever wrote it and reaches nobody — the kind of thing that goes unnoticed for months. Not being in a git repository at all counts as not ignored, since that is how a shared template repository starts out. Three tests: the override path eject writes to is the one the generators read back, a path outside any repository is not reported as ignored, and a missing git does not take the command down.
Three places where the framework defined half of something and left the application to supply the rest. Scheduling did not work at all. ScheduledBackgroundService resolves IScheduleStore with GetRequiredService, and the packages shipped no implementation — so "drift-free scheduling", advertised in the README, threw the moment anyone used it. Adds InMemoryScheduleStore and AddMagicScheduling(), which also registers a file-system lock provider. Both defaults are single-machine and the XML docs say exactly what that costs: two instances each keep their own schedule state and both run the job. Registering your own first wins. Nothing mapped exceptions to responses. The framework throws NotFoundException for a row that is not there, and it reached the caller as a 500 with a stack trace. AddMagicErrorHandling/UseMagicErrorHandling maps it to 404, validation and argument failures to 400, and a cancelled request to 499 rather than counting it as a server error. Outside Development an unexpected exception returns a generic message and logs the detail — its message routinely carries a connection string. Adds HttpException and the status-carrying subclasses for when a use case genuinely means a status code, while pointing at the domain exception as the better default: a use case that throws NotFoundException still works from a queue consumer. Also ValidateServices, resolving every registration at startup so a miswired dependency fails the deploy rather than the first request that needs it. Generated apps get all three wired in. One bug found by running it: ValidateServices tried to resolve keyed services, which need their key, so an ordinary app using AddOpenApi failed to boot. A preflight that stops startup on a false positive is worse than no preflight. Keyed registrations are skipped. Verified against a running generated service in Production: /probe/missing returns 404 with the entity name, /probe/boom returns a generic 500 with the connection string in the log and not the response.
Two things. MagicCSharp.App bundles the four packages a web service needs — core, AspNetCore, Events, Scheduling — behind builder.AddMagicApp() and app.UseMagicApp(builder). A generated Program.cs went from thirteen wiring lines to two, and from four package references to two. It is a shortcut, not a layer: everything it calls is public on the package that owns it, so outgrowing the defaults means replacing two lines with five. MagicAppOptions turns any piece off, and registering a transport or a schedule store first means the single-machine defaults step aside rather than fight. The granular packages remain the answer for a worker, a console app or a test project. Also fixes AddMagicCSharp, which did not forward the assembly filter its own AddMagicUseCases accepts. README: the Quick Start told people to install five packages and wire them by hand, which is now the third-best way to start and was the only one documented. It leads with the CLI, then MagicCSharp.App for an existing project, then the granular list. The repository layout was three-quarters of the way down and explained what the structure is without saying why anyone would want it. It now opens with what it buys: one repository so a cross-service change is one pull request, per-service solutions so you still build one at a time, a domain that does not know how it is stored, every entity looking the same, and conventions that are checked rather than agreed.
Package metadata. Three packages shipped RepositoryUrl=github.com/yourusername/magiccsharp in their nuspec, and the author name disagreed between packages. src/Directory.Build.props now holds the shared metadata, the version, and GenerateDocumentationFile — the XML comments were not in any of the twelve nupkgs, so none of that guidance reached IntelliSense. Symbols and SourceLink ship too. That also collapses the version bookkeeping. version.txt said 0.0.13, every csproj said 0.0.11 and the dotnet new template pinned 0.1.0, because the release script sed-ed fourteen files that could drift. It rewrites one line now, and --dry-run restores files one at a time — a single git checkout aborted on the untracked props file and silently left the rest bumped. Kafka delivery claims. The README promised "Guaranteed Delivery — persisted before returning", "Manual Commit" and at-least-once. None held: ProduceAsync's task was discarded behind a TODO, so a rejected message failed silently; EnableAutoCommit was unset, so Confluent's default committed offsets on a timer and made the listener's careful manual commit decorative. Both fixed in code — the produce is observed and failures logged, auto-commit and auto-offset-store are off. The third claim could not be fixed by config: AsyncEventDispatcher catches handler exceptions so one failure cannot stop the others, which means the transport always sees success. Both transport READMEs now say what is true — at-least-once to the dispatcher, at-most-once per handler — and what to do about it. Events README documented an instance Priority property while the dispatcher reads a static one, so every priority in the docs was silently ignored. Also adds the registration order: RegisterMagicEvents discovers handlers and must come before any transport, which alone only registers the dispatcher. Root README's Setup block had the same missing call and predates MagicCSharp.App.
CLI - create-domain --name Orders produced Apps/Shop/Shop.Domains//Default with a double slash and a wrong assembly name, then add-entity failed on the projects it did not find. The short form is now accepted and reported. - init writes a .gitignore. Without one the first build left hundreds of bin/ and obj/ files staged. - --version worked but crashed; a bad flag printed a Spectre stack trace. Parse and runtime errors now print their message and point at --help. Error handling, all found by looking at real responses - Response.ContentType was set and then overwritten by WriteAsJsonAsync, so problem responses went out as application/json. The content type goes to the write now. - The requestId in the body was Kestrel's connection counter while the header carried the real id. It comes from IRequestIdHandler, and UseRequestId now runs before the error handler so the id exists when the problem is written — and so the handler's own log line carries it. - UseExceptionHandler logged every handled exception at Error, including the 404s this module treats as routine. Replaced with explicit middleware, so there is one log line at the level the status implies. - X-Request-ID was echoed back unbounded; a 3000-character header ended up in every log line for the request. Capped at 128 and to printable ASCII. Naming, before any of it is published under these names - RegisterMagicEvents/RegisterLocalMagicEvents/RegisterMagicKafkaEvents/ RegisterMagicSQSEvents/RegisterSnowflakeKeyGen become AddMagicEvents/ AddLocalMagicEvents/AddMagicKafkaEvents/AddMagicSqsEvents/ AddSnowflakeKeyGen, matching every other registration method. The old names remain as [Obsolete] forwarders. - AddLocalMagicEvents is self-sufficient. It used to register only IEventDispatcher, so calling it alone — which the README told people to do — failed at resolution on the first dispatch. AddMagicEvents is now idempotent, so the transports calling it does not double-register. - HttpExceptions' NotFoundException and NotImplementedException collided with the domain exception and with System's under implicit usings; the latter made any file importing the namespace fail to compile. Renamed HttpNotFoundException and HttpNotImplementedException. Correctness - SearchText stripped every non-ASCII letter, so "Søgaard" indexed as "sgaard" and no one searching the actual name found it. - MagicEventSerializer keyed events by simple name via ToDictionary, so two same-named events in different namespaces crashed startup with no clue which. It now names both. CI .github/workflows/ci.yml builds, tests and lints, then does what the CHANGELOG claims and nothing automated: packs all fourteen packages, installs the CLI from that feed, scaffolds two services, a domain, two entities and a shared library from an empty directory, builds it, re-runs every command and fails if the worktree changed, then boots a service and checks it answers with a request-id header.
Building a two-service example repository with the mcs CLI, then running it
against Postgres, turned up five bugs. Each was silent: the code built, the
service started, and the wrong thing happened without a word.
Event handlers in a domain project never ran. Discovery scanned
AppDomain.CurrentDomain.GetAssemblies(), which lists only assemblies .NET has
already loaded, and it loads one lazily on first use of a type in it. A domain
project holding nothing but handlers is referenced by the host and used by
nothing, so it was not there. The dispatch returned 202 and no handler received
it. Reading the reference graph does not fix this either: the C# compiler
leaves a reference out of the compiled metadata when no type from it is used,
which is exactly that project. ApplicationAssemblies now loads the managed DLLs
deployed next to the executable, so what gets discovered follows what the
application ships. Both use-case and event-handler discovery go through it, and
the old advice to touch a type per project at startup is retired.
create-domain scaffolded a domain and left it unreferenced by the service that
owns it, so the assembly was never deployed and the above could not have saved
it anyway. It now adds the reference, as create-app already does for the data
projects.
The domain project template composed its own assembly name as
{prefix}.Libraries.{name}, leaving the service out, so two services with a
same-named domain both produced Acme.Libraries.Domains.Orders.
The HTTP layer ignored the framework's own JSON conventions. JsonDefaults was
wired into Postgres jsonb columns and nowhere else, so the same enum was a name
in the database and a number over the wire, and Optional<T> did not round-trip
through a request body — a PATCH could not tell "set this to null" from "do not
touch this", the one distinction that type exists to make. Only the two
converters are applied, for controllers and minimal APIs both; JsonDefaults
wholesale also sets IgnoreReadOnlyProperties, which suits a jsonb column and
would drop Pagination.TotalPages and every other computed property from a
response.
Also, smaller:
- GetOrThrow on IRepository. Update and Delete throw NotFoundException for a
missing key while Get returned null, so every endpoint fetching by id wrote
its own throw to get a 404 out of the error handling.
- DB_VERIFY_CONNECTION turns off the startup connection check from
configuration. Opening a connection while registering is right by default,
but there was no way to turn it off without editing the registration, which
blocked booting a service in a test that replaces every repository.
- create-domain and add-entity take a service name: --solution Shop rather than
--solution Acme.Shop.slnx, and no flag at all when the repository has one
service. More than one and no flag lists them rather than guessing.
152 tests, up from 127. The new MagicCSharp.App.Tests boots a real host; four
of its six tests failed before the JSON fix.
InsertBefore spliced the new line in at the anchor, which sits after its own leading whitespace, so the caller's eight spaces were added to the anchor's eight and the anchor was pushed onto a fresh line of stray spaces. Every generated repositories module carried it, blank lines between each registration included — and since it is the first generated code anyone reads, it is the first impression the scaffolding makes. It now reads the anchor's indentation and applies it, inserting after the last statement so the blank line separating the body from `return services;` stays where the template put it and successive entities group together.
The scaffold job commits a baseline so it can assert that re-running every mcs command leaves the worktree untouched. A runner has no git identity configured and none to derive — the account has no full name — so the commit failed with "empty ident name" and took the job with it. Passed per-command rather than configured globally: the repository it commits to is a scratch one under RUNNER_TEMP, and nothing else in the workflow commits. Both jobs run clean locally now, the whole scaffold sequence included: pack thirteen packages, install the CLI from that feed, scaffold two services with a domain, two entities and a shared library, build, re-run every command with no change, and boot the service to check the body and the X-Request-ID header.
Offer the repository layout, with tooling to create and maintain it
The layout could express one thing inside a service: a flat domain. The backend
has needed more than that for a while and does it with Scripts/CreateAppLib.cs,
which mcs had no equivalent of, and the layout doc never explained.
One rule carries it: dots in a library name nest directories, and the leaf gets
the Default/Models/Tests triple. The first segment joins the service name.
create-app-lib --name Processors Apps/Shop/Shop.Processors
create-domain --name Orders Apps/Shop/Shop.Domains/Orders
create-domain --name Orders.App Apps/Shop/Shop.Domains/Orders/App
create-domain --name Orders.Fulfilment
Apps/Shop/Shop.Domains/Orders/Fulfilment
Three behaviours follow from the name and nothing else: a name under Domains.
is referenced by the host, or the assembly is not deployed and its use cases
never register; a name ending in App is a domain's HTTP surface and is rendered
from a new template with the ASP.NET framework reference and a reference to the
domain it fronts; --models points Default at Models.
The .App is the point of the change. A domain's controllers live with the domain
instead of piling into the service's host project, and the host needs no wiring
for them: ASP.NET finds controllers in a referenced assembly, and
ApplicationAssemblies finds the use cases. Verified by serving one.
A subdomain's reference to its parent is offered, not added. Most want it, but
an unnecessary project reference never announces itself, and unlike the .App
case it is not true by definition.
create-domain is create-app-lib with Domains. prepended; its surface is
unchanged. create-lib gains --models for symmetry, and add-entity --domain takes
a dotted subdomain while its repository, DAL and context stay in the service's
one Data project.
--tests-only is not carried over. In the backend it silently disables --models,
strips a reference by regex, and marks a fixtures library IsTestProject, so
dotnet test opens it and warns. A fixtures library is an ordinary app library
with two package references, which the doc now shows.
AppLibrary holds every path and naming rule so they are testable without a disk;
38 of the 194 tests are its theory table and the names it refuses. Scaffolded
all four shapes, built them, served a controller from a domain's App, placed an
entity in a subdomain, and ran both CI steps locally.
Shop.Domain/Orders looks right in the tree, builds, ships, and does nothing: only Shop.Domains/ is referenced by the host, so a singular typo produces a domain whose use cases never register and whose handlers never fire. Nothing reports it, which is the same silent failure the host-reference wiring exists to prevent — reintroduced by one letter. The first segment is now checked against Domains at an edit distance of one, transpositions included, so Domain, Domians, Domans and Domainss are all refused with the name they probably meant. Length differing by more than one returns immediately, so Documents.Templates, Testing.Fixtures and Clients.Stripe are untouched; there are tests for both halves. An error rather than a warning: the whole problem is that the wrong thing looks like the right thing, and a warning scrolls past.
Around 1,500 lines whose whole job is producing correct SQL — BaseIdRepository, BaseKeyRepository, the filter extensions, soft delete, search, pagination — had no test that opened a database. The framework even ships the harness for this, MagicCSharp.Testing.Database with Testcontainers, and never used it once. Unit tests cannot cover this. Faking DbContext means asserting that a fake behaves like the fake; the things that actually break — a filter that translates wrong, a soft delete that forgets its WHERE, a timestamp written in local time — only appear when Postgres runs the query. Fifty tests over two fixtures. Create writing every field, ids assigned before insert rather than by a sequence, timestamps from the clock and returned as UTC, enums stored as their name rather than an ordinal (asserted with raw SQL against the column), filters by string, substring, enum and number and the combination of them, all four update overloads including that a partial batch writes nothing, delete by key, by keys and by filter, pagination's 1-based skip and that pages neither overlap nor lose rows, soft delete leaving the row present but out of default results, and the search column matching case, punctuation, synonyms on both sides, and a non-ASCII name. That last one is worth calling out: the Søgaard fix earlier today was tested on the string only. This is the first time it has been through Postgres. Checked the tests bite rather than merely pass, by making pagination 0-based again — Pages_do_not_overlap_and_cover_everything fails, as it should. One thing the fixtures document by existing: soft delete and search are sibling base classes, so no repository can inherit both. The Contact table is served by two repositories for that reason. They need Docker, and CI has it. Without it, dotnet test --filter "Category!=Database".
Testcontainers 4.1.0 depends on SSH.NET 2024.1.0, which has a high-severity advisory. Every repository mcs generates sets TreatWarningsAsErrors, so adding MagicCSharp.Testing.Database to one failed the build outright with NU1903 — found by doing exactly that in the example project. The framework could not see it: its own build did not treat NuGet audit warnings as errors, so it shipped a package that only broke for consumers. NU1901-NU1904 are now errors here as well, which is the part that stops this recurring. Testcontainers moves to 4.15.0, whose SSH.NET is patched.
CONTRIBUTING: how to build, how to run the tests without Docker, how to try a CLI change against a scratch repository, and the two things worth raising in an issue before writing code — a new package, and a change to the shape of a generated file, both hard to reverse once people have repositories built on them. SECURITY: private vulnerability reporting rather than a public issue, an honest response expectation for a one-maintainer project, and what is actually in scope — the CLI writes files into your repository, templates are compiled, error handling decides what a caller is told, and the data packages build SQL from filters. GOVERNANCE: where the architecture came from and, more usefully, what is true about it today — MagicDoor runs the structure with one reference service fully in this shape, and does not yet consume these packages. It runs a copy that has diverged. "Battle tested" is a claim about the structure, not the packages, and this file will keep saying so until convergence has happened. It also settles the divergence question in advance rather than per argument: the framework stays generic, and MagicDoor's house style lives where every other adopter's does — a template repository at .magiccsharp/templates, and its own Libs/. The override mechanism already existed; this makes it the policy. Dependabot for NuGet and Actions, grouped so the Microsoft packages arrive as one pull request. A vulnerable transitive dependency shipped here once and was caught only when a consumer's build failed on it; NU1901-NU1904 are build errors now, and this is the half that opens the pull request rather than waiting. Repository description and ten topics, both previously empty, so the project can be found by someone searching for what it is.
The README sold a distributed-systems toolkit. "Distributed" appeared twenty times and "subdomain" none, the first code block called methods the code marks [Obsolete], and the layout — the reason to use any of this — started at line 406 under a heading saying "optional", below four hundred lines of checkmarks. 586 lines to 213. It now opens with what the thing is: one deployable, a tree of domains, a shape that is decided, enforced and generated, and a service that reads the same at a hundred use cases as at ten. Then the tree, because the diagram is the pitch. Then sixty seconds that ends with a domain in the service rather than a HelloController the template tells you to delete — and that starts Postgres first, because create-app --database opens a connection at registration and the old fast path could not boot. What went: the checkmark walls, Real-World Benefits, Philosophy, the closing call to action, and the fifty-line Complete Example pointing at the flat shape this layout exists to replace. What stayed, edited: the packages table, "what it buys you", and the paragraph on nothing being overwritten. The insurance example is stated at what is actually true rather than what is memorable. No type in the parent's contract project names a provider; its logic names one, in a statistics use case. The earlier claim of "no using names a provider" was wrong — a grep with the wrong namespace prefix, and a reader would have found it in a minute. Also: current method names throughout, since AddLocalMagicEvents and its siblings appeared in no document at all; fourteen packages, counted; the template count corrected to twenty-one in the three places it disagreed; and docs/repository-layout.md no longer points at a tools/ directory deleted two releases ago or describes the CLI as single-file scripts.
The last rewrite over-corrected. It fixed a README that sold a generic infrastructure bundle and never mentioned the shape, by selling the shape and pushing everything else into twenty lines near the bottom under "Without the layout". That is the wrong trade: the use cases, repositories, events and clock work in isolation, and adding one package to a project you already have is how most people will meet this. It is also the smallest commitment on offer, so it is the door most likely to be used. The opening now names both halves and says either works without the other, so a reader knows in a paragraph which one they came for. Use cases get the space they earn: the class, the one registration call, what that call refuses (two implementations of an interface, an interface with none) and why the interface matters. Then testing, which is the argument for the whole arrangement — two fakes and a constructor, no host and no database — followed by what else is in the box in prose rather than a table of checkmarks. The layout follows, as the arrangement the packages were built for, keeping the tree, the sixty seconds and what the shape buys. Two corrections while writing it: the lifetime attribute takes ServiceLifetime, not a Lifetime enum that does not exist; and the test example says plainly that both fakes are a few lines you write rather than doubles the framework ships, because it does not ship them and SyncEventDispatcher — which earlier docs offered for this — needs a container. 281 lines. 160 for the packages, 121 for the layout.
Still the wrong weighting. The packages are the product — use cases, repositories, events, the clock — and the layout and CLI are something extra on top for people who want the whole arrangement. The last version gave them equal billing under a top-level heading and 121 lines. The opening now describes the packages and what they do, says every one stands alone, and mentions the layout in one sentence as optional and last. The layout section is 46 lines: the tree, a paragraph on what it buys, the commands, and a link to the guide that already covers it properly. It was 121. Also removed the duplicate link list — "Going further" was repeating the two links the layout section had just given — and reworded the provenance, which described only the layout as coming from MagicDoor's backend when the packages came from the same place. 233 lines: 152 for the packages, 46 for the layout.
Cutting the marketing also cut the teaching. The old README showed how to write a use case, dispatch an event, handle it in two places and swap the transport; the replacement had a paragraph per capability. A paragraph does not let anyone decide whether they want this. Repositories and events now get sections that show the code. Repositories: the entity/edit/filter triple and why they are three types, the interface the domain depends on, and the two members an implementation actually writes — CreateDal and ApplyFilter — with everything else inherited. Events: the record, two handlers for one event, static Priority and why it is static, and the one line that differs between in-process, Kafka and SQS, with the fire-and-forget model stated rather than implied, because a handler written against blocking dispatch breaks on the day you switch. Time, ids, jobs and errors get shorter sections with the same rule: show the call, say what it buys. The scheduling example is the one I checked hardest, having written it wrong first. ScheduledBackgroundService takes five constructor arguments and three abstract members — there is no ISchedulePolicy and no RunAsync. I compiled the README's version against the real package before committing it. The Scheduling package README had the same defect from earlier: a two-argument constructor that does not exist, and no mention of AddMagicScheduling, without which the service throws the first time it records a run. Both fixed. 377 lines: 296 for the packages, 46 for the layout.
MagicCSharp.Templates hardcoded its version at 0.0.11 and sits outside src/, so the release script's bump never reached it; a 1.0.0 release would have shipped it as 0.0.11. It now reads scripts/version.txt. Every package README becomes its NuGet page and is frozen per version, so the old Register* method names are replaced with the Add* names they forward to. Also: the CHANGELOG entry becomes 1.0.0 and counts the packages correctly, scripts/README.md describes the script as it is, and Testing.Database stops calling a Testcontainers constructor marked for removal.
Shipping them in 1.0.0 would keep them until 2.0.0, and they save little: the upgrade from 0.0.x is breaking anyway. The CHANGELOG migration section lists each old name and its Add* replacement.
Bumps Microsoft.NET.Test.Sdk from 17.12.0 to 18.10.1 Bumps xunit from 2.9.2 to 2.9.3 Bumps xunit.extensibility.core from 2.9.2 to 2.9.3 Bumps xunit.runner.visualstudio from 2.8.2 to 4.0.0 --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.10.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.10.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.10.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.10.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.10.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: xunit dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testing - dependency-name: xunit dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testing - dependency-name: xunit dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testing - dependency-name: xunit dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testing - dependency-name: xunit dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testing - dependency-name: xunit.extensibility.core dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testing - dependency-name: xunit.runner.visualstudio dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: xunit.runner.visualstudio dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: xunit.runner.visualstudio dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: xunit.runner.visualstudio dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing - dependency-name: xunit.runner.visualstudio dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: testing ... Signed-off-by: dependabot[bot] <support@github.com>
Author
|
This pull request was built based on a group rule. Closing it will not ignore any of these versions in future pull requests. To ignore these dependencies, configure ignore rules in dependabot.yml |
dependabot
Bot
deleted the
dependabot/nuget/src/MagicCSharp.Testing.Database/testing-776542c19d
branch
September 23, 2026 23:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updated Microsoft.NET.Test.Sdk from 17.12.0 to 18.10.1.
Release notes
Sourced from Microsoft.NET.Test.Sdk's releases.
18.10.1
What's Changed
Full Changelog: microsoft/vstest@v18.10.0...v18.10.1
18.10.0
What's Changed
vstest.consoleand datacollector by @nohwnd in Run Microsoft.Testing.Platform test apps under vstest.console and datacollector microsoft/vstest#16201Full Changelog: microsoft/vstest@v18.9.0...v18.10.0
18.9.0
What's Changed
New Contributors
Full Changelog: microsoft/vstest@v18.8.0...v18.9.0
18.8.1
What's Changed
Full Changelog: microsoft/vstest@v18.8.0...v18.8.1
18.8.0
What's Changed
Full Changelog: microsoft/vstest@v18.7.0...v18.8.0
18.7.0
What's Changed
New Contributors
Full Changelog: microsoft/vstest@v18.6.0...v18.7.0
18.6.0
What's Changed
Changes to tests and infra
... (truncated)
18.5.1
What's Changed
Full Changelog: microsoft/vstest@v18.5.0...v18.5.1
18.5.0
What's Changed
Full Changelog: microsoft/vstest@v18.4.0...v18.5.0
18.4.0
What's Changed
Add LoongArch64 support by @stdmnpkg in Add LoongArch64 support microsoft/vstest#15359
Refactor Condition evaluation by @Youssef1313 in Refactor Condition evaluation microsoft/vstest#15357
Adding info on extensions points part 1 by @nohwnd in Adding info on extensions points part 1 microsoft/vstest#15360
Add option to ask for uploading code QL before the standard window ends by @nohwnd in Add option to ask for uploading code QL before the standard window ends microsoft/vstest#15373
Update runtime versions by @nohwnd in Update runtime versions microsoft/vstest#15372
Fix .NET 10 regression for traits by @Youssef1313 in Fix .NET 10 regression for traits microsoft/vstest#15370
Update target frameworks to net10.0 and net11.0 by @dotnet-maestro[bot] in Update target frameworks to net10.0 and net11.0 microsoft/vstest#15349
Fix names in pipeline matrix so we don't have to align them by @nohwnd in Fix names in pipeline matrix so we don't have to align them microsoft/vstest#15365
Update SECURITY.md by @Youssef1313 in Update SECURITY.md microsoft/vstest#15342
New Contributors
Full Changelog: microsoft/vstest@v18.3.0...v18.4.0
18.3.0
What's Changed
Internal fixes and updates
New Contributors
18.0.1
What's Changed
Fixing an issue with loading covrun64.dll on systems that have .NET 10 SDK installed: https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/10.0/code-coverage-dynamic-native-instrumentation
Internal changes
Full Changelog: microsoft/vstest@v18.0.0...v18.0.1
18.0.0
What's Changed
Internal fixes and updates
... (truncated)
17.14.1
What's Changed
Full Changelog: microsoft/vstest@v17.14.0...v17.14.1
17.14.0
What's Changed
.NET versions updated
This version of VS Test upgraded .NET to net8 and net9. All projects targeting net6.0 (or other end-of-life .NET target frameworks) should pin their version of Microsoft.NET.Test.SDK to 17.13.0, or update the projects to net8 or newer. We remain backwards compatible with previous versions of Microsoft.NET.Test.SDK. This change does NOT prevent you from:
It also has no impact on .NET Framework projects, where we continue targeting .NET Framework 4.6.2.
Changes
Internal version updates and fixes
New Contributors
... (truncated)
17.14.0-preview-25107-01
What's Changed
.NET versions updated
This version of VS Test upgraded .NET to net8 and net9. All projects targeting net6.0 (or other end-of-life .NET target frameworks) should pin their version of Microsoft.NET.Test.SDK to 17.13.0, or update the projects to net8 or newer. We remain backwards compatible with previous versions of Microsoft.NET.Test.SDK. This change does NOT prevent you from:
It also has no impact on .NET Framework projects, where we continue targeting .NET Framework 4.6.2.
Changes
Internal version updates and fixes
Will probably revert before release:
New Contributors
Full Changelog: microsoft/vstest@v17.13.0...v17.14.0-preview-25107-01
17.13.0
What's Changed
Add letter number among valid identifiers in class name by @nohwnd in Add letter number among valid identifiers in class name microsoft/vstest#13868
Fix formatting in Runner by @mthalman in Fix formatting in Runner microsoft/vstest#13871
Downgrade xunit skip warning to info by @nohwnd in Downgrade xunit skip warning to info microsoft/vstest#10381
Add msdia for arm64 into nuget by @nohwnd in Add msdia for arm64 into nuget microsoft/vstest#10382
Enable native debugging for vstest.console by @ocitrev in Enable native debugging for vstest.console microsoft/vstest#10401
Fix RFCs links by @Youssef1313 in Fix RFCs links microsoft/vstest#10424
Convert to auto property by @nohwnd in Convert to auto property microsoft/vstest#10365
Update Versions.props by @nohwnd in Update Versions.props microsoft/vstest#10378
Enable TSA by @jakubch1 in Enable TSA microsoft/vstest#10385
Arm64 dia by @nohwnd in Arm64 dia microsoft/vstest#10390
Update source-build team references by @MichaelSimons in Update source-build team references microsoft/vstest#10388
Exclude .signature.p7s from nupkg file count by @ellahathaway in Exclude .signature.p7s from nupkg file count microsoft/vstest#10418
Set NetCurrent so that it doesn't roll forward automatically by @ViktorHofer in Set NetCurrent so that it doesn't roll forward automatically microsoft/vstest#10622
New Contributors
Full Changelog: microsoft/vstest@v17.12.0...v17.13.0
Commits viewable in compare view.
Updated xunit from 2.9.2 to 2.9.3.
Release notes
Sourced from xunit's releases.
No release notes found for this version range.
Commits viewable in compare view.
Updated xunit.extensibility.core from 2.9.2 to 2.9.3.
Release notes
Sourced from xunit.extensibility.core's releases.
No release notes found for this version range.
Commits viewable in compare view.
Updated xunit.runner.visualstudio from 2.8.2 to 4.0.0.
Release notes
Sourced from xunit.runner.visualstudio's releases.
4.0.0
Release notes: https://xunit.net/releases/visualstudio/4.0.0
4.0.0-pre.5
Release notes: https://xunit.net/releases/visualstudio/4.0.0-pre.5
4.0.0-pre.4
Release notes: https://xunit.net/releases/visualstudio/4.0.0-pre.4
4.0.0-pre.3
Release notes: https://xunit.net/releases/visualstudio/4.0.0-pre.3
3.1.5
Release notes: https://xunit.net/releases/visualstudio/3.1.5
Commits viewable in compare view.
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore <dependency name> major versionwill close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)@dependabot ignore <dependency name> minor versionwill close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)@dependabot ignore <dependency name>will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)@dependabot unignore <dependency name>will remove all of the ignore conditions of the specified dependency@dependabot unignore <dependency name> <ignore condition>will remove the ignore condition of the specified dependency and ignore conditions