Invariant scaffolding for the btcmap import-RPC integration. Both
changes are additive (Phone is optional, BtcMap constant unreferenced
until impl). Build clean.
Pending coordination Qs from rollforsats (lat/lon source, category
mapping, external_id namespace) before service + controller wiring.
Addresses post-#224 review feedback from @rollforsats + CodeRabbit:
- IHttpClientFactory + named HttpClientNames.BtcMapsDirectory client
replaces per-request `new HttpClient()`. 15s per-call timeout caps the
~5-7 GitHub round-trips at a bounded worst case instead of the default
100s x N. Bearer token stays per-request (the BTCMAPS token is distinct
from the global PluginBuilder GitHub token; must not leak into the
singleton handler).
- Markdown injection guard on the PR body. User fields (Name, Type,
SubType, Country, Twitter, GitHub) are wrapped in inline code spans
with backtick-escape so a doctored merchant name can't render as a
clickable link in the maintainer-facing PR description. Description
goes inside a fenced code block. URL is rendered as <bare-url> autolink
so the maintainer always sees the actual destination.
- Idempotent branch name: SHA-1-derived suffix from the normalized URL
replaces the random GUID. Two concurrent same-URL submissions now
collide on `git/refs` create instead of racing through preflight and
opening duplicate PRs. The 422 "Reference already exists" surface is
caught and mapped to the open-PR lookup or `branch-exists-no-open-pr`.
- NormalizeUrl lowercases scheme + host only and preserves path + query
case verbatim. Lowercasing the whole URL falsely de-duplicates
case-sensitive paths.
- Country code validation moves to an actual ISO 3166-1 alpha-2 set
built from CultureInfo at startup. Replaces the
`length==2 && IsUpper` shape that accepted reserved/unassigned codes
like ZZ.
- Missing BTCMAPS:DirectoryGithubToken throws
DirectoryTokenMissingException at the service layer; controller maps
it to 503 with `directory-not-configured`. Previously surfaced as a
200 OK with `Skipped` which a client could misread as "accepted".
5 new tests:
- Validate_RejectsNonAssignedTwoLetterCountry (ZZ)
- NormalizeUrl_PreservesPathCase
- NormalizeUrl_PreservesQueryCase
- BuildBranchName_DeterministicForSameUrl
- BuildBranchName_DiffersForDifferentUrls
25/25 BtcMapsServiceTests pass on Release build.