Skip to content

Bump the testing group with 4 updates - #6

Closed
dependabot[bot] wants to merge 50 commits into
masterfrom
dependabot/nuget/src/MagicCSharp.Testing.Database/testing-776542c19d
Closed

dependabot[bot] wants to merge 50 commits into
masterfrom
dependabot/nuget/src/MagicCSharp.Testing.Database/testing-776542c19d

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Sep 23, 2026

Copy link
Copy Markdown

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

Full 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

18.5.1

What's Changed

Full Changelog: microsoft/vstest@v18.5.0...v18.5.1

18.5.0

⚠️ Unlisted on Nuget, because of #​15718

What's Changed

Full Changelog: microsoft/vstest@v18.4.0...v18.5.0

18.4.0

What's Changed

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

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:

  • Updating to the latest VS, and running tests from net6.0 test projects.
  • Updating to the latest .NET SDK, and running tests from net6.0 test projects.

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

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:

  • Updating to the latest VS, and running tests from net6.0 test projects.
  • Updating to the latest .NET SDK, and running tests from net6.0 test projects.

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

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 rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore <dependency name> major version will 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 version will 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

KRSogaard and others added 30 commits November 1, 2025 00:31
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.
KRSogaard and others added 20 commits September 6, 2026 22:15
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>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Sep 23, 2026
@KRSogaard KRSogaard closed this Sep 23, 2026
@dependabot @github

dependabot Bot commented on behalf of github Sep 23, 2026

Copy link
Copy Markdown
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
dependabot Bot deleted the dependabot/nuget/src/MagicCSharp.Testing.Database/testing-776542c19d branch September 23, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants