Skip to content

V9.0.0 - Modernize CI workflow, update project structure, and enhance API coverage#224

Merged
kevbite merged 39 commits into
masterfrom
prerelease
Jul 6, 2026
Merged

V9.0.0 - Modernize CI workflow, update project structure, and enhance API coverage#224
kevbite merged 39 commits into
masterfrom
prerelease

Conversation

@kevbite

@kevbite kevbite commented Jul 6, 2026

Copy link
Copy Markdown
Owner

This pull request introduces several infrastructure and documentation improvements to the repository, focusing on modernizing the build system, centralizing dependency management, and enhancing CI/CD automation.

Infrastructure and CI/CD Enhancements:

  • Modernized the GitHub Actions CI workflow (.github/workflows/continuous-integration-workflow.yml):

    • Updated action versions (e.g., actions/checkout@v4, actions/setup-dotnet@v4) and enabled recursive submodule checkout.
    • Improved versioning logic for releases, supporting both master and prerelease branches, and automated release notes generation with NuGet package links.
    • Added validation to ensure NuGet packages include README.md and correct nuspec metadata.
  • Added a scheduled workflow to automatically bump the api-enumerations submodule monthly and open a pull request if updates are detected. (.github/workflows/bump-api-enumerations.yml)

  • Registered the api-enumerations submodule in .gitmodules to enable its use in the repository.

Summary of Most Important Changes:

CI/CD and Build System Modernization

  • Updated the main CI workflow to use the latest GitHub Actions, improved versioning for releases, added package validation, and automated release notes with NuGet links.
  • Added a scheduled workflow to automatically update the api-enumerations submodule and create pull requests for changes.
  • Registered the api-enumerations submodule in .gitmodules for future use.

kevbite and others added 30 commits July 1, 2026 17:07
…t stack

- Add global.json (SDK 10.0.100), Directory.Packages.props (CPM), enable
  nullable/deterministic build/doc generation in Directory.Build.props.
- Retarget CompaniesHouse and CompaniesHouse.Extensions.Microsoft.
  DependencyInjection to net8.0;net9.0;net10.0; drop netstandard/net45 and
  reference-assembly packages.
- Retarget all test projects + sample to net10.0.
- Convert CompaniesHouse.sln to CompaniesHouse.slnx; delete the old .sln.
- Update Dockerfile (SDK 10.0, slnx) and CI workflow (checkout@v4 with
  recursive submodules, setup-dotnet@v4 via global.json, softprops/action-gh-
  release).
- Replace NUnit + FluentAssertions with xUnit + Shouldly across all four test
  projects (FluentAssertions license changed to a paid tier from v8). Add a
  dependency-free EquivalencyAssertionExtensions helper in CompaniesHouse.Tests
  to replace the old FluentAssertions IEquivalencyStep/MapProviders machinery
  for bridging test-fixture wire strings against string-backed/enum response
  properties.
- Add .plans/ (outstanding + completed) and AGENTS.md; move plan 00 to
  completed with full notes on the test-stack migration.
- 594/597 tests pass; 3 pre-existing IntegrationTests failures are due to
  live Companies House API behaviour drift (200 + empty results instead of
  404 for malformed company numbers), unrelated to this change.

Co-authored-by: Copilot <[email protected]>
…ine, remove Newtonsoft)

- Redesign CompaniesHouseClientResponse<T> with status code, reason phrase, retry-after, headers, IsSuccess
- Add shared send/deserialize pipeline (ToCompaniesHouseClientResponseAsync); throw CompaniesHouseApiException only for 5xx
- Central STJ JsonSerializerOptions with snake_case naming, EnumMember-aware enum converter, enum-array-or-single converter, polymorphic SearchItem converter
- Remove Newtonsoft.Json entirely from src/CompaniesHouse (all response models, converters, csproj)
- Update default base URI to api.company-information.service.gov.uk
- Round out integration test coverage for insolvency info, registered office address, officer-by-appointment (valid + invalid real API cases)
…ed multi-client support

- CompaniesHouseClientOptions/CompaniesHouseClientDocumentOptions now carry
  DataAnnotations validation (Required ApiKey/BaseUri).
- Registration rebuilt around AddOptions<>().ValidateDataAnnotations().ValidateOnStart()
  so a missing/invalid API key fails fast at startup instead of on first request.
- Added IConfiguration/IConfigurationSection binding overloads (default section
  CompaniesHouse / CompaniesHouseDocument).
- Added named/keyed overloads (AddCompaniesHouseClient(name, ...)) so multiple
  distinct clients can coexist, resolved via GetRequiredKeyedService.
- Exposed an optional Action<IHttpClientBuilder> hook on the flexible overloads
  for resilience handlers (Polly etc).
- Bumped Microsoft.Extensions.* package versions and added Options/Configuration
  packages to Directory.Packages.props.
- Expanded DI tests to cover config binding, keyed resolution and validation failure.
- CompanyStatus is now a readonly record struct wrapping the raw wire string,
  with a trivial [JsonConverter] applied directly on the type instead of the
  global enum converter, so unrecognised status values from Companies House
  round-trip instead of throwing.
- No implicit string conversions; no None static member - default(CompanyStatus)
  / HasValue == false represents an absent value, per the plan's answered
  open questions.
- IsKnown and Description (sourced from api-enumerations constants.yml) added.
- CompanyProfile.CompanyStatus and search Company.CompanyStatus already used
  this type name, so no call-site changes were needed beyond one test's
  CompanyStatus.None -> default.
- Added CompanyStatusTests covering known-value round-trip, unknown-value
  no-throw + raw preservation, null handling, equality, switch matching, and
  Description.
… workflow (plan 05)

- Pin external/api-enumerations to upstream master.
- enumerations/extra/ documents the submodule-first/extras-override merge
  rule for plan 04's generator, with a real example (closed-on company_status
  description missing upstream).
- Dockerfile build stage now COPYs external/ and enumerations/ so the
  generator sees them inside the container build too.
- New .github/workflows/bump-api-enumerations.yml fast-forwards the submodule
  monthly and opens a PR for review.
- New CompaniesHouse.SourceGenerator project (netstandard2.0, build-time-only
  analyzer referenced from CompaniesHouse.csproj, never shipped to consumers).
- Hand-rolled MinimalYamlParser for the api-enumerations YAML subset, with
  dedicated unit tests.
- MemberNameGenerator (wire-value -> PascalCase), EnumMapParser (enum-map.txt
  config format), and EnumDataMerger (submodule-first, extras-override merge).
- ValueTypeEmitter reproduces the plan 03 CompanyStatus shape exactly
  (readonly record struct + JsonConverter, KnownValues, Descriptions, no
  member for an empty wire value).
- CompanyStatus is now fully generator-produced from enum-map.txt; the
  hand-authored CompanyStatus.cs/CompanyStatusJsonConverter.cs are removed.
  Generated output includes ClosedOn from the enumerations/extra overlay,
  proving the submodule+extras+generator round trip end-to-end.
- New CompaniesHouse.SourceGenerator.Tests project: 28 tests covering the
  parser, member-name conversion, merge rules, enum-map parsing, and
  end-to-end generator runs via CSharpGeneratorDriver with in-memory
  AdditionalTexts.
- Full solution build (net8/9/10) 0 errors; CompaniesHouse.Tests 314/314;
  ScenarioTests 2/2; format clean on touched files.
Fix the company search restrictions query bug and complete the missing search surface with alphabetical, dissolved, and advanced company search endpoints. This adds dedicated request models, URI builders, typed response models, CompaniesHouseClient entry points, and DI registrations while keeping the existing search-client/factory architecture in place.

Migrate CompanyType onto the enum source generator and add generated CompanySubtype support for advanced-search filters and responses. Expand unit, DI, source-generator, and integration coverage for the new builders, generated value types, and search deserialization paths.
Migrate CompanyStatusDetail and Jurisdiction from hand-written wire enums to the generated string-backed value-type pipeline, and add generated ForeignAccountType and TermsOfAccountPublication support from api-enumerations. Wire the new generated types into company profile deserialization and update the existing test enum mappings to the generated member names.

Extend the company profile response models to cover the confirmed live API gaps: subtype, has_super_secure_pscs, external_registration_number, foreign_company_details, and the missing exemptions and uk_establishments links. Add focused unit, scenario, client, and integration coverage for the new fields and for the established 404 response semantics on GetCompanyProfileAsync.
The plan 07 commit (f75f7ab) marked several CompanyProfile/CompanyProfileLinks
properties as C# 'required', but real Companies House API responses
frequently omit them (e.g. annual_return, confirmation_statement,
previous_company_names, sic_codes, branch_company_details, and most of the
individual links - verified against live payloads for a standard company, an
overseas company, a CIC, and a liquidation/dissolved company, none of which
carry every link or every optional section). This broke STJ deserialization
for the vast majority of real profiles with a JsonException about missing
required properties. Reverted those properties to plain (non-required)
members matching their pre-plan-07 declarations, and made the remaining
CompanyProfileLinks members nullable since none of the observed payloads
populate every link.

Also fixed a test-only build break: the CompaniesHouseCompanyProfileClientTests
private fields were marked 'required' with a private setter, which is
invalid C# (a required member's setter cannot be less visible than the
containing type) and failed the build with CS9032.
Cross-checked all 7 search endpoints against the live docs and real API responses. Fixed missing page_number on search/all, search/officers and search/disqualified-officers envelopes; added company search/search-all address_snippet and external_registration_number fields; changed company description_identifier to string[] and added matches.snippet/address_snippet handling; marked advanced-search and dissolved-search item fields optional where live payloads omit company_subtype, sic_codes, ordered_alpha_key_with_id, matched_previous_company_name or registered_office_address; and added live-captured scenario tests plus richer real-API integration coverage for restrictions, advanced filters, alphabetical paging, previous-name dissolved search and foreign company/officer payloads.
Migrate officers wire enums to generated string-backed value types by adding OfficerRole and IdentificationType to enum-map.txt and removing the hand-written OfficerRole enum. Extend the officers models with the confirmed live API fields for etags, person_number, is_pre_1992_appointment, identity_verification_details, list-level kind/links/items_per_page/inactive_count, officer links.self, and the restored OfficerId convenience derived from the appointments link. Also add the historic appointed_before field seen on real Tesco officer payloads.

Update GetOfficersAsync and OfficersUriBuilder to default to a 35-item page size and support the documented register_type, register_view and order_by query parameters while omitting unsupplied optional values. Add unit, scenario, value-type, URI builder and live integration coverage for the confirmed Tesco and Informa officer payloads, including real appointment deserialization and corporate identification handling.
…iour

The Companies House officers list endpoint returns 200 with an empty
item list for a malformed/non-existent company number rather than a
404, so CompaniesHouseClientResponse.Data is populated (not null).
Updated the pre-existing test to assert an empty result set instead
of a null Data property, verified against the real live API.
…n 10)

Adds IntegrationFactAttribute/IntegrationTheoryAttribute which skip cleanly
(rather than fail) when COMPANIES_HOUSE_API_KEY is not set, applied to the
search/company-profile/officer integration test suites (charges/filing-
history/PSC/documents/registered-office/appointments left for the plan 09
implementation pass to avoid clashing with concurrent work there). Verified
by running with and without the env var: 35/35 pass with a key, 30/30 skip
cleanly without one.

Also adds a full-source snapshot test (ValueTypeEmitterSnapshotTests) that
asserts the entire generated value-type + JSON converter source text for a
representative enum group, guarding against accidental whitespace/shape
regressions in ValueTypeEmitter beyond the existing spot-check assertions.
Rebuild the registered office address, filing history, officer appointments,
PSC list, charges, insolvency, and document endpoints against the live
Companies House APIs. This updates the prerelease models to match observed
payloads, removes legacy wire-enum assumptions that break on new values, and
moves the affected contracts onto the generated string-backed value-type
pattern where the API exposes stable enum-like strings. The work also promotes
registered office address to a public sub-client, adds dedicated URI builders
for appointments and insolvency, and expands document metadata handling for
real filenames, timestamps, and large content lengths.

Add the supporting generator inputs and serializer changes needed for the new
value types, including extra YAML groups for filing, charge, insolvency, and
PSC values plus converter support for single-string-or-array payloads backed by
record-struct value types. Update the affected tests across unit, scenario,
DI, source-generator, and integration suites with live-schema assertions for
09a-09g, and serialize the integration project's real API tests to avoid flaky
parallel execution against Companies House.
…plan 10)

Applies IntegrationFactAttribute/IntegrationTheoryAttribute (added in a
previous commit) to the charges, filing history, insolvency, PSC,
document, registered-office-address, and officer-appointments-list
integration test suites, so the whole CompaniesHouse.IntegrationTests
project now skips cleanly rather than failing when
COMPANIES_HOUSE_API_KEY is not set.

Verified: 73/73 pass with the key present (run three times, one
transient run showed unrelated real-API rate-limiting/500s that cleared
immediately on retry), and the full suite (58 non-parameterised Skip
results - xUnit collapses Theory data rows to a single Skip entry) skips
cleanly with the env var unset.
…num types

Generated readonly record struct enum types previously duplicated each
wire value as a string literal twice: once for the static member and
again for the KnownValues hash set / Descriptions dictionary key.
KnownValues and Descriptions now reference the static member's .Value
property instead, so the raw wire string is defined once.
…aniesHouseResponse<T>

- Add CompaniesHouseResponse<T> abstract class with Success, NotFound, RateLimited,
  Unauthorized, ClientError, and ServerError sealed subtypes
- Delete CompaniesHouseClientResponse<T> flat class and CompaniesHouseApiException
- Rewrite HttpResponseMessageExtensions to map HTTP status codes to subtypes;
  remove EnsureNotServerErrorAsync (5xx is now a ServerError subtype, not an exception)
- Update all interfaces and client implementations to use the new type
- Rewrite CompaniesHouseDocumentDownloadClient to use the discriminated union inline
- Update unit tests: replace IsSuccess/Data-null assertions with type checks;
  rewrite HttpResponseMessageExtensionsTests for the new contract
- Update integration tests: replace CompaniesHouseApiException catch with
  pattern match on ServerError subtype
- Update SampleProject to pattern-match on Success for company profile display
- README: switch the full-branching example to a switch expression
- MIGRATION.md: replace stale CompaniesHouseClientResponse<T> section with
  discriminated union before/after (including CompaniesHouseApiException removal)
- SampleProject: proper non-success handling in all Display methods, switch
  expression for company profile, alias to resolve Officer ambiguity, drop
  redundant System.* usings
All 'Invalid' tests were asserting .Data.ShouldBeNull() on 404 responses,
which now throws InvalidOperationException. Updated to assert NotFound subtype
using ShouldBeOfType<CompaniesHouseResponse<T>.NotFound>() instead.
@kevbite kevbite self-assigned this Jul 6, 2026
Copilot AI review requested due to automatic review settings July 6, 2026 22:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@kevbite kevbite merged commit f80ca79 into master Jul 6, 2026
2 of 4 checks passed
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.

2 participants