Contracts completion: web-contracts-tests (083) + server-side CreateRole with backend validation (079)#270
Merged
Conversation
…(Decision 3 build-out) Dedicated Fixie+Shouldly project referencing web-contracts ONLY, so contract serialization is checkable in the contract-first window (before any server exists — the BFF workflow the skill teaches). dev test discovers it via the tests/ glob automatically. 7 tests targeting shapes where serialization can actually diverge (per the resolved RFC Decision 3 prioritization; trivial POCO round-trips deliberately skipped and the skip documented): - ctor+Guard Responses: camelCase JSON binds to constructor parameters, and Guard invariants run DURING deserialization (Guid.Empty roleId throws rather than materializing an invalid Response) - GetRoles.Response: ListResponse<RoleDto> envelope (base get-only props via derived ctor) - GetRole.Query: source-generated [ApiRoute] route property round-trips - SharedProblemDetails: validation errors + traceId survive the Extensions extension-data catch-all in both directions Inference-removal candidate (standing directive): canonical JsonSerializerOptions now exists in three places by convention (web-spa program.cs, web-spa-integration-tests, this project) — hoist one declaration into foundation-contracts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…all roles route warts
Completes the RoleForm round-trip: POST api/Roles now has a real endpoint
(BaseEndpoint MVC shim, mirroring the web-server slice pattern) and handler
(deliberate in-memory stub, documented). Backend validation costs zero new
code: FluentValidationBehavior runs the contract's composed Validator
(shared RoleDetailsValidator + AuthApiRequestValidator) server-side.
Prerequisite fix: BaseEndpoint/BaseFastEndpoint constrained
TResponse : BaseResponse, which would force Responses back into the shape
RFC Decision 5 rejected -> loosened to class (Send uses no BaseResponse
member; both bases kept aligned).
Roles route warts fixed (unblocks 078 Edit/View):
- get-role: {RoleId:min(1)} -> {RoleId:guid}
- update-role: api/Role/{RoleId:int} -> api/Roles/{RoleId:guid}; removed
stray `Guid Guid` property
- delete-role: RPC-style api/DeleteRole -> api/Roles/{RoleId:guid}; removed
hand-declared `required int RoleId` (generator emits it from the route)
The 083 serialization tests caught the generated-property type change
(RoleId int -> Guid) exactly as the tests-first sequencing intended.
Integration tests (web-server, real host): 200 + non-empty RoleId on valid
command; 400 problem-details naming the property for empty Name and empty
UserId. Suite 11 -> 18 passed. dev build 0/0; contracts 7/7; analyzer 21/21;
sourcegen 14/14.
New task 085: collected analyzer/source-gen inference-removal candidates
(endpoint<->contract verb agreement, missing-endpoint detection, canonical
JsonSerializerOptions, generated mock-factory registry, Aspire resource
names, base-endpoint alignment).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Summary
The final two contracts-arc tasks, sequenced tests-first deliberately.
083 — web-contracts-tests (Decision 3 build-out)
Host-free Fixie+Shouldly project referencing web-contracts only, so serialization is checkable in the contract-first window (before any server exists). 7 tests targeting shapes where serialization can actually diverge:
GuardResponses bind camelCase JSON to constructor parameters — and Guard invariants run during deserialization (Guid.EmptyroleId throws rather than materializing an invalid Response)ListResponse<RoleDto>envelope; source-generated[ApiRoute]route property;SharedProblemDetailsextension-data losslessness (validationerrors+traceIdboth directions)dev testdiscovers the project via the tests/ glob automatically.079 — server-side CreateRole + backend validation
Completes the RoleForm round-trip that previously 405'd:
CreateRoleEndpoint(BaseEndpointMVC shim, per web-server slice convention) + handler (deliberate, documented in-memory stub)FluentValidationBehaviorruns the contract's composed Validator (sharedRoleDetailsValidator+AuthApiRequestValidator) server-side; integration tests prove both 400 rejection paths by property nameBaseEndpoint/BaseFastEndpointTResponse : BaseResponseconstraint loosened toclass(Decision 5 made ctor+Guard Responses first-class){RoleId:guid}everywhere, plural resource routes, removed strayGuid Guidproperty and RPC-styleapi/DeleteRole+ hand-declaredint RoleId. The 083 tests caught the generated-property type change — tests-first sequencing working as intended.Verification
dev build0/0 · web-server integration 18 passed (was 11) · contracts round-trips 7/7 · analyzer 21/21 · sourcegen 14/14Board
083, 079 done. New task 085: collected analyzer/source-gen inference-removal candidates (endpoint↔contract verb agreement, missing-endpoint detection, canonical
JsonSerializerOptions, generated mock-factory registry, Aspire resource names).🤖 Generated with Claude Code