Skip to content

Harden Spring-parity modules: real DI, real scaffolds, integration tests#24

Merged
ancongui merged 2 commits intomainfrom
spring-parity-hardening
May 8, 2026
Merged

Harden Spring-parity modules: real DI, real scaffolds, integration tests#24
ancongui merged 2 commits intomainfrom
spring-parity-hardening

Conversation

@ancongui
Copy link
Copy Markdown
Contributor

@ancongui ancongui commented May 7, 2026

Summary

  • Round 2 on the 16 new modules from Add 16 modules to reach Spring/pyfly parity #23. Every issue surfaced while writing follow-up integration tests is fixed in the source, not the tests.
  • 9 new integration tests bring the suite to 399 total, all green.

What changed

Real DI / no anti-patterns

  • FireflySecurity no longer calls services.BuildServiceProvider() inside AddJwtBearer. JwtBearerOptions is now configured through an IConfigureNamedOptions<JwtBearerOptions> so the bearer scheme resolves through the real container.
  • FireflyActuator.BeansEndpoint resolves via IReadOnlyList<BeanRegistration> instead of the implicit List<> (which the DI graph couldn't satisfy).
  • FireflyActuator.MappingsEndpoint takes EndpointDataSource as optional — the actuator loads cleanly in console hosts / hosted services / test rigs that don't have ASP.NET Core routing wired.
  • FireflySession middleware was writing Set-Cookie after the inner pipeline flushed headers; cookie issuance moved before _next, persistence runs in Response.OnCompleted.

Real scaffolds

  • Cli.HandlerScaffold now emits real ICommandHandler<TCommand, TResult> / IQueryHandler<TQuery, TResult> with matching record types. The previous output didn't compile.
  • Cli.ServiceScaffold now writes a valid Visual Studio Format 12.00 .sln plus per-module csproj files. The previous output was a placeholder comment.
  • Cli.SagaScaffold includes namespace + cleaner method bodies. Roslyn parses every scaffold in CliScaffoldTests to keep it that way.

Honest docs

  • Agentic README rewritten to match what ships: the core module has no provider SDK reference; the user implements IChatModel for their preferred adapter. The list of adapter packages that don't yet exist is gone.
  • Messaging.SendToAttribute removed (advertised but never wired). MessageListenerAttribute kept with a docstring noting it's a marker for future framework-side scanners.
  • XML docs swept across FireflyResilienceOptions (every strategy bag), FireflySecurityOptions, AgentInvocation / AgentInvocationResult, and Message<T>. Every public type has at least a one-line summary.

Tighter contracts

  • Aop.AspectRegistry now scans BindingFlags.Public only — advice on private methods would never be reachable from the framework, and locking it down makes the contract explicit.

New tests (399 total, all green)

Test What it covers
SecurityMiddlewareTests (2) TestServer-driven; asserts holder bind + X-Tenant-Id header propagation
SessionMiddlewareTests (1) cookie issued, value persisted across requests
ActuatorRouteTests (1) /actuator index + /actuator/{id} 200/404 routing
CliScaffoldTests (4) every scaffold parses with Roslyn; .sln is a real Format-12 solution
StarterApplicationWiringTests (1) all 14 modules in AddFireflyApplication actually resolve out of DI

Test plan

  • dotnet build FireflyFramework.sln — clean
  • dotnet test FireflyFramework.sln — 399 / 399 pass, 0 skipped
  • Every issue caught was fixed in the source, not papered over in the test

Andres Contreras added 2 commits May 7, 2026 23:40
Round 2 on the 16 new modules. Every issue found while writing the
follow-up integration tests is fixed in the source, not the tests:

- Cli scaffolds were producing uncompilable C#. HandlerScaffold now
  emits real ICommandHandler<T,TResult> / IQueryHandler<T,TResult> with
  matching record types, and ServiceScaffold writes a valid Visual
  Studio Format 12.00 .sln + per-module csproj instead of a placeholder
  comment. CliScaffoldTests parses every output through Roslyn to keep
  it that way.

- FireflySecurity dropped the BuildServiceProvider() anti-pattern
  inside AddJwtBearer. JwtBearerOptions is now configured through
  IConfigureNamedOptions, so the bearer scheme resolves through the
  real container without spinning up a duplicate root.

- FireflySession middleware was writing Set-Cookie *after* the inner
  pipeline flushed headers, which threw on any request that wrote a
  body. Cookie issuance moved before _next; persistence runs in
  Response.OnCompleted. New SessionMiddlewareTests exercises the full
  cookie round-trip via TestServer.

- Actuator BeansEndpoint now resolves via IReadOnlyList<BeanRegistration>
  so the snapshot is callable from DI; MappingsEndpoint takes
  EndpointDataSource as optional so the actuator loads cleanly outside
  ASP.NET Core hosts (test rigs, console hosts, hosted services). New
  ActuatorRouteTests covers the real /actuator/{id} routing.

- Aop AspectRegistry now scans BindingFlags.Public only — advice on
  private methods would never be reachable from the framework anyway,
  and locking it to public makes the contract explicit.

- New StarterApplicationWiringTests asserts that every module advertised
  by AddFireflyApplication actually resolves out of the container, so
  future starter-bundle drift breaks the build instead of consumers.

- Agentic README rewritten to match what actually ships: the core
  module deliberately has no provider SDK reference; consumers
  implement IChatModel against their preferred adapter. Removed the
  list of adapter packages that don't yet exist.

- Messaging.SendToAttribute removed (it was advertised but never wired);
  MessageListenerAttribute kept with a docstring noting it's a
  placeholder marker for future framework-side scanners.

- XML docs swept across FireflyResilienceOptions (every strategy bag),
  FireflySecurityOptions, AgentInvocation/AgentInvocationResult, and
  Message<T>. Every public type has at least a one-line summary.

Tests: 9 new integration tests bringing the suite to 399 total, all
green:
- SecurityMiddlewareTests (2): TestServer-driven, asserts holder bind
  + tenant header propagation
- SessionMiddlewareTests (1): cookie issued, value persisted across
  requests
- ActuatorRouteTests (1): /actuator index + /actuator/{id} 200/404
  routing
- CliScaffoldTests (4): every scaffold parses with Roslyn + sln file
  is real
- StarterApplicationWiringTests (1): all 14 modules in
  AddFireflyApplication resolve
The original test used a 1ms eviction window and depended on real wall
clock progress between Register and EvictStale. Locally that worked
(host clock granularity is ~16ms); GitHub Actions runners can resolve
DateTimeOffset.UtcNow with sub-ms precision and the eviction window
slipped before the comparison ran, dropping the instance.

Replaced with two deterministic cases:
- EvictStale_keeps_fresh_instances: two fresh registrations + 5min
  window — both stay.
- EvictStale_drops_instances_past_timeout: register, await 50ms,
  evict with 1ms window — instance is gone.

Adjusted Register_then_heartbeat assertion to BeOnOrAfter (heartbeat
in the same millisecond as registration is a legitimate outcome on
fast clocks).
@ancongui ancongui merged commit a412783 into main May 8, 2026
2 checks passed
@ancongui ancongui deleted the spring-parity-hardening branch May 8, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant