From 477577521bf97f9c7a8d638410504ef4067a0da4 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 17:07:37 +0100 Subject: [PATCH 01/38] Update CI workflow to conditionally set VERSION and PUBLISH_PACKAGE for different branches --- .github/workflows/continuous-integration-workflow.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 245954c..8e61e49 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -6,7 +6,8 @@ jobs: build: runs-on: ubuntu-latest env: - VERSION: 8.0.${{ github.run_number }} + VERSION: ${{ github.ref == 'refs/heads/master' && format('8.0.{0}', github.run_number) || format('9.0.0-pre{0}', github.run_number) }} + PUBLISH_PACKAGE: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/prerelease' }} DOCKER_BUILDKIT: 1 BUILDKIT_PROGRESS: plain steps: @@ -22,12 +23,12 @@ jobs: with: trx_files: TestResults/**/*.trx - name: NuGet.Org push - if: github.ref == 'refs/heads/master' + if: ${{ env.PUBLISH_PACKAGE }} run: | dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }} - name: Create Release id: create_release - if: github.ref == 'refs/heads/master' + if: ${{ env.PUBLISH_PACKAGE }} uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From ea201b88376a8abdd6d9659a0714fd306af1eb8e Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 18:22:18 +0100 Subject: [PATCH 02/38] Plan 00: foundation solution/build modernization + xUnit/Shouldly test 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 <223556219+Copilot@users.noreply.github.com> --- .../continuous-integration-workflow.yml | 12 +- .plans/README.md | 44 ++++ .plans/completed/.gitkeep | 0 .../00-foundation-solution-and-build.md | 158 ++++++++++++++ .../01-core-client-architecture.md | 120 +++++++++++ .../outstanding/02-di-extensions-ioptions.md | 94 +++++++++ .../03-string-backed-value-types.md | 116 +++++++++++ .../outstanding/04-enum-source-generator.md | 124 +++++++++++ .../05-api-enumerations-submodule.md | 85 ++++++++ .plans/outstanding/06-endpoint-search.md | 99 +++++++++ .../07-endpoint-company-profile.md | 62 ++++++ .plans/outstanding/08-endpoint-officers.md | 71 +++++++ .../09-endpoint-catalogue-remaining.md | 93 +++++++++ .plans/outstanding/10-testing-strategy.md | 89 ++++++++ .../outstanding/11-docs-samples-migration.md | 66 ++++++ .../99-recurring-issues-backlog.md | 91 ++++++++ AGENTS.md | 141 +++++++++++++ CompaniesHouse.sln | 61 ------ CompaniesHouse.slnx | 9 + Directory.Build.props | 20 +- Directory.Packages.props | 32 +++ Dockerfile | 5 +- global.json | 7 + samples/SampleProject/SampleProject.csproj | 2 +- ...sions.Microsoft.DependencyInjection.csproj | 9 +- src/CompaniesHouse/CompaniesHouse.csproj | 17 +- swagger.json | 195 ++++++++++++++++++ ...Microsoft.DependencyInjection.Tests.csproj | 11 +- .../ServiceCollectionExtensionsTests.cs | 37 ++-- .../CompaniesHouse.IntegrationTests.csproj | 15 +- tests/CompaniesHouse.IntegrationTests/Keys.cs | 2 +- .../AppointmentsTests/AppointmentsTestBase.cs | 19 +- .../AppointmentsTests/OfficersTestsValid.cs | 17 +- .../ChargesTests/ChargeByIdTestsInValid.cs | 11 +- .../ChargesTests/ChargeByIdTestsValid.cs | 11 +- .../ChargesTests/ChargesListTestsInValid.cs | 9 +- .../ChargesTests/ChargesListTestsValid.cs | 39 ++-- .../Tests/ChargesTests/ChargesTestBase.cs | 16 +- .../CompanyFilingHistoryTestBase.cs | 18 +- .../CompanyFilingHistoryTestsInvalid.cs | 17 +- .../CompanyFilingHistoryTestsValid.cs | 54 ++--- ...ilingHistoryByTransactionIdTestsInvalid.cs | 17 +- .../FilingHistoryByTransactionIdTestsValid.cs | 17 +- .../CompanyInsolvencyInformationTests.cs | 33 ++- .../CompanyProfileTestsBase.cs | 19 +- .../CompanyProfileTestsInvalid.cs | 17 +- .../CompanyProfileTestsValid.cs | 17 +- .../DocumentTests/DocumentDownloadTests.cs | 28 +-- .../DocumentMetadataTestsInvalid.cs | 17 +- .../DocumentMetadataTestsValid.cs | 20 +- .../Tests/DocumentTests/DocumentTestBase.cs | 17 +- .../OfficerByAppointmentTestsValid.cs | 15 +- .../Tests/OfficerTests/OfficersTestBase.cs | 19 +- .../OfficerTests/OfficersTestsInvalid.cs | 17 +- .../Tests/OfficerTests/OfficersTestsValid.cs | 17 +- .../PersonsWithSignificantControlTestBase.cs | 19 +- ...rsonsWithSignificantControlTestsInValid.cs | 19 +- ...PersonsWithSignificantControlTestsValid.cs | 18 +- .../RegisteredOfficeAddressTestBase.cs | 16 +- .../RegisteredOfficeAddressesTestsValid.cs | 9 +- .../Tests/SearchingTests/AllSearchTests.cs | 40 ++-- .../SearchingTests/CompanySearchTests.cs | 42 ++-- .../DisqualifiedOfficersSearchTests.cs | 29 +-- .../SearchingTests/OfficersSearchTests.cs | 30 +-- .../CompaniesHouse.ScenarioTests.csproj | 13 +- tests/CompaniesHouse.ScenarioTests/Keys.cs | 2 +- ...dFetchCorrespondingCompanyScenarioTests.cs | 21 +- .../UsingMicrosoftServiceContainerTests.cs | 10 +- .../CompaniesHouse.Tests.csproj | 26 +-- .../CompaniesHouseChargesClientTests.cs | 24 ++- ...iesHouseCompanyFilingHistoryClientTests.cs | 20 +- ...CompaniesHouseCompanyProfileClientTests.cs | 20 +- .../CompaniesHouseDocumentClientTests.cs | 20 +- ...mpaniesHouseDocumentMetadataClientTests.cs | 16 +- ...niesHouseOfficersAppointmentClientTests.cs | 16 +- ...ompaniesHouseCompanyOfficersClientTests.cs | 14 +- ...HousePersonsWithSignificantControlTests.cs | 14 +- ...paniesHouseRegisteredOfficeAddressTests.cs | 17 +- ...sHouseSearchClientTestsForCompanySearch.cs | 118 +++++------ ...estsForCompanySearchWithTooManyRequests.cs | 19 +- ...sHouseSearchClientTestsForOfficerSearch.cs | 21 +- .../ComparingArrayEnumWith.cs | 43 ---- .../CompaniesHouse.Tests/ComparingEnumWith.cs | 42 ---- ...CompaniesHouseAuthorizationHandlerTests.cs | 20 +- .../DescriptionProviderTests.cs | 25 ++- .../EquivalencyAssertionExtensions.cs | 155 ++++++++++++++ .../HttpResponseMessageExtensionsTests.cs | 33 +-- tests/CompaniesHouse.Tests/Initializer.cs | 36 ---- ...tringArrayOrFieldEnumConverterTestsBase.cs | 10 +- ...ieldEnumConverterTestsForMultipleValues.cs | 8 +- ...OrFieldEnumConverterTestsForSingleValue.cs | 16 +- ...alDateJsonConverterTestsForUnknownValue.cs | 16 +- .../CompanyFilingHistoryUriBuilderTests.cs | 23 +-- .../CompanyProfileUriBuilderTests.cs | 19 +- .../RegisteredOfficeUriBuilderTests.cs | 20 +- .../OfficersAppointmentUriBuilderTests.cs | 20 +- .../OfficersUriBuilderTests.cs | 23 +-- ...rsonsWithSignificantControlBuilderTests.cs | 23 +-- .../CompanySearchUriBuilderTestsBase+Thens.cs | 14 +- .../SearchUriBuilderTestsBase.cs | 38 ++-- .../SearchUriBuilderTestsBaseForQuery.cs | 9 +- .../SearchUriBuilderTestsForItemsPerPage.cs | 9 +- .../SearchUriBuilderTestsForStartIndex.cs | 9 +- 103 files changed, 2578 insertions(+), 1014 deletions(-) create mode 100644 .plans/README.md create mode 100644 .plans/completed/.gitkeep create mode 100644 .plans/completed/00-foundation-solution-and-build.md create mode 100644 .plans/outstanding/01-core-client-architecture.md create mode 100644 .plans/outstanding/02-di-extensions-ioptions.md create mode 100644 .plans/outstanding/03-string-backed-value-types.md create mode 100644 .plans/outstanding/04-enum-source-generator.md create mode 100644 .plans/outstanding/05-api-enumerations-submodule.md create mode 100644 .plans/outstanding/06-endpoint-search.md create mode 100644 .plans/outstanding/07-endpoint-company-profile.md create mode 100644 .plans/outstanding/08-endpoint-officers.md create mode 100644 .plans/outstanding/09-endpoint-catalogue-remaining.md create mode 100644 .plans/outstanding/10-testing-strategy.md create mode 100644 .plans/outstanding/11-docs-samples-migration.md create mode 100644 .plans/outstanding/99-recurring-issues-backlog.md create mode 100644 AGENTS.md delete mode 100644 CompaniesHouse.sln create mode 100644 CompaniesHouse.slnx create mode 100644 Directory.Packages.props create mode 100644 global.json create mode 100644 swagger.json delete mode 100644 tests/CompaniesHouse.Tests/ComparingArrayEnumWith.cs delete mode 100644 tests/CompaniesHouse.Tests/ComparingEnumWith.cs create mode 100644 tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs delete mode 100644 tests/CompaniesHouse.Tests/Initializer.cs diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 8e61e49..3f90b54 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -11,9 +11,13 @@ jobs: DOCKER_BUILDKIT: 1 BUILDKIT_PROGRESS: plain steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + submodules: recursive - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json - name: Docker Build NuGet packages run: | docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.VERSION }} --build-arg COMPANIES_HOUSE_API_KEY=${{ secrets.COMPANIES_HOUSE_API_KEY }} -f ./Dockerfile --output ./ . @@ -29,12 +33,12 @@ jobs: - name: Create Release id: create_release if: ${{ env.PUBLISH_PACKAGE }} - uses: actions/create-release@v1 + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ env.VERSION }} - release_name: Release ${{ env.VERSION }} + name: Release ${{ env.VERSION }} body: | Release ${{ env.VERSION }} draft: false diff --git a/.plans/README.md b/.plans/README.md new file mode 100644 index 0000000..33c67d3 --- /dev/null +++ b/.plans/README.md @@ -0,0 +1,44 @@ +# .plans + +The work breakdown for the **CompaniesHouse.NET v-next** major rewrite (on the +`prerelease` branch). + +## How this folder works + +- **`outstanding/`** — plans not yet completed. Each file is a self-contained, + refinable unit of work. Numeric prefixes suggest ordering (lower first). +- **`completed/`** — plans that have been fully delivered and verified. When you + finish a plan, **move its file here** in the same change. + +Plans are **living documents**. Refine tasks, record decisions, and capture +open questions as you learn. A plan is "done" only when its acceptance criteria +are met and its code is merged to `prerelease`. + +## Plan index (outstanding) + +| # | Plan | Theme | +|---|------|-------| +| 00 | `00-foundation-solution-and-build.md` | `.slnx`, central packages, multi-target net8/9/10, drop Newtonsoft, CI | +| 01 | `01-core-client-architecture.md` | `CompaniesHouseClient` entry point, sub-client pattern, `System.Text.Json`, response/error model | +| 02 | `02-di-extensions-ioptions.md` | Modern DI with `IOptions<>` / `AddOptions` / config binding | +| 03 | `03-string-backed-value-types.md` | Replace all enums with string-backed `readonly record struct`s | +| 04 | `04-enum-source-generator.md` | Roslyn generator that emits the value types | +| 05 | `05-api-enumerations-submodule.md` | `api-enumerations` git submodule + local "extra" lists | +| 06 | `06-endpoint-search.md` | All 7 search endpoints (incl. advanced search) — **start here for endpoints** | +| 07 | `07-endpoint-company-profile.md` | Company profile | +| 08 | `08-endpoint-officers.md` | Officer list + get appointment | +| 09 | `09-endpoint-catalogue-remaining.md` | Every other endpoint, to be split into its own plan when picked up | +| 10 | `10-testing-strategy.md` | Unit / scenario / integration / generator tests | +| 11 | `11-docs-samples-migration.md` | README, samples, v-old → v-next migration guide | +| 99 | `99-recurring-issues-backlog.md` | Historical pain points the design must eliminate | + +## Suggested execution order + +1. **Foundation** (`00`) — get the solution building on modern targets first. +2. **Core + DI + enums** (`01`, `02`, `03`, `04`, `05`) — the plumbing every + endpoint depends on. `03`/`04`/`05` can proceed in parallel with `01`. +3. **Endpoints, one at a time** (`06` → `07` → `08` → `09`), starting with + search. Each endpoint should be shippable on its own. +4. **Testing and docs** (`10`, `11`) run continuously alongside the endpoints. + +Keep `99` open as a checklist to validate the design against real-world bugs. diff --git a/.plans/completed/.gitkeep b/.plans/completed/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.plans/completed/00-foundation-solution-and-build.md b/.plans/completed/00-foundation-solution-and-build.md new file mode 100644 index 0000000..5625451 --- /dev/null +++ b/.plans/completed/00-foundation-solution-and-build.md @@ -0,0 +1,158 @@ +# 00 — Foundation: solution, packaging & build + +**Status:** complete +**Depends on:** nothing (do this first) +**Blocks:** everything + +## Goal + +Get the repository building on a modern, consistent foundation so every +subsequent plan lands on solid ground: modern target frameworks, central +package management, `.slnx` solution, and an updated CI pipeline. No behaviour +changes to the client itself — this is pure infrastructure. + +## Why + +The current projects target `netstandard1.1;netstandard2.0;net45`, pin package +versions per-project, use a classic `.sln`, and depend on `Newtonsoft.Json`. +For a clean-slate major version we want the most modern setup possible. + +## Scope + +### Target frameworks +- Multi-target the shippable libraries to **`net8.0;net9.0;net10.0`**. + - `src/CompaniesHouse` + - `src/CompaniesHouse.Extensions.Microsoft.DependencyInjection` +- Remove `netstandard*` / `net45` targets and the + `Microsoft.NETFramework.ReferenceAssemblies` and `Microsoft.Net.Http` + package references. +- Tests target `net8.0;net9.0;net10.0` (or just `net10.0` if multi-targeting + tests is not worth the run time — decide and note it). +- The source generator project (plan `04`) targets **`netstandard2.0`** — this + is a hard Roslyn requirement and is the one exception to the "no netstandard" + rule. + +### Central Package Management (CPM) +- Add a root **`Directory.Packages.props`** with + `true` and a + `` for every dependency used anywhere in the repo. +- Strip `Version="..."` from every `` in every `.csproj`. +- Consolidate versions so all projects share one version per package + (previously `Microsoft.Extensions.*` was pinned to `3.1.9`). + +### Directory.Build.props / .targets +- Enable **`enable`** (already have `ImplicitUsings`, + `LangVersion latest`, `TreatWarningsAsErrors`, `EnforceCodeStyleInBuild`). +- Refresh `` (currently hard-coded to 2020) — use a year-agnostic or + current value. +- Keep `IncludeSymbols` + `snupkg`; add ``, + ``, `` and + **deterministic builds** for good source-link/NuGet hygiene. +- Add `true` on the + shipped libraries so public XML docs are packaged. + +### Solution format +- Convert `CompaniesHouse.sln` to **`CompaniesHouse.slnx`** (the new XML + solution format). Verify `dotnet build CompaniesHouse.slnx` works with the + installed SDK (repo has 10.x and 11.x preview SDKs available). Delete the old + `.sln` once the `.slnx` is proven, or keep both briefly if tooling needs it — + decide and note. +- Add an `.slnx` entry for the future source-generator project. + +### CI workflow +- Update `.github/workflows/continuous-integration-workflow.yml`: + - Ensure the SDK it installs can build `net10.0` (+ `.slnx`); pin via + `global.json` if needed. + - Recursively checkout submodules (needed once plan `05` lands): + `actions/checkout` with `submodules: recursive`. + - Bump the `VERSION` scheme to the new major (the prerelease tag currently + produces `9.0.0-preN` — align with the chosen next major). + - Modernise action versions (`checkout@v2`/`setup-dotnet@v1` are old). + +## Tasks + +- [x] Add `Directory.Packages.props` and migrate all `PackageReference`s. +- [x] Retarget both library projects to `net8.0;net9.0;net10.0`. +- [x] Remove framework-reference/`Microsoft.Net.Http` packages. +- [x] Enable nullable + doc generation + deterministic build in `Directory.Build.props`. +- [x] Convert solution to `.slnx`; add all existing projects. +- [x] Update CI (SDK, submodules, versioning, action versions). +- [x] `dotnet build -c Release` and `dotnet test -c Release` are green. +- [x] Migrate test stack from NUnit/FluentAssertions to xUnit/Shouldly (scope + addition requested mid-execution — FluentAssertions' license changed to a + paid tier from v8; NUnit swapped along with it). See "Test stack + migration" below. + +## Design decisions + +- **CPM over per-project versions** — single place to bump, no drift. +- **Drop `netstandard`** — the new major only supports in-support .NET; this + is an intentional breaking change and is fine for a new major. + +## Open questions + +- Do we keep `net8.0` (LTS) as the floor, or go `net9.0`+ only? (Assumption: + keep `net8.0` for the widest supported reach; revisit if a dependency forces + it.) +Keep net8.0 for the time being. + +- Should tests multi-target or run once on `net10.0`? (Assumption: run on + `net10.0` only for speed; multi-target the libraries only.) +Just target the latest version of `net10.0` + +## Acceptance criteria + +- Solution builds and tests pass from a clean checkout with only the .NET SDK + installed. +- No `Newtonsoft.Json`, `netstandard`, or `net45` remain in any shipped + project (Newtonsoft removal itself is finished in plan `01`). +- All package versions resolve from `Directory.Packages.props`. + +## Test stack migration (Shouldly + xUnit) + +Mid-execution the user asked to drop FluentAssertions (license changed to a +paid tier from v8) in favour of **Shouldly**, and to swap **NUnit for xUnit** +at the same time. This expanded plan `00`'s scope to a full test-framework +port across all four test projects (~66 files). Completed: + +- `Directory.Packages.props`: removed `NUnit`, `NUnit3TestAdapter`, + `FluentAssertions`; added `xunit` (2.9.2), `xunit.runner.visualstudio` + (2.8.2), `Shouldly` (4.2.1). +- All NUnit attributes converted to xUnit: `[TestFixture]` removed, + `[Test]` → `[Fact]`/`[Theory]`, `[TestCase]` → `[InlineData]`, + `[TestCaseSource]` → `[MemberData]`, `[SetUp]`/`[TearDown]` → constructor + or `IAsyncLifetime`. +- All FluentAssertions/`NUnit` classic assertions converted to Shouldly + (`.Should().Be(x)` → `.ShouldBe(x)`, etc). +- **Enum-equivalency redesign**: `CompaniesHouse.Tests` had bespoke + FluentAssertions `IEquivalencyStep` classes (`ComparingEnumWith`, + `ComparingArrayEnumWith`) registered via a `[SetUpFixture]` (`Initializer`) + to bridge test-fixture raw wire strings against real deserialized C# enum + properties during `BeEquivalentTo` comparisons. Shouldly has no equivalent + extensibility point. Replaced with a single dependency-free helper, + `tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs`, exposing + `actual.ShouldBeEquivalentTo(expected, params string[] excludingPropertyNames)`. + It recursively walks public properties and bridges enum ↔ raw wire string + automatically via each enum member's `[EnumMember(Value=...)]` attribute (no + per-enum registration needed — a strict improvement over the old + `MapProviders` dictionaries, which had to be hand-maintained in parallel + with the enums). The old `ComparingEnumWith.cs`, `ComparingArrayEnumWith.cs` + and `Initializer.cs` were deleted. `EnumerationMappings.cs`/`MapProviders/*` + were kept — they're still used to enumerate wire-string values for + parameterized `TestCaseSource`/`MemberData` test data. +- Result: `CompaniesHouse.slnx` builds with 0 errors; 594/597 tests pass. The + 3 failures (`OfficersTestsInvalid`, `PersonsWithSignificantControlTestsInValid`, + `CompanyFilingHistoryTestsInvalid` in `CompaniesHouse.IntegrationTests`) are + pre-existing/unrelated to this migration — the live Companies House API now + returns `200` with an empty result set for malformed company numbers instead + of `404`, so the "invalid number ⇒ null data" assumption in these three + tests is stale against current API behaviour. Not fixed here (out of scope + for infrastructure plan `00`); worth a follow-up ticket. +- `AGENTS.md` and `.plans/outstanding/10-testing-strategy.md` updated to + document xUnit + Shouldly as the standing test-stack convention. + +## References + +- Issue #188 (System.Text.Json), #199/#191 (move to GitHub Actions — already + done, keep modern). +- `.slnx` format: current .NET SDK solution tooling. diff --git a/.plans/outstanding/01-core-client-architecture.md b/.plans/outstanding/01-core-client-architecture.md new file mode 100644 index 0000000..e70b17c --- /dev/null +++ b/.plans/outstanding/01-core-client-architecture.md @@ -0,0 +1,120 @@ +# 01 — Core client architecture + +**Status:** outstanding +**Depends on:** `00-foundation` +**Blocks:** all endpoint plans (`06`+) + +## Goal + +Establish the core client plumbing for the new major version: the +`CompaniesHouseClient` facade, the per-capability sub-client pattern, +`System.Text.Json` serialization, a redesigned response wrapper, and consistent +error handling. This is the skeleton every endpoint hangs off. + +## Why + +The old client wires up ~11 sub-clients by hand in a constructor, uses +`Newtonsoft.Json`, and returns a bare `CompaniesHouseClientResponse` that +carries only `Data`. Consumers have repeatedly asked for richer responses +(status code, headers, retry-after) and the serialization stack must move to +STJ. + +## Scope + +### Entry point & sub-client pattern (keep the shape) +- `CompaniesHouseClient : ICompaniesHouseClient` remains the single entry point. +- Every capability is its own sub-client behind its own interface + (`ICompaniesHouseSearchClient`, `ICompaniesHouseCompanyProfileClient`, ...), + aggregated by `ICompaniesHouseClient`. Preserve this — it is a deliberate, + liked design. +- Keep the two construction paths: + - `new CompaniesHouseClient(HttpClient)` — bring-your-own `HttpClient` + (the DI/`IHttpClientFactory` path). + - `new CompaniesHouseClient(settings)` — convenience path that builds an + `HttpClient` with the auth handler. +- Keep the small **URI builder** types per endpoint; they are unit-testable and + already proven. New endpoints follow the same pattern. + +### Serialization: System.Text.Json +- Central `JsonSerializerOptions` factory used by every sub-client: + - `PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower` (API is + snake_case) — verify member-by-member with `[JsonPropertyName]` where the + policy is insufficient. + - Register the string-backed value-type converters (plan `03`). + - Custom converters for CH's quirky date formats (`yyyy-MM-dd` and partial + dates like month/year only — see old `OptionalDateJsonConverter`). + - `NumberHandling = AllowReadingFromString` where the API returns numbers as + strings (issue #212: `total_results`/`items_per_page`/`start_index` came + back as strings for SearchAll). +- Prefer **`System.Text.Json` source-generation** (`JsonSerializerContext`) for + the response models to stay trim/AOT-friendly and fast. Decide whether to + require this for all models or adopt incrementally. +- Delete every `Newtonsoft.Json`-based converter under `JsonConverters/` and + reimplement the needed ones for STJ. + +### Response & error model (redesign — issues #181, #182, #189) +- Redesign the response wrapper so callers can see transport metadata, not just + the body. Candidate shape: + ```csharp + public sealed class CompaniesHouseClientResponse + { + public T? Data { get; } + public int StatusCode { get; } + public string? ReasonPhrase { get; } + public TimeSpan? RetryAfter { get; } // 429 handling (#181/#182) + public bool IsSuccess { get; } + // headers exposed read-only + } + ``` +- Define the semantics for **not found**: today several `GetX` methods return + `null` data on 404. Decide between "null data + `IsSuccess=false`" vs a + dedicated result type, and document it. (Assumption: keep returning a + response with `Data == null` for 404 on single-resource gets, but surface + `StatusCode`.) +- Replace `EnsureSuccessStatusCode2()` with explicit handling that captures the + status/headers/retry-after before throwing (or before returning a non-success + response). Define the exception type(s) for genuine errors. + +### Auth & settings +- Keep `IApiKeyProvider` abstraction (issues #192/#194 want the key pulled from + arbitrary locations) — the DI layer (plan `02`) supplies implementations. +- Keep `CompaniesHouseAuthorizationHandler` (Basic auth: API key as username). +- Confirm the base URI: the old default is `https://api.companieshouse.gov.uk/` + but the current spec host is `https://api.company-information.service.gov.uk/` + — **update the default** and note it as a breaking change. + +## Tasks + +- [ ] Add the STJ `JsonSerializerOptions` factory + `JsonSerializerContext`. +- [ ] Port/replace date and number converters for STJ. +- [ ] Redesign `CompaniesHouseClientResponse` (status/headers/retry-after). +- [ ] Rework the HTTP send/deserialize pipeline shared by sub-clients. +- [ ] Update the default base URI to the current CH host. +- [ ] Remove all `Newtonsoft.Json` usage from `src/CompaniesHouse`. +- [ ] Keep `CompaniesHouseClient` + sub-client interfaces as the public shape. + +## Design decisions + +- **Sub-client-per-capability** preserved — good separation, already liked. +- **STJ everywhere**, ideally source-generated — perf + trimming/AOT. +- **Richer response wrapper** — directly resolves long-standing requests. + +## Open questions + +- One `JsonSerializerContext` for the whole assembly, or per-endpoint? (Lean: + one shared context.) +- Do we keep throwing on non-2xx, or always return a response and let callers + branch on `IsSuccess`? (Lean: return response for expected 404s; throw for + unexpected 5xx/transport — finalise here.) + +## Acceptance criteria + +- A trivial call (e.g. company profile) round-trips via STJ with no Newtonsoft. +- Response exposes status code + retry-after on both success and failure. +- All sub-clients share one serialization + send pipeline. + +## References + +- Issues #181/#182 (headers/retry-after), #189 (response redesign), #188 (STJ), + #212 (numbers-as-strings), #156 (raw values), #202 (auth specifics). +- API host: `api.company-information.service.gov.uk` (see `swagger.json`). diff --git a/.plans/outstanding/02-di-extensions-ioptions.md b/.plans/outstanding/02-di-extensions-ioptions.md new file mode 100644 index 0000000..fcc96d1 --- /dev/null +++ b/.plans/outstanding/02-di-extensions-ioptions.md @@ -0,0 +1,94 @@ +# 02 — DI extensions with IOptions<> + +**Status:** outstanding +**Depends on:** `01-core-client-architecture` +**Blocks:** nothing (parallel with endpoints) + +## Goal + +Modernise `CompaniesHouse.Extensions.Microsoft.DependencyInjection` to use the +`IOptions<>` pattern (`AddOptions`, `IConfiguration` binding, validation) and +provide clean overloads to configure the client several ways. + +## Why + +The current extension stashes a `CompaniesHouseClientOptions` as a singleton it +news-up by hand, rather than using the framework's options pipeline. It also +targets `netstandard2.0` and pins `Microsoft.Extensions.*` to `3.1.9`. A new +major is the time to adopt the idiomatic options approach and let consumers bind +from configuration and validate on start. + +## Scope + +### Options type +- Define `CompaniesHouseClientOptions` (name/section e.g. `"CompaniesHouse"`) + with at least `BaseUri`, `ApiKey`, and room for future knobs (timeouts, + document API base URI). Add **`DataAnnotations`** (`[Required]` ApiKey, valid + `Uri`) for validation. +- Provide a defaulted `BaseUri` (current CH host — see plan `01`). + +### Registration via the options pipeline +- Use `services.AddOptions()` with: + - `.Bind(configuration.GetSection("CompaniesHouse"))` + - `.Configure(...)` for the delegate overloads + - `.ValidateDataAnnotations()` + - `.ValidateOnStart()` +- Register the client as a **typed `HttpClient`** + (`AddHttpClient`) and set + `BaseAddress` from resolved options. +- Keep `services.TryAdd*` for all sub-client interfaces resolving from the + single `ICompaniesHouseClient` (issue #190 — libraries should use `TryAdd`; + already done, preserve it). +- Register `IApiKeyProvider` (default `StaticApiKeyProvider` from options), + overridable by consumers (issues #192/#194). + +### Overloads (multiple ways to configure) +Provide these entry points: +1. `AddCompaniesHouseClient(string apiKey)` +2. `AddCompaniesHouseClient(Uri baseUri, string apiKey)` +3. `AddCompaniesHouseClient(Action configure)` +4. `AddCompaniesHouseClient(Action configure)` +5. `AddCompaniesHouseClient(IConfiguration section)` / + `AddCompaniesHouseClient(IConfiguration config, string sectionName = "CompaniesHouse")` +6. A named/keyed variant so multiple configured clients can coexist (optional — + note if deferred). +- Allow the caller to customise the underlying `IHttpClientBuilder` (return it, + or accept a `Action`), so Polly/resilience handlers can be + added. + +### Packaging +- Retarget to `net8.0;net9.0;net10.0`, versions via `Directory.Packages.props`. +- Bump `Microsoft.Extensions.*` to versions matching the target frameworks. + +## Tasks + +- [ ] Add DataAnnotations validation to `CompaniesHouseClientOptions`. +- [ ] Rewrite registration around `AddOptions` + `ValidateOnStart`. +- [ ] Add the `IConfiguration`-binding overloads. +- [ ] Keep `TryAdd` sub-client registrations; add new sub-clients as endpoints land. +- [ ] Return `IHttpClientBuilder` (or expose a hook) for resilience config. +- [ ] Update DI tests (plan `10`) for the new surface. + +## Design decisions + +- **`IOptions<>` + `ValidateOnStart`** — fail fast on a missing/invalid API key + instead of at first request. +- **Config binding** — first-class `appsettings.json` support. + +## Open questions + +- Do we ship keyed/named multi-client support in v-next or defer? (Assumption: + design the API so it can be added later; defer the actual keyed overloads + unless cheap.) +- Section name default: `"CompaniesHouse"` — confirm. + +## Acceptance criteria + +- `services.AddCompaniesHouseClient(Configuration.GetSection("CompaniesHouse"))` + binds and validates; a missing API key fails at startup. +- All previously-registered sub-client interfaces still resolve. +- No `Newtonsoft.Json`; builds on all target frameworks. + +## References + +- Issues #190 (TryAdd), #192/#194 (API key from anywhere), #193 (TryAdd PR). diff --git a/.plans/outstanding/03-string-backed-value-types.md b/.plans/outstanding/03-string-backed-value-types.md new file mode 100644 index 0000000..ab40a2e --- /dev/null +++ b/.plans/outstanding/03-string-backed-value-types.md @@ -0,0 +1,116 @@ +# 03 — String-backed value types (replace all enums) + +**Status:** outstanding +**Depends on:** `01-core-client-architecture` (for STJ options) +**Blocks:** every model with an "enum" field; pairs with `04` (generator) + +## Goal + +Replace every C# `enum` used on the API contract with a **string-backed +`readonly record struct`** that preserves the raw wire value and never throws on +an unrecognised value. This kills the single largest category of historical bugs +in this library. + +## Why + +Companies House claim API versioning but do not honour it, so new string values +appear in responses without warning. With plain enums + `StringEnumConverter`, +each new value is a `JsonException`/`ArgumentException` at deserialization for +every consumer who hasn't upgraded. See the design blog: + and the trail of +issues: #168, #185, #186, #197, #198, #200, #201, #209, #218. + +## The pattern + +For each API enum group (e.g. `CompanyStatus`, `CompanyType`, `OfficerRole`): + +```csharp +[JsonConverter(typeof(CompanyStatusJsonConverter))] +public readonly record struct CompanyStatus(string Value) +{ + // Known values (generated — see plan 04) + public static CompanyStatus Active => new("active"); + public static CompanyStatus Dissolved => new("dissolved"); + // ... + + public bool IsKnown => KnownValues.Contains(Value); + + // Optional: human-readable description from api-enumerations + public string? Description => Descriptions.GetValueOrDefault(Value); + + public override string ToString() => Value; +} +``` + +Converter (trivial — just reads/writes the string, no lookup, no throw): + +```csharp +public sealed class CompanyStatusJsonConverter : JsonConverter +{ + public override CompanyStatus Read(ref Utf8JsonReader reader, Type t, JsonSerializerOptions o) + => new(reader.GetString()!); + public override void Write(Utf8JsonWriter writer, CompanyStatus value, JsonSerializerOptions o) + => writer.WriteStringValue(value.Value); +} +``` + +### Requirements +- **Never throws** on unknown values — the raw string is retained (issue #156 + asked for raw-value access; this delivers it for free). +- **Equatable / usable in `switch`** via `== Known.X` patterns; value-equality + from `record struct`. +- **`IsKnown`** to branch on recognised vs unrecognised. +- **Prefix helpers** where CH uses structured values (the blog's + `IsProcessing`/`ProcessingStep` idea) — e.g. filing categories/subcategories. + Provide these where the enumeration is naturally hierarchical. +- **Null handling** — the old `OptionalStringEnumConverter` mapped null to a + default. Decide: default to `default(struct)` (empty `Value`) vs a `None` + static. (Lean: `Value == ""`/`default` represents absent; expose `HasValue`.) +- Optional **`Description`** property backed by the api-enumerations + descriptions, so consumers get the friendly text (partially covers issue #205 + "SIC codes?" and the various `*_descriptions.yml`). + +## Scope + +- Define the shared building blocks (base converter helpers, common members, + analyzers/format) that the generator (plan `04`) will emit against. +- Hand-author **one or two** value types first (e.g. `CompanyStatus`) to prove + the pattern and the converter, its tests, and STJ registration — then let the + generator take over producing the rest. +- Registration: value-type converters are applied via `[JsonConverter]` on the + type, so no central registration is strictly needed, but confirm they compose + with the shared `JsonSerializerOptions` (plan `01`). + +## Tasks + +- [ ] Implement the reference value type + converter by hand (`CompanyStatus`). +- [ ] Unit tests: known value, unknown value (no throw, raw preserved), + round-trip, equality, `IsKnown`, null/empty. +- [ ] Decide + document null/absent semantics and prefix-helper conventions. +- [ ] Freeze the shape the generator must emit (feed into plan `04`). +- [ ] Migrate models to use value types as endpoints are built. + +## Design decisions + +- **`readonly record struct` wrapping a string** — preserves raw value, value + equality, cheap, immutable. Chosen over "enum + Unknown fallback" (loses the + raw value) per the blog. +- **Converter does no validation** — unknown values are first-class, not errors. + +## Open questions + +- Provide implicit `string` conversions? (Lean: explicit `Value`/`ToString` + only, to avoid accidental stringly-typed misuse; revisit.) +- Ship `Description` in v-next or defer? (Lean: ship it — it's cheap once the + generator reads the YAML descriptions and answers real requests.) + +## Acceptance criteria + +- Deserializing an unknown status string succeeds and round-trips byte-for-byte. +- Known values compare equal to the static members. +- No plain enum remains on any wire-facing model once endpoints are migrated. + +## References + +- Blog: +- Issues #156, #168, #185, #186, #197, #198, #200, #201, #209, #218. diff --git a/.plans/outstanding/04-enum-source-generator.md b/.plans/outstanding/04-enum-source-generator.md new file mode 100644 index 0000000..6763d8d --- /dev/null +++ b/.plans/outstanding/04-enum-source-generator.md @@ -0,0 +1,124 @@ +# 04 — Enum source generator + +**Status:** outstanding +**Depends on:** `03-string-backed-value-types` (target shape), `05-submodule` +(input data) +**Blocks:** full model coverage + +## Goal + +Build a Roslyn **incremental source generator** that emits the string-backed +value types (plan `03`) from the Companies House `api-enumerations` YAML plus our +own local "extra" lists — so new enum values are picked up by rebuilding and +releasing, never by hand-coding. + +## Why + +Hand-maintaining enum members is exactly the treadmill that produced issues +#168, #185, #186, #197, #198, #200, #201, #209, #218. Generating from the +authoritative YAML means a version bump (not a code change) absorbs new values, +and unknown values never break consumers anyway (plan `03`). + +## Input data + +The `api-enumerations` repo (submodule — plan `05`) contains YAML files whose +top-level keys are enum groups and whose entries are `'wire-value': "Friendly +Description"`. Example (`constants.yml`): + +```yaml +company_status: + 'active' : "Active" + 'dissolved' : "Dissolved" +company_type: + 'ltd' : "Private limited company" + 'plc' : "Public limited company" +``` + +Relevant files include (non-exhaustive — enumerate at implementation time): +`constants.yml` (company_status, company_type, company_summary, jurisdiction, +identification_type, ...), `filing_history_descriptions.yml`, +`mortgage_descriptions.yml`, `psc_descriptions.yml`, +`disqualified_officer_descriptions.yml`, `exemption_descriptions.yml`, +`officer_filing.yml`, `psc_filing.yml`, etc. + +## Design + +### Generator project +- New project `src/CompaniesHouse.SourceGenerator`, targeting + **`netstandard2.0`** (Roslyn requirement), referencing + `Microsoft.CodeAnalysis.CSharp` (analyzer/generator packaging — `PrivateAssets` + so it isn't a runtime dependency of consumers). +- Ship the generator **inside the `CompaniesHouse` package** (analyzer asset), + or wired as a project-reference `OutputItemType="Analyzer"` — decide packaging + (lean: bundle as analyzer in the main package so no extra dependency for + consumers). + +### Inputs → generator +- Feed the YAML files as **`AdditionalFiles`** (from the submodule path + our + local extras folder) so the generator reads them via + `context.AdditionalTextsProvider` (incremental, cache-friendly). Avoid doing + network I/O in the generator — the submodule provides the files at build time. +- A small **mapping/config** (attribute, or a `enum-map.json`) declares which + YAML key maps to which C# type name + namespace, plus: + - PascalCase member-name conversion from wire values + (`private-unlimited` → `PrivateUnlimited`), with a collision/override table + for awkward values (empty string, values differing only by punctuation, or + C# keyword clashes). + - Which groups get prefix helpers (plan `03`). + - Which groups expose `Description`. + +### Output +- For each configured group, emit the `readonly record struct`, its `[JsonConverter]`, + the static known-value members, `KnownValues`, optional `Descriptions` + dictionary, `IsKnown`, and any configured prefix helpers — matching the frozen + shape from plan `03`. +- Emit into the `CompaniesHouse.Response` (or a dedicated `CompaniesHouse.Enums`) + namespace. + +### Extensibility (our own extra lists) +- Support a repo-local `enumerations/extra/*.yml` (same format) merged on top of + the submodule data, so we can add values CH hasn't published yet or define + library-only groups. Merge order: submodule first, extras override/append. + +## Tasks + +- [ ] Scaffold the generator project (netstandard2.0 + CodeAnalysis). +- [ ] YAML parsing (a lightweight parser or `YamlDotNet` — note: generator deps + must be bundled into the analyzer; prefer a minimal parser to avoid load + issues). +- [ ] Wire YAML files as `AdditionalFiles` (submodule + extras). +- [ ] Implement wire-value → PascalCase with an override table. +- [ ] Emit value types matching plan `03`'s shape. +- [ ] Snapshot/verify tests over the generated output (plan `10`). +- [ ] Package the generator as an analyzer in the `CompaniesHouse` package. + +## Design decisions + +- **Incremental generator + `AdditionalFiles`** — no network at build, cacheable, + fast. +- **Local extras override submodule** — lets us react even faster than CH. +- **Bundle in the main package** — zero extra dependency for consumers. + +## Open questions + +- YAML parser choice inside the generator (bundling `YamlDotNet` into an + analyzer can be fiddly). (Lean: minimal hand-rolled parser for the simple + `key: {'v':"desc"}` shape; revisit if files use richer YAML.) +- Do we generate at consumer build-time, or generate once in *this* repo and + commit the output? (Lean: generate in *this* repo's build so the shipped + package contains concrete types; the generator need not run in consumers. + Confirm — this affects packaging: generator could be a build-time-only tool + rather than a shipped analyzer.) + +## Acceptance criteria + +- Adding a value to a YAML file and rebuilding produces a new static member with + no hand-editing. +- Generated types compile clean under `TreatWarningsAsErrors`. +- Unknown values (not in YAML) still deserialize fine at runtime (plan `03`). + +## References + +- api-enumerations: +- Blog: +- Recurring enum issues: #168, #185, #186, #197, #198, #200, #201, #209, #218. diff --git a/.plans/outstanding/05-api-enumerations-submodule.md b/.plans/outstanding/05-api-enumerations-submodule.md new file mode 100644 index 0000000..ee554c1 --- /dev/null +++ b/.plans/outstanding/05-api-enumerations-submodule.md @@ -0,0 +1,85 @@ +# 05 — api-enumerations submodule & local extras + +**Status:** outstanding +**Depends on:** `00-foundation` (CI submodule checkout) +**Blocks:** `04-enum-source-generator` (provides its input) + +## Goal + +Pull the Companies House `api-enumerations` data into the repo as a **git +submodule** and establish a repo-local "extras" area, so the source generator +(plan `04`) has a versioned, updatable source of enum values plus a place for our +own additions. + +## Why + +The enum values must come from an authoritative, refreshable source rather than +being copied into the repo by hand. A submodule pins an exact commit (reproducible +builds) while making updates a one-liner. We also need a way to add values CH +hasn't published yet, so we keep a local overlay. + +## Scope + +### Submodule +- Add `https://github.com/companieshouse/api-enumerations` as a submodule at a + stable path, e.g. `external/api-enumerations`. + ``` + git submodule add https://github.com/companieshouse/api-enumerations external/api-enumerations + ``` +- Pin to a known-good commit; document the update procedure: + ``` + git submodule update --remote external/api-enumerations + ``` +- Ensure CI checks out submodules recursively (coordinated in plan `00`: + `actions/checkout` with `submodules: recursive`). The `Dockerfile` build path + must also receive the submodule content (copy it into the build context). + +### Local extras overlay +- Create `enumerations/extra/` in this repo for our own YAML lists in the same + `key: {'value': "Description"}` format. Two uses: + 1. **Overrides/additions** to existing groups (values CH is late publishing). + 2. **Library-only groups** not present upstream. +- Document the merge rule (submodule first, extras override/append) — consumed + by plan `04`. + +### Consumption +- The generator reads YAML from **both** `external/api-enumerations/*.yml` and + `enumerations/extra/*.yml` via `AdditionalFiles` globs in the generator/host + project. + +## Tasks + +- [ ] Add the submodule at `external/api-enumerations` and pin a commit. +- [ ] Add `.gitmodules`; verify a fresh `git clone --recursive` populates it. +- [ ] Create `enumerations/extra/` with a README describing the format + merge + rules and a small example file. +- [ ] Ensure CI and the Dockerfile build include submodule content. +- [ ] Document the "how to refresh enumerations" steps (in the extras README or + AGENTS.md). + +## Design decisions + +- **Submodule over vendoring/copy** — pins an exact upstream commit, trivially + updatable, keeps provenance clear. +- **Local overlay** — lets us out-run CH's publishing cadence without forking. + +## Open questions + +- Submodule path: `external/api-enumerations` vs `lib/` vs `third_party/`. + (Lean: `external/`.) +- Auto-update cadence: a scheduled CI job that bumps the submodule and opens a + PR? (Nice-to-have; note as a follow-up, not required for v-next.) + +## Acceptance criteria + +- Fresh `git clone --recursive` yields the YAML files on disk. +- CI builds have the submodule content available to the generator. +- Adding a file under `enumerations/extra/` is picked up by the generator + (verified once plan `04` lands). + +## References + +- Enumerations repo: +- Files seen: `constants.yml`, `filing_history_descriptions.yml`, + `mortgage_descriptions.yml`, `psc_descriptions.yml`, `officer_filing.yml`, + `disqualified_officer_descriptions.yml`, `exemption_descriptions.yml`, etc. diff --git a/.plans/outstanding/06-endpoint-search.md b/.plans/outstanding/06-endpoint-search.md new file mode 100644 index 0000000..9d860a6 --- /dev/null +++ b/.plans/outstanding/06-endpoint-search.md @@ -0,0 +1,99 @@ +# 06 — Endpoint: Search (start here) + +**Status:** outstanding +**Depends on:** `01-core`, `03-value-types` (for status/type fields) +**Blocks:** nothing; first endpoint to build + +## Goal + +Build the full **Search** surface — the most-used part of the API — end to end +against the current documentation, using the URI-builder pattern. This is the +first endpoint rebuilt from scratch in v-next and sets the template for the rest. + +## Endpoints to cover + +From the API reference (verify exact paths/params against the live docs): + +| Method on client | Docs page | Path (verify) | +|---|---|---| +| `SearchAllAsync` | search-all | `GET /search` | +| `SearchCompaniesAsync` | search-companies | `GET /search/companies` | +| `SearchOfficersAsync` | search-officers | `GET /search/officers` | +| `SearchDisqualifiedOfficersAsync` | search-disqualified-officers | `GET /search/disqualified-officers` | +| `SearchCompaniesAlphabeticallyAsync` | search-companies-alphabetically | `GET /alphabetical-search/companies` | +| `SearchDissolvedCompaniesAsync` | search-dissolved-companies | `GET /dissolved-search/companies` | +| `AdvancedCompanySearchAsync` | advanced-company-search | `GET /advanced-search/companies` | + +Reference docs: +- +- +- +- +- +- +- + +## Scope + +### URI builders (keep the pattern) +- Preserve the `SearchUriBuilder`/factory approach: a base builder for the common + `q` / `items_per_page` / `start_index` query params, with per-search subclasses + adding their own params. +- **Advanced search** (issue #216 "not implemented", #220 PR) has a rich set of + filter params (company name includes/excludes, company status, company type, + company subtype, dissolved-from/to, incorporated-from/to, SIC codes, location, + size). Model these as a dedicated request with a builder that emits only the + supplied params. +- **Company search** carries a `restrictions` query param — the old code had a + bug (`if (string.IsNullOrWhiteSpace(...))` added it only when *empty*). Fix: + add `restrictions` only when **non**-empty (issues #203/#204/#208). + +### Request models +- One request record per search (`SearchAllRequest`, `SearchCompaniesRequest`, + `AdvancedCompanySearchRequest`, ...). Use the string-backed value types for + `company_status`/`company_type`/`company_subtype` filters (plan `03`). + +### Response models +- Model each response faithfully from the docs: the search envelope + (`total_results`, `items_per_page`, `start_index`, `page_number`, `kind`, + `items[]`) plus per-search item shapes. +- **Numbers-as-strings**: SearchAll returned `total_results` etc. as strings — + handle with `NumberHandling.AllowReadingFromString` or a converter (issue #212). +- Item enum-ish fields (company status/type, officer role, etc.) use value types. +- The old code had a polymorphic `SearchItemConverter` for the "all" search + (mixed item kinds keyed by `kind`) — reimplement for STJ if `search/all` + returns heterogeneous items. + +### Client wiring +- `ICompaniesHouseSearchClient` (+ granular interfaces if we keep the + per-search-interface split) hung off `CompaniesHouseClient`. Register in DI + (plan `02`). + +## Tasks + +- [ ] Confirm each path + full query-param list from the live docs. +- [ ] Build request models (with value-type filters). +- [ ] Build/extend URI builders per search; fix the `restrictions` bug. +- [ ] Build response envelope + item models from the docs. +- [ ] Handle numbers-as-strings and any polymorphic items. +- [ ] Wire sub-client + DI registrations. +- [ ] Tests: URI-builder unit tests, deserialization scenario tests, one + integration test per search (plan `10`). + +## Open questions + +- Do we keep separate `ICompaniesHouseSearchCompanyClient` etc. interfaces, or + collapse into one `ICompaniesHouseSearchClient` with all methods? (Lean: one + cohesive search sub-client interface; note the breaking change.) +- Advanced search param names — confirm exact spelling from docs. + +## Acceptance criteria + +- All 7 searches callable from `CompaniesHouseClient`, returning typed results. +- Unknown status/type values in results don't throw (value types). +- `restrictions` is only sent when provided. + +## References + +- Issues #203/#204/#208 (restrictions), #212 (numeric strings), #216/#220 + (advanced search), #185/#186 (new company statuses/types in search results). diff --git a/.plans/outstanding/07-endpoint-company-profile.md b/.plans/outstanding/07-endpoint-company-profile.md new file mode 100644 index 0000000..8cf53b1 --- /dev/null +++ b/.plans/outstanding/07-endpoint-company-profile.md @@ -0,0 +1,62 @@ +# 07 — Endpoint: Company Profile + +**Status:** outstanding +**Depends on:** `01-core`, `03-value-types`; do after `06-search` +**Blocks:** nothing + +## Goal + +Rebuild the **Company Profile** endpoint from the current documentation. + +Docs: + + +`GET /company/{companyNumber}` + +## Scope + +### Client +- `ICompaniesHouseCompanyProfileClient.GetCompanyProfileAsync(string companyNumber, CancellationToken)` + hung off `CompaniesHouseClient`. +- Keep the `CompanyProfileUriBuilder` pattern (`company/{escaped-number}`). +- 404 semantics per plan `01` (response with `Data == null`, `StatusCode` set). + +### Response model (faithful to docs) +Model the full profile, notably the historically-missing/tricky bits: +- `company_status` / `company_status_detail` / `company_type` / `subtype` / + `jurisdiction` — **string-backed value types** (plan `03`) — these are the + exact fields that produced #184/#185/#186/#200/#214. +- `registered_office_address`, `accounts` (incl. `accounting_reference_date`, + next/last made-up-to, overdue flags), `confirmation_statement`, + `annual_return`, `sic_codes` (issue #205), `previous_company_names`, + `foreign_company_details` (issue #217), `links`, `branch_company_details`, + `date_of_creation`/`date_of_cessation`, `has_charges`, `has_insolvency_history`, + `has_super_secure_pscs`, `registered_office_is_in_dispute`, + `undeliverable_registered_office_address`, `can_file`, `is_community_interest_company`. +- Partial/February-style dates and month/year-only fields — use the shared date + converters (plan `01`). + +## Tasks + +- [ ] Confirm the full response schema from the docs. +- [ ] Build the response model with value-type enums + `foreign_company_details` + + `sic_codes`. +- [ ] Wire sub-client + DI registration. +- [ ] Tests: URI builder, deserialization of a real sample payload, integration + test for a known company number. + +## Open questions + +- Does `accounting_reference_date` etc. come back as `{day, month}` objects? + Model as nested types — confirm from docs. + +## Acceptance criteria + +- A real company profile deserializes fully, including `foreign_company_details` + and `sic_codes`. +- Unknown `company_status`/`company_type` values do not throw. + +## References + +- Issues #184/#185/#186 (types/statuses), #200/#214 (breaking data changes), + #205 (SIC codes), #217 (foreign_company_details), #179 (company address). diff --git a/.plans/outstanding/08-endpoint-officers.md b/.plans/outstanding/08-endpoint-officers.md new file mode 100644 index 0000000..83d86b7 --- /dev/null +++ b/.plans/outstanding/08-endpoint-officers.md @@ -0,0 +1,71 @@ +# 08 — Endpoint: Officers + +**Status:** outstanding +**Depends on:** `01-core`, `03-value-types`; do after `07-company-profile` +**Blocks:** nothing + +## Goal + +Rebuild the **Officers** endpoints from the current documentation. + +Docs: +- List: +- Get appointment: + +Paths (verify): +- `GET /company/{companyNumber}/officers` +- `GET /company/{companyNumber}/appointments/{appointmentId}` + +## Scope + +### Client +- `ICompaniesHouseOfficersClient`: + - `GetOfficersAsync(companyNumber, startIndex = 0, pageSize = 35, ..., CancellationToken)` + - `GetOfficerAppointmentAsync(companyNumber, appointmentId, CancellationToken)` +- Keep `OfficersUriBuilder` / `OfficersAppointmentUriBuilder` patterns. +- The list endpoint supports `register_view`, `order_by`, `items_per_page`, + `start_index` — confirm and expose the useful ones. + +### Response models (faithful to docs) +- List envelope: `total_results`, `items_per_page`, `start_index`, + `active_count`, `inactive_count`, `resigned_count`, `kind`, `links`, `items[]`. + - Ensure `total_results` is present and typed `int` (issues #206/#207). +- Officer item / appointment: + - `officer_role` — **string-backed value type** (issues #197/#198: + `managing-officer` and other new roles kept breaking the old enum). + - `person_number` (issues #221/#222 — was missing). + - `address`, `date_of_birth` (month/year only — partial-date converter), + `appointed_on`, `resigned_on`, `nationality`, `occupation`, + `country_of_residence`, `identification` (+ `identification_type` value + type), `former_names`, `links.officer.appointments`, + `contact_details`, `principal_office_address`, + `responsibilities`/`is_pre_1992_appointment` where present. +- Provide the computed `OfficerId` convenience the community relied on (issues + #169/#171 — it was deleted then restored). Derive from the appointments link. + +## Tasks + +- [ ] Confirm list + appointment schemas and query params from docs. +- [ ] Build response models with value-type `officer_role`/`identification_type` + and `person_number`, `total_results:int`. +- [ ] Restore the `OfficerId` computed property. +- [ ] Wire sub-client + DI registrations. +- [ ] Tests: URI builders, deserialization of sample payloads (incl. an unknown + officer role), integration tests. + +## Open questions + +- Default page size: API default is 35 for officers — match it rather than the + old 25. Confirm. + +## Acceptance criteria + +- Officer list and single appointment deserialize fully, including + `person_number` and `total_results`. +- An unknown `officer_role` does not throw. +- `OfficerId` is available on items. + +## References + +- Issues #197/#198 (officer roles), #206/#207 (total_results), + #221/#222 (person_number), #169/#171 (OfficerId), #165/#166 (get appointment). diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md new file mode 100644 index 0000000..6027a5e --- /dev/null +++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md @@ -0,0 +1,93 @@ +# 09 — Endpoint catalogue: the remaining API surface + +**Status:** outstanding +**Depends on:** `01-core`, `03-value-types`; do after `08-officers` +**Blocks:** nothing + +## Goal + +Track and rebuild **every remaining endpoint** of the Companies House Public +Data API, one at a time, following the pattern established by plans `06`–`08`. +When picking up an endpoint from this catalogue, **split it into its own plan +file** (`09a-...`, `09b-...`, or a dedicated number) rather than doing it inline +here. + +Reference index: + + +## Catalogue (rebuild in roughly this order) + +Grouped by API tag. Confirm exact paths/params/schemas from the docs at +implementation time. + +### Registered office & registers +- [ ] **Registered office address** — `GET /company/{n}/registered-office-address` + (issues #163/#164, #179). +- [ ] **Registers** — `GET /company/{n}/registers`. + +### Filing history +- [ ] **Filing history list** — `GET /company/{n}/filing-history`. +- [ ] **Single filing** — `GET /company/{n}/filing-history/{transactionId}`. + Category/subcategory are the enum-heavy fields that caused #168 (`debenture`), + #209/#210, #218/#219 (`investment-company`) — use value types + the + `filing_history_descriptions.yml` generator data. Note subcategory can be + an array in some payloads (old `FilingSubcategoryConverter`). + +### Officers (related) +- [ ] **Company officer disqualifications (natural)** — + `GET /disqualified-officers/natural/{officerId}`. +- [ ] **Corporate officer disqualifications** — + `GET /disqualified-officers/corporate/{officerId}`. +- [ ] **Officer appointments list** — `GET /officers/{officerId}/appointments` + (the current `GetAppointmentsAsync`). + +### Persons with significant control (PSC) +- [ ] **PSC list** — `GET /company/{n}/persons-with-significant-control`. +- [ ] **Individual PSC** — `.../individual/{id}`. +- [ ] **Corporate entity PSC** — `.../corporate-entity/{id}`. +- [ ] **Legal person PSC** — `.../legal-person/{id}`. +- [ ] **PSC statements** — `.../statements` and `.../statements/{id}`. +- [ ] **Super-secure PSC** — `.../super-secure/{id}`. + PSC kinds/natures-of-control are enum-heavy (issues #200/#201/#211/#214); + use value types + `psc_descriptions.yml`. Ensure `total_results` + (issue #211) and `identification` (issue #173/#155) are modelled. + +### Charges +- [ ] **Charges list** — `GET /company/{n}/charges`. +- [ ] **Single charge** — `GET /company/{n}/charges/{chargeId}`. + Status/classification/particulars are enum-heavy — value types. + +### Insolvency & exemptions +- [ ] **Insolvency** — `GET /company/{n}/insolvency`. +- [ ] **Exemptions** — `GET /company/{n}/exemptions` + (uses `exemption_descriptions.yml`). + +### UK establishments +- [ ] **UK establishments** — `GET /company/{n}/uk-establishments`. + +### Documents (separate Document API host) +- [ ] **Document metadata** — Document API `GET /document/{id}`. +- [ ] **Download document** — `GET /document/{id}/content` (binary; keep the + separate base URI + document sub-client, and the DI document options). + +## Per-endpoint checklist (apply to each) + +- [ ] Confirm path, query params, and response schema from the live docs. +- [ ] Request model (if any) + URI builder following the established pattern. +- [ ] Response model faithful to docs; all enum-ish fields use value types. +- [ ] Sub-client interface hung off `CompaniesHouseClient` + DI registration. +- [ ] Tests: URI builder, deserialization scenario, integration. +- [ ] Move the split-out plan to `completed/` when done. + +## Open questions + +- Which endpoints are in-scope for the first v-next release vs a later minor? + (Lean: search + company profile + officers + registered office + filing + history + PSC + charges for the first stable; documents/exemptions/registers + can follow.) + +## References + +- Full reference index (above). Enum data in `api-enumerations` (plan `05`). +- Issues: #163/#164/#179, #168/#209/#210/#218/#219 (filing categories), + #155/#173/#200/#201/#211/#214 (PSC), #205 (SIC), #180 (sandbox). diff --git a/.plans/outstanding/10-testing-strategy.md b/.plans/outstanding/10-testing-strategy.md new file mode 100644 index 0000000..329c1da --- /dev/null +++ b/.plans/outstanding/10-testing-strategy.md @@ -0,0 +1,89 @@ +# 10 — Testing strategy + +**Status:** outstanding +**Depends on:** `00-foundation`; runs continuously alongside every plan +**Blocks:** nothing (but gates "done" for each plan) + +## Goal + +Define and stand up the test approach for v-next so every endpoint and the enum +generator ship with meaningful coverage, and so the historical "it broke in +production on a new value" class of bug is caught by tests. + +## Test projects (existing, to modernise) + +- `tests/CompaniesHouse.Tests` — fast **unit tests** (URI builders, converters, + value types, response mapping). +- `tests/CompaniesHouse.ScenarioTests` — **behavioural** tests against canned + HTTP responses (no network). +- `tests/CompaniesHouse.IntegrationTests` — hit the **real API** (needs the + `api_key` env var); skipped/soft-failed without it. +- `tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests` — DI + registration/resolution tests. + +Retarget all to the new TFMs (plan `00`), remove `Newtonsoft.Json` from tests +(issues #177/#178/#195/#196 were all Newtonsoft bumps — delete the dependency), +enable nullable. + +## What to test + +### Value types & generator (highest priority — this is the whole point) +- **Unknown value never throws**: deserialize a status/type/role string that is + *not* in the YAML; assert it round-trips and `IsKnown == false`. +- Known values equal their static members; equality/`GetHashCode`; `ToString`. +- Null/empty/absent semantics. +- Prefix helpers (e.g. filing subcategory) where used. +- **Generator snapshot tests**: given a small YAML input, assert the generated + source matches a checked-in snapshot (use `Microsoft.CodeAnalysis` test host + or a verify library). Guards against accidental generator regressions. + +### Per-endpoint +- **URI builder** unit tests: correct path + query for each param combination, + proper escaping, and *omission* of optional params (e.g. the `restrictions` + bug, #203/#204/#208). +- **Deserialization scenario tests**: feed **real sample payloads** (captured + from the API / docs) through the client via a stubbed `HttpMessageHandler` + (or WireMock) and assert the mapped model — including the fields that were + historically missing (`person_number`, `total_results`, `foreign_company_details`, + `sic_codes`). +- **Numbers-as-strings** (#212), **partial dates**, **404 → null data + + status** (plan `01`). +- **Error/transport**: 429 surfaces `RetryAfter` (#181/#182); non-2xx surfaces + status + headers. + +### DI +- Every sub-client interface resolves from the container. +- Options bind from `IConfiguration`; missing API key fails `ValidateOnStart`. + +## Tooling + +- **Test runner: xUnit.** **Assertions: Shouldly.** (NUnit and FluentAssertions + have been fully removed as of plan `00` — FluentAssertions' license changed + to a paid tier from v8 onward.) For deep object-graph comparisons that need + to bridge a raw wire string against a string-backed value type, use the + repo's own `EquivalencyAssertionExtensions.ShouldBeEquivalentTo(...)` helper + in `CompaniesHouse.Tests` rather than reaching for a new dependency. +- Add an HTTP stubbing approach for scenario tests (a hand-rolled + `HttpMessageHandler` stub, or `WireMock.Net`). Decide and standardise. +- Snapshot/verify library for generator output (e.g. `Verify`). +- CI already publishes TRX results — keep that. + +## Tasks + +- [ ] Retarget test projects; strip Newtonsoft; enable nullable. +- [ ] Establish the HTTP-stub helper for scenario tests. +- [ ] Add value-type + generator snapshot tests. +- [ ] Add a per-endpoint test checklist (mirror plan `09`'s per-endpoint list). +- [ ] Wire integration tests to skip cleanly without `api_key`. + +## Acceptance criteria + +- `dotnet test -c Release` is green offline (integration tests skip without a + key). +- Every shipped endpoint has URI-builder + deserialization coverage. +- The generator has snapshot coverage. + +## References + +- Issues #177/#178/#195/#196 (remove Newtonsoft from tests), #180 (sandbox), + #181/#182 (error metadata), plus all the endpoint-specific issues in `09`. diff --git a/.plans/outstanding/11-docs-samples-migration.md b/.plans/outstanding/11-docs-samples-migration.md new file mode 100644 index 0000000..ac637eb --- /dev/null +++ b/.plans/outstanding/11-docs-samples-migration.md @@ -0,0 +1,66 @@ +# 11 — Docs, samples & migration guide + +**Status:** outstanding +**Depends on:** endpoints as they land; finalise near release +**Blocks:** the v-next release announcement + +## Goal + +Refresh all consumer-facing documentation for v-next: the README, the runnable +sample, and a clear **migration guide** from the previous major, since v-next is +deliberately breaking. + +## Scope + +### README +- Rewrite for the new setup: + - Installation (both packages), supported TFMs (net8/9/10). + - `CompaniesHouseClient` usage (settings + `HttpClient` construction paths). + - **DI section** using the new `IOptions<>` overloads and `IConfiguration` + binding (plan `02`). + - **Enum/value-type section**: explain string-backed value types, why unknown + values never throw, `IsKnown`, `Description`, and how to pattern-match + (link the blog post). + - Response model: how to read `StatusCode`/`RetryAfter`/`Data` (plan `01`). + - Per-endpoint usage snippets as endpoints land. +- Fix stale bits: the AppVeyor badge (CI is GitHub Actions now), the 2020 + copyright, and the old base URL / API-key portal links. + +### Sample project +- Update `samples/SampleProject` to the new client + DI, demonstrating search, + company profile, officers, and handling an unknown enum value gracefully. + +### Migration guide (`MIGRATION.md` or a README section) +- Enumerate the breaking changes: + - Target frameworks dropped (`netstandard`/`net45`). + - `Newtonsoft.Json` → `System.Text.Json` (custom converters replaced). + - **All enums → string-backed value types** (biggest behavioural change; show + before/after for a `switch`). + - `CompaniesHouseClientResponse` shape change (now carries status/headers). + - Default base URI change (`companieshouse.gov.uk` → + `company-information.service.gov.uk`). + - DI API changes (options/overloads); any renamed methods/interfaces. +- Provide copy-paste before/after snippets for the common cases. + +### Contributor docs +- Ensure `AGENTS.md` and `.plans/README.md` stay accurate. +- Document the "refresh enumerations" and "release" flows. + +## Tasks + +- [ ] Rewrite README for v-next (progressive, per-endpoint). +- [ ] Update the sample project. +- [ ] Write the migration guide with before/after snippets. +- [ ] Fix stale badges/links/copyright. +- [ ] Cross-check AGENTS.md + plans are current at release. + +## Acceptance criteria + +- A new user can install, configure (via DI and directly), and make a search + + profile call by following the README alone. +- The migration guide covers every breaking change with a before/after example. + +## References + +- Blog (enum rationale): +- API reference: diff --git a/.plans/outstanding/99-recurring-issues-backlog.md b/.plans/outstanding/99-recurring-issues-backlog.md new file mode 100644 index 0000000..1f62997 --- /dev/null +++ b/.plans/outstanding/99-recurring-issues-backlog.md @@ -0,0 +1,91 @@ +# 99 — Recurring issues backlog (design validation) + +**Status:** outstanding (keep open as a checklist) +**Purpose:** a living record of the real-world pain points the v-next design must +eliminate. Validate each plan against this list; tick items off as the design +provably handles them. + +## The dominant class: new enum values break deserialization + +By far the most common bug report. Every one of these is "Companies House +returned a string my client's enum didn't know about, and it threw." + +- #168 — `ArgumentException: Requested value 'debenture'` (filing subcategory) +- #185 / #186 — new company statuses & types +- #184 — company type `registered-overseas-entity` +- #197 / #198 — officer role `managing-officer` and other new roles +- #200 / #201 — PSC deserialization / missing enum values +- #209 / #210 — new filing category/subcategory values +- #218 / #219 — filing subcategory `investment-company` + +**Design answer:** string-backed value types (plan `03`) + source generator from +`api-enumerations` (plans `04`/`05`). Unknown values **never throw** and the raw +string is preserved. ✅ when: a scenario test deserializes an unknown value for +every enum-ish field without throwing. + +## Second class: missing / mistyped fields on responses + +- #205 — SIC codes +- #206 / #207 — `total_results` missing on officers +- #211 — `total_results` missing on PSC +- #212 — SearchAll `total_results`/`items_per_page`/`start_index` typed as + string, should be int +- #217 — `foreign_company_details` missing on company profile +- #221 / #222 — `person_number` missing on officers +- #155 / #173 — PSC `identification` +- #179 — company address +- #214 — PSC breaking data changes + +**Design answer:** rebuild every response model faithfully from the current docs +(plans `06`–`09`), with `NumberHandling.AllowReadingFromString` for CH's +string-numbers (plan `01`). ✅ when: each endpoint's model is doc-complete and +has a deserialization test over a real payload. + +## Third class: raw value / observability + +- #156 — consumers want the raw string, not a mapped/swallowed value + +**Design answer:** value types keep `.Value` (the raw string) always. ✅ when: +`.Value` returns the exact wire string for unknown values. + +## Response & error ergonomics + +- #181 / #182 — include StatusCode, ReasonPhrase, RetryAfter on failures +- #189 — redesign `CompaniesHouseClientResponse` +- #202 — auth discrepancies + +**Design answer:** redesigned response wrapper carrying transport metadata +(plan `01`). ✅ when: a 429 surfaces `RetryAfter`; non-2xx surfaces status/headers. + +## Serialization & dependencies + +- #188 — move to `System.Text.Json` +- #176 / #177 / #178 / #195 / #196 — endless Newtonsoft.Json version bumps + +**Design answer:** STJ everywhere, Newtonsoft removed entirely (plans `00`/`01`). +✅ when: no `Newtonsoft.Json` reference anywhere in the repo. + +## DI / configuration + +- #190 / #193 — use `services.TryAdd*` (done — preserve) +- #192 / #194 — pull the API key from any location +- (v-next) move to `IOptions<>` with config binding + validation + +**Design answer:** `IApiKeyProvider` abstraction + `IOptions<>` pipeline +(plan `02`). ✅ when: a custom `IApiKeyProvider` can be registered and the key +can be bound from `IConfiguration`. + +## Feature gaps + +- #216 / #220 — advanced search not implemented → now first-class (plan `06`) +- #165 / #166 — get individual officer appointment (plan `08`) +- #163 / #164 — registered office address (plan `09`) +- #180 — sandbox/test API support (configurable base URI — plans `01`/`02`) +- #213 / #215 — optional date formatting on descriptable types +- #169 / #171 — keep the `OfficerId` computed property (plan `08`) + +## How to use this file + +When closing out a plan, re-read the relevant class above and confirm the design +demonstrably handles it (ideally with a test named after the issue). This file +is the "have we actually fixed the recurring pain?" gate for the release. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8ff83c2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,141 @@ +# AGENTS.md + +Guidance for AI agents (and humans) working in this repository. Read this +before making changes, then read the relevant plan in `.plans/outstanding/`. + +## What this project is + +`CompaniesHouse.NET` is a .NET client SDK for the +[Companies House Public Data API](https://developer-specs.company-information.service.gov.uk/companies-house-public-data-api/reference). +It is published as two NuGet packages: + +- **`CompaniesHouse`** — the core client (`CompaniesHouseClient`) and all + request/response models. +- **`CompaniesHouse.Extensions.Microsoft.DependencyInjection`** — DI helpers + for registering the client with `IServiceCollection`. + +## Current state: a v-next rewrite + +We are building a **new major version** on the `prerelease` branch. Breaking +changes are expected and welcome. The old surface lives on `master` and can be +referenced for behaviour, but we are rebuilding the client endpoint-by-endpoint +from the official API documentation rather than porting the old code verbatim. + +**The single source of truth for the work is `.plans/`.** Do not freelance a +large redesign — pick up an outstanding plan, refine it if needed, and execute +it. + +## Non-negotiable design decisions + +These are settled for the new major version. Do not reverse them without +updating the relevant plan and calling it out explicitly. + +1. **Multi-target `net8.0;net9.0;net10.0`.** No `netstandard`, no `net45`. Drop + the `Microsoft.NETFramework.ReferenceAssemblies` and `Microsoft.Net.Http` + references. +2. **`System.Text.Json` only.** Remove every reference to `Newtonsoft.Json` + (see issue #188). No new dependency on Json.NET in any project, including + tests. +3. **No plain C# `enum`s on the wire.** Every API "enum" is modelled as a + **string-backed `readonly record struct`** that preserves the raw value and + never throws on an unrecognised value. See + `.plans/outstanding/03-string-backed-value-types.md` and the design blog + post: . +4. **Enum values are generated, not hand-written.** A Roslyn **source + generator** produces the string-backed types from the Companies House + [`api-enumerations`](https://github.com/companieshouse/api-enumerations) + YAML (pulled in as a git submodule) plus our own local "extra" lists. We + ship a new package version to pick up new values — we do not hand-edit + generated types. See plans `04` and `05`. +5. **`CompaniesHouseClient` stays the entry point.** Every capability hangs off + it as its own focused sub-client (e.g. search, company profile, officers), + each behind its own interface, exactly as today. +6. **DI uses `IOptions<>`.** The DI package uses `AddOptions`, + `IConfiguration` binding and validation, with overloads to configure the + client several ways. See `.plans/outstanding/02-di-extensions-ioptions.md`. +7. **`nullable` reference types enabled** across all projects. +8. **Test stack: xUnit + Shouldly.** No NUnit, no FluentAssertions (license + changed to a paid tier from v8). Use `[Fact]`/`[Theory]`/`[MemberData]` and + `IAsyncLifetime` for async setup/teardown; assert with Shouldly's + `.ShouldBe(...)` family. For deep object-graph comparisons against test + fixtures that hold raw wire strings, use the repo's own + `EquivalencyAssertionExtensions.ShouldBeEquivalentTo(...)` helper in + `CompaniesHouse.Tests` (bridges enum <-> wire string, no FluentAssertions + `IEquivalencyStep` needed). + +## Repository layout + +``` +src/ + CompaniesHouse/ core client + models + CompaniesHouse.Extensions.Microsoft.DependencyInjection/ DI helpers + (planned) CompaniesHouse.SourceGenerator/ enum value-type generator +tests/ + CompaniesHouse.Tests/ unit tests + CompaniesHouse.IntegrationTests/ hit the real API (needs key) + CompaniesHouse.ScenarioTests/ end-to-end behaviour + CompaniesHouse.Extensions.*.Tests/ DI tests +samples/SampleProject/ runnable usage sample +external/api-enumerations/ (planned) git submodule +swagger.json partial CH OpenAPI 2.0 spec +CompaniesHouse.slnx solution (XML .slnx format) +.plans/ the work breakdown (read this) +``` + +## Conventions + +- **File-scoped namespaces**, `ImplicitUsings` enabled, `LangVersion` latest. +- **Warnings are errors** (`TreatWarningsAsErrors=true`) — keep the build clean. +- One public type per file; interface `IThing` lives next to `Thing`. +- URLs are built with small, testable **URI builder** types (see + `src/CompaniesHouse/UriBuilders`). Keep this pattern for new endpoints. +- Async methods take a `CancellationToken` (defaulted) and end in `Async`. +- JSON property names come from the API (snake_case); map with + `[JsonPropertyName(...)]` or a snake_case naming policy — never rename the + wire contract. +- Prefer **central package management**: versions live in + `Directory.Packages.props`, not in individual `.csproj` files. + +## Build, test, format + +Run from the repository root. + +```powershell +dotnet restore +dotnet build -c Release +dotnet test -c Release # unit + scenario tests +dotnet format --verify-no-changes # style gate +``` + +Integration tests need a Companies House API key in the `api_key` environment +variable and are skipped/failed without one — do not treat their absence as a +regression when working offline. + +## Working agreement for agents + +- **Pick up a plan** from `.plans/outstanding/`. Work in the order implied by + the numeric prefixes (foundation first) unless the plan says otherwise. +- **Keep changes surgical and endpoint-scoped.** Build the client up + gradually; do not rewrite everything in one pass. +- **Update the plan as you learn.** Plans are living documents — refine tasks, + record decisions, and note open questions. +- **When a plan is fully delivered and verified, move its file** from + `.plans/outstanding/` to `.plans/completed/` in the same change. +- **Design for the recurring issues.** A huge share of historical bug reports + are "new enum value broke deserialisation" (#168, #185, #186, #197, #200, + #201, #209, #218) and "missing field on a response" (#205, #206, #211, #212, + #217, #221). The string-backed value types and generator exist to kill the + first class entirely; model responses faithfully from the docs to avoid the + second. +- **Cite your sources.** Reference the specific API doc page and/or GitHub + issue in code comments and PRs when a decision is non-obvious. + +## Key references + +- API reference: +- Enumerations repo: +- Enum design rationale: +- Issue tracker: + +## Commits +Commit in small amounts with a summary of what work we're building and not include the co-authorized by, however, do not push! Don't commit the .plans folder or the AGENTS.md \ No newline at end of file diff --git a/CompaniesHouse.sln b/CompaniesHouse.sln deleted file mode 100644 index f9576c0..0000000 --- a/CompaniesHouse.sln +++ /dev/null @@ -1,61 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30523.141 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse.Tests", "tests\CompaniesHouse.Tests\CompaniesHouse.Tests.csproj", "{BC825074-5662-421D-A849-FD94158F3029}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse.IntegrationTests", "tests\CompaniesHouse.IntegrationTests\CompaniesHouse.IntegrationTests.csproj", "{6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse", "src\CompaniesHouse\CompaniesHouse.csproj", "{9639747A-C49F-42E9-85A4-41FCBFCB7A16}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CompaniesHouse.ScenarioTests", "tests\CompaniesHouse.ScenarioTests\CompaniesHouse.ScenarioTests.csproj", "{E1DA350A-FC73-4999-9B02-CBF8538945C9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleProject", "samples\SampleProject\SampleProject.csproj", "{4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompaniesHouse.Extensions.Microsoft.DependencyInjection", "src\CompaniesHouse.Extensions.Microsoft.DependencyInjection\CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj", "{5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests", "tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj", "{011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BC825074-5662-421D-A849-FD94158F3029}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC825074-5662-421D-A849-FD94158F3029}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC825074-5662-421D-A849-FD94158F3029}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BC825074-5662-421D-A849-FD94158F3029}.Release|Any CPU.Build.0 = Release|Any CPU - {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6B83B8C2-9DA6-42D5-BB32-BD0C6FDFC14D}.Release|Any CPU.Build.0 = Release|Any CPU - {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9639747A-C49F-42E9-85A4-41FCBFCB7A16}.Release|Any CPU.Build.0 = Release|Any CPU - {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E1DA350A-FC73-4999-9B02-CBF8538945C9}.Release|Any CPU.Build.0 = Release|Any CPU - {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F078B5D-05F5-4134-9B8F-1AC43BFCFD7E}.Release|Any CPU.Build.0 = Release|Any CPU - {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C6AC4CD-8E8B-4700-B01B-7B57C74AE791}.Release|Any CPU.Build.0 = Release|Any CPU - {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {011662B7-5E03-4E6A-BAEA-B5C3FE169D3F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1CF7FB6A-FF49-463C-95A9-C35E7F97B96E} - EndGlobalSection -EndGlobal diff --git a/CompaniesHouse.slnx b/CompaniesHouse.slnx new file mode 100644 index 0000000..da86daa --- /dev/null +++ b/CompaniesHouse.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Directory.Build.props b/Directory.Build.props index 221bfca..752047e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,13 +3,23 @@ true latest enable + enable true + + $(WarningsNotAsErrors);Nullable + + $(NoWarn);CS1591 Kevin Smith Kevsoft - Copyright © Kevsoft 2020 + Copyright © Kevsoft CompaniesHouse;Registrar;Kevsoft;API;REST;WebService https://raw.githubusercontent.com/kevbite/CompaniesHouse.NET/master/companies-house.jpg @@ -20,4 +30,12 @@ git https://github.com/kevbite/CompaniesHouse.NET + + + + true + true + true + true + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..4eb02f6 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,32 @@ + + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dockerfile b/Dockerfile index fbcc36e..f7e0a27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,14 @@ ARG CONFIGURATION="Release" ARG NUGET_PACKAGE_VERSION="1.0.0" ARG COMPANIES_HOUSE_API_KEY -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS restore +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore ARG CONFIGURATION COPY ./*.props . COPY ./*.targets . -COPY ./*.sln . +COPY ./global.json . +COPY ./*.slnx . COPY ./*.jpg . COPY ./README.md . COPY ./LICENSE . diff --git a/global.json b/global.json new file mode 100644 index 0000000..d46d21e --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestFeature", + "allowPrerelease": false + } +} diff --git a/samples/SampleProject/SampleProject.csproj b/samples/SampleProject/SampleProject.csproj index f5aafc9..1016f92 100644 --- a/samples/SampleProject/SampleProject.csproj +++ b/samples/SampleProject/SampleProject.csproj @@ -1,7 +1,7 @@ Exe - net7.0 + net10.0 false diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj index 7faeb94..167ad53 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj @@ -1,19 +1,19 @@ - netstandard2.0 + net8.0;net9.0;net10.0 true snupkg + true The CompaniesHouse extensions for ASP.NET Core - Copyright © Kevsoft 2020 @@ -22,8 +22,9 @@ - - + + + diff --git a/src/CompaniesHouse/CompaniesHouse.csproj b/src/CompaniesHouse/CompaniesHouse.csproj index 532c91d..225f01e 100644 --- a/src/CompaniesHouse/CompaniesHouse.csproj +++ b/src/CompaniesHouse/CompaniesHouse.csproj @@ -1,19 +1,15 @@  - netstandard1.1;netstandard2.0;net45 + net8.0;net9.0;net10.0 true snupkg + true - - - - - CompaniesHouse.NET CompaniesHouse.NET @@ -21,18 +17,15 @@ A simple .NET API client wrapper for CompaniesHouse - + - - - - - + + diff --git a/swagger.json b/swagger.json new file mode 100644 index 0000000..8097c5c --- /dev/null +++ b/swagger.json @@ -0,0 +1,195 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Companies House Public Data API", + "description": "An API suite providing read only access to search and retrieve public company data" + }, + "host": "api.company-information.service.gov.uk", + "schemes": [ + "https", + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [{ + "name": "registeredOfficeAddress", + "description": "Registered office address" + }, + { + "name": "companyProfile", + "description": "Company profile" + }, + { + "name": "search", + "description": "Search" + }, + { + "name": "officers", + "description": "Officers" + }, + { + "name": "registers", + "description": "Registers" + }, + { + "name": "charges", + "description": "Charges" + }, + { + "name": "filingHistory", + "description": "Filing history" + }, + { + "name": "insolvency", + "description": "Insolvency" + }, + { + "name": "exemptions", + "description": "Exemptions" + }, + { + "name": "officerDisqualifications", + "description": "Officer disqualifications" + }, + { + "name": "officerAppointments", + "description": "Officer appointments" + }, + { + "name": "UKEstablishments", + "description": "UK Establishments" + }, + { + "name": "personsWithSignificantControl", + "description": "Persons with significant control" + }, + { + "name": "pscDiscrepancies", + "description": "PSC discrepancies" + }, + { + "name": "personsWithSignificantControlNotifications", + "description": "Persons with significant control notifications" + } + ], + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "security": [{ + "api_key": [] + } + ], + "paths": { + "/company/{companyNumber}/registered-office-address": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json#/getCompanyAddress" + }, + "/company/{companyNumber}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json" + }, + "/search": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchAll" + }, + "/search/companies": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchCompanies" + }, + "/search/officers": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchOfficers" + }, + "/search/disqualified-officers": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchDisqualified-officers" + }, + "/dissolved-search/companies": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchDissolved" + }, + "/alphabetical-search/companies": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAlphabetic" + }, + "/advanced-search/companies": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAdvanced" + }, + "/company/{company_number}/officers": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/listCompanyOfficers" + }, + "/company/{company_number}/appointments/{appointment_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/getCompanyOfficerAppointment" + }, + "/company/{company_number}/registers": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json" + }, + "/company/{company_number}/filing-history/{transaction_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/getFilingHistory" + }, + "/company/{company_number}/filing-history": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/listFilingHistory" + }, + "/company/{company_number}/exemptions": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json" + }, + "/disqualified-officers/natural/{officer_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getNatural" + }, + "/disqualified-officers/corporate/{officer_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getCorporate" + }, + "/officers/{officer_id}/appointments": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json" + }, + "/company/{company_number}/charges": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeList" + }, + "/company/{company_number}/charges/{charge_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeDetails" + }, + "/company/{company_number}/insolvency": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json#/insolvencyCase" + }, + "/company/{company_number}/uk-establishments": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json" + }, + "/company/{company_number}/persons-with-significant-control": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSC" + }, + "/company/{company_number}/persons-with-significant-control/individual/{notification_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualPSC" + }, + "/company/{company_number}/persons-with-significant-control/individual-beneficial-owner/{notification_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualBO" + }, + "/company/{company_number}/persons-with-significant-control/corporate-entity/{notification_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityPSC" + }, + "/company/{company_number}/persons-with-significant-control/corporate-entity-beneficial-owner/{notification_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityBO" + }, + "/company/{company_number}/persons-with-significant-control/legal-person/{notification_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonPSC" + }, + "/company/{company_number}/persons-with-significant-control/legal-person-beneficial-owner/{notification_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonBO" + }, + "/company/{company_number}/persons-with-significant-control-statements": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSCStatements" + }, + "/company/{company_number}/persons-with-significant-control-statements/{statement_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getPSCStatement" + }, + "/company/{company_number}/persons-with-significant-control/super-secure/{super_secure_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecurePSC" + }, + "/company/{company_number}/persons-with-significant-control/super-secure-beneficial-owner/{super_secure_id}": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecureBO" + }, + "/company/{company_number}/persons-with-significant-control/{psc_id}/notifications": { + "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json" + } + } +} diff --git a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj index cbdcc13..7372dfb 100644 --- a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj +++ b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj @@ -1,14 +1,15 @@ - net7.0 + net10.0 false - - - - + + + + + diff --git a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs index c33853b..c9b95fb 100644 --- a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs @@ -1,11 +1,12 @@ using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests { public class ServiceCollectionExtensionsTests { - [Test] + [Fact] public void CanResolveCompaniesHouseClients() { var serviceCollection = new ServiceCollection(); @@ -14,21 +15,21 @@ public void CanResolveCompaniesHouseClients() var serviceProvider = serviceCollection.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); } - [Test] + [Fact] public void CanResolveCompaniesHouseDocumentClients() { @@ -38,9 +39,9 @@ public void CanResolveCompaniesHouseDocumentClients() var serviceProvider = serviceCollection.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); - Assert.NotNull(scope.ServiceProvider.GetService()); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj b/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj index 4e31e2c..75dbe52 100644 --- a/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj +++ b/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj @@ -1,22 +1,19 @@  - net7.0 + net10.0 false - - - - - - - - + + + + + \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Keys.cs b/tests/CompaniesHouse.IntegrationTests/Keys.cs index f27e30f..5349fa2 100644 --- a/tests/CompaniesHouse.IntegrationTests/Keys.cs +++ b/tests/CompaniesHouse.IntegrationTests/Keys.cs @@ -4,6 +4,6 @@ namespace CompaniesHouse.IntegrationTests { public static class Keys { - public static string ApiKey { get; } = Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY"); + public static string ApiKey { get; } = Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY")!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs index e4b6389..83cae95 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs @@ -1,21 +1,22 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Appointments; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.AppointmentsTests { - public abstract class AppointmentsTestBase + public abstract class AppointmentsTestBase : IAsyncLifetime { - protected CompaniesHouseClient Client; - protected CompaniesHouseClientResponse Result; + protected CompaniesHouseClient Client = null!; + protected CompaniesHouseClientResponse Result = null!; - [SetUp] - public void Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - When(); + await When(); } + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() @@ -24,4 +25,4 @@ private void GivenACompaniesHouseClient() Client = new CompaniesHouseClient(settings); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs index f2c0946..d22e231 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs @@ -1,30 +1,31 @@ -using System.Threading.Tasks; -using NUnit.Framework; +using System.Threading.Tasks; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.AppointmentsTests { - [TestFixture] + public class AppointmentsTestsValid : AppointmentsTestBase { // Sergey Brin's officer id private const string ValidOfficerId = "uQNQ-blSo-8PiOaehWClTPmbZNI"; - [SetUp] + protected override async Task When() { await WhenRetrievingAppointmentsForAValidOfficer() - .ConfigureAwait(false); + ; } - [Test] + [Fact] public void ThenTheDataItemsAreNotEmpty() { - Assert.That(Result.Data.Items, Is.Not.Empty); + Result.Data.Items.ShouldNotBeEmpty(); } private async Task WhenRetrievingAppointmentsForAValidOfficer() { - Result = await Client.GetAppointmentsAsync(ValidOfficerId).ConfigureAwait(false); + Result = await Client.GetAppointmentsAsync(ValidOfficerId); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs index ee2699d..baf2a10 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs @@ -1,10 +1,11 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Charges; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.ChargesTests { - [TestFixture] + public class ChargeByIdTestsInValid : ChargesTestBase { private const string CompanyNumber = "00000000"; @@ -12,7 +13,7 @@ public class ChargeByIdTestsInValid : ChargesTestBase protected override async Task When() => Result = await Client.GetChargeByIdAsync(CompanyNumber, ChargeId); - [Test] - public void ThenChargesListIsNull() => Assert.Null(Result.Data); + [Fact] + public void ThenChargesListIsNull() => Result.Data.ShouldBeNull(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs index 6e5cb90..dd78c22 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs @@ -1,10 +1,11 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Charges; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.ChargesTests { - [TestFixture] + public class ChargeByIdTestsValid : ChargesTestBase { private const string CompanyNumber = "00445790"; @@ -12,7 +13,7 @@ public class ChargeByIdTestsValid : ChargesTestBase protected override async Task When() => Result = await Client.GetChargeByIdAsync(CompanyNumber, ChargeId); - [Test] - public void ThenChargesListIsNull() => Assert.IsNotNull(Result.Data); + [Fact] + public void ThenChargesListIsNull() => Result.Data.ShouldNotBeNull(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs index 0a11896..671a8b7 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs @@ -1,17 +1,18 @@ using System.Threading.Tasks; using CompaniesHouse.Response.Charges; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.ChargesTests { - [TestFixture] + public class ChargesListTestsInValid : ChargesTestBase { private const string CompanyNumber = "00000000"; protected override async Task When() => Result = await Client.GetChargesListAsync(CompanyNumber); - [Test] - public void ThenChargesListIsNull() => Assert.IsEmpty(Result.Data.Items); + [Fact] + public void ThenChargesListIsNull() => Result.Data.Items.ShouldBeEmpty(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs index 40fe978..bdc4933 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs @@ -1,29 +1,28 @@ using System.Threading.Tasks; -using CompaniesHouse.Response.Charges; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.ChargesTests { - [TestFixtureSource(nameof(TestCases))] - public class ChargesListTestsValid : ChargesTestBase + public class ChargesListTestsValid { - private readonly string _companyNumber; + private readonly CompaniesHouseClient _client; - public ChargesListTestsValid(string companyNumber) => _companyNumber = companyNumber; - protected override async Task When() => Result = await Client.GetChargesListAsync(_companyNumber); - - [Test] - public void ThenChargesListIsNotEmpty() => Assert.IsNotEmpty(Result.Data.Items); + public ChargesListTestsValid() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(CompaniesHouseUris.Default, Keys.ApiKey)); + } - public static string[] TestCases() + [Theory] + [InlineData("03977902")] + [InlineData("00445790")] + [InlineData("00002065")] + [InlineData("03487070")] + public async Task ThenChargesListIsNotEmpty(string companyNumber) { - return new[] - { - "03977902", // Google - "00445790", // Tesco - "00002065", // Lloyds Bank PLCo - "03487070" - }; - } + var result = await _client.GetChargesListAsync(companyNumber); + + result.Data.Items.ShouldNotBeEmpty(); + } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs index a4a8aed..33c4d44 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs @@ -1,19 +1,21 @@ using System.Threading.Tasks; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.ChargesTests { - public abstract class ChargesTestBase + public abstract class ChargesTestBase : IAsyncLifetime { - protected CompaniesHouseClient Client { get; set; } - protected CompaniesHouseClientResponse Result; + protected CompaniesHouseClient Client { get; set; } = null!; + protected CompaniesHouseClientResponse Result = null!; - [SetUp] - public async Task Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - await When().ConfigureAwait(false); + await When(); } + + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestBase.cs index 9584326..dcd953e 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestBase.cs @@ -1,22 +1,20 @@ -using System; using System.Threading.Tasks; -using CompaniesHouse.Response.CompanyFiling; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyFilingHistoryTests { - public abstract class CompanyFilingHistoryTestBase + public abstract class CompanyFilingHistoryTestBase : IAsyncLifetime { - protected CompaniesHouseClient _client; + protected CompaniesHouseClient _client = null!; - [SetUp] - public async Task Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - await When() - .ConfigureAwait(false); + await When(); } + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() @@ -25,4 +23,4 @@ private void GivenACompaniesHouseClient() _client = new CompaniesHouseClient(settings); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs index a851628..c2da1df 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs @@ -1,32 +1,33 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.CompanyFiling; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyFilingHistoryTests { - [TestFixture] + public class CompanyFilingHistoryTestsInvalid : CompanyFilingHistoryTestBase { private const string InvalidCompanyNumber = "ABC00000"; - private CompaniesHouseClientResponse _result; + private CompaniesHouseClientResponse _result = null!; protected override async Task When() { await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() - .ConfigureAwait(false); + ; } - [Test] + [Fact] public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data.Items, Is.Null); + _result.Data.Items.ShouldBeNull(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() { _result = await _client.GetCompanyFilingHistoryAsync(InvalidCompanyNumber) - .ConfigureAwait(false); + ; } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs index fa9d6ac..45ceb3d 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs @@ -2,55 +2,43 @@ using System.Linq; using System.Threading.Tasks; using CompaniesHouse.Response.CompanyFiling; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyFilingHistoryTests { - [TestFixtureSource(nameof(TestCases))] - public class CompanyFilingHistoryTestsValid : CompanyFilingHistoryTestBase + public class CompanyFilingHistoryTestsValid { - private readonly string _companyNumber; - private List _results; + private readonly CompaniesHouseClient _client; - public CompanyFilingHistoryTestsValid(string companyNumber) + public CompanyFilingHistoryTestsValid() { - _companyNumber = companyNumber; + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - public static string[] TestCases() - { - return new[] - { - "03977902", // Google - "00445790", // Tesco - "00002065", // Lloyds Bank PLC - "09965459", // Amazebytes - "06768813", // TEST & COOL LTD, - "00059337", - "SC171417", - "09018331" - }; - } - - protected override async Task When() + [Theory] + [InlineData("03977902")] + [InlineData("00445790")] + [InlineData("00002065")] + [InlineData("09965459")] + [InlineData("06768813")] + [InlineData("00059337")] + [InlineData("SC171417")] + [InlineData("09018331")] + public async Task ThenTheDataItemsAreNotEmpty(string companyNumber) { var page = 0; var size = 100; - _results = new List(); + var results = new List(); CompaniesHouseClientResponse result; do { - result = await _client.GetCompanyFilingHistoryAsync(_companyNumber, page++ * size, size) - .ConfigureAwait(false); - _results.AddRange(result.Data.Items); + result = await _client.GetCompanyFilingHistoryAsync(companyNumber, page++ * size, size); + results.AddRange(result.Data.Items); } while (result.Data.Items.Any()); - } - [Test] - public void ThenTheDataItemsAreNotEmpty() - { - Assert.That(_results, Is.Not.Empty); + results.ShouldNotBeEmpty(); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs index 5451971..9aa3295 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs @@ -1,33 +1,34 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.CompanyFiling; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyFilingHistoryTests { - [TestFixture] + public class FilingHistoryByTransactionIdTestsInvalid : CompanyFilingHistoryTestBase { private const string InvalidCompanyNumber = "ABC00000"; private const string InvalidTransactionId = "00000000"; - private CompaniesHouseClientResponse _result; + private CompaniesHouseClientResponse _result = null!; protected override async Task When() { await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() - .ConfigureAwait(false); + ; } - [Test] + [Fact] public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data, Is.Null); + _result.Data.ShouldBeNull(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() { _result = await _client.GetFilingHistoryByTransactionAsync(InvalidCompanyNumber, InvalidTransactionId) - .ConfigureAwait(false); + ; } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs index 59a133c..5cac2c8 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs @@ -1,33 +1,34 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.CompanyFiling; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyFilingHistoryTests { - [TestFixture] + public class FilingHistoryByTransactionIdTestsValid : CompanyFilingHistoryTestBase { private const string InvalidCompanyNumber = "00445790"; private const string InvalidTransactionId = "QUE3UDBHVU9hZGlxemtjeA"; - private CompaniesHouseClientResponse _result; + private CompaniesHouseClientResponse _result = null!; protected override async Task When() { await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() - .ConfigureAwait(false); + ; } - [Test] + [Fact] public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data, Is.Not.Null); + _result.Data.ShouldNotBeNull(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() { _result = await _client.GetFilingHistoryByTransactionAsync(InvalidCompanyNumber, InvalidTransactionId) - .ConfigureAwait(false); + ; } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs index ca73cda..c794095 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs @@ -1,34 +1,25 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Insolvency; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyInsolvencyInformationTests { - [TestFixture] public class CompanyInsolvencyInformationTests { - private CompaniesHouseClient _client; - private CompaniesHouseClientResponse _result; - - [OneTimeSetUp] - public void GivenACompaniesHouseClient() - { - var settings = new CompaniesHouseSettings(Keys.ApiKey); - - _client = new CompaniesHouseClient(settings); - } + private readonly CompaniesHouseClient _client; - [SetUp] - public async Task WhenSearching() + public CompanyInsolvencyInformationTests() { - _result = await _client.GetCompanyInsolvencyInformationAsync("08749409") - .ConfigureAwait(false); + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Test] - public void TheItemsAreReturned() + [Fact] + public async Task TheItemsAreReturned() { - Assert.That(_result.Data, Is.Not.Null); + var result = await _client.GetCompanyInsolvencyInformationAsync("08749409"); + + result.Data.ShouldNotBeNull(); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs index 6669c38..33a66a2 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs @@ -1,23 +1,22 @@ -using System; using System.Threading.Tasks; using CompaniesHouse.Response.CompanyProfile; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyProfileTests { - [TestFixture] - public abstract class CompanyProfileTestsBase + public abstract class CompanyProfileTestsBase : IAsyncLifetime { - protected CompaniesHouseClient _client; - protected CompaniesHouseClientResponse _result; + protected CompaniesHouseClient _client = null!; + protected CompaniesHouseClientResponse _result = null!; - [SetUp] - public void Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - When(); + await When(); } + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() @@ -26,4 +25,4 @@ private void GivenACompaniesHouseClient() _client = new CompaniesHouseClient(settings); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs index 1109de1..c2f1802 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs @@ -1,30 +1,31 @@ -using System.Threading.Tasks; -using NUnit.Framework; +using System.Threading.Tasks; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyProfileTests { - [TestFixture] + public class CompanyProfileTestsInvalid : CompanyProfileTestsBase { private const string InvalidCompanyNumber = "ABC00000"; - [SetUp] + protected override async Task When() { await WhenRetrievingAnInvalidCompanyProfile() - .ConfigureAwait(false); + ; } - [Test] + [Fact] public void ThenTheProfileIsNotReturned() { - Assert.That(_result.Data, Is.Null); + _result.Data.ShouldBeNull(); } private async Task WhenRetrievingAnInvalidCompanyProfile() { _result = await _client.GetCompanyProfileAsync(InvalidCompanyNumber) - .ConfigureAwait(false); + ; } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs index a12641d..2b31037 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs @@ -1,31 +1,32 @@ -using System.Threading.Tasks; -using NUnit.Framework; +using System.Threading.Tasks; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyProfileTests { - [TestFixture] + public class CompanyProfileTestsValid : CompanyProfileTestsBase { // Google UK company number, unlikely to go away soon private const string ValidCompanyNumber = "03977902"; - [SetUp] + protected override async Task When() { await WhenRetrievingAValidCompanyProfile() - .ConfigureAwait(false); + ; } - [Test] + [Fact] public void ThenTheProfileIsReturned() { - Assert.That(_result.Data.CompanyName, Is.Not.Empty); + _result.Data.CompanyName.ShouldNotBeEmpty(); } private async Task WhenRetrievingAValidCompanyProfile() { _result = await _client.GetCompanyProfileAsync(ValidCompanyNumber) - .ConfigureAwait(false); + ; } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs index 0b7eeda..3bf926c 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs @@ -1,44 +1,46 @@ -using System.IO; +using System.IO; using System.Threading.Tasks; using CompaniesHouse.Response.Document; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.DocumentTests { - [TestFixture] + public class DocumentDownloadTests : DocumentTestBase { private const string DocumentId = "Mw2JX3NUZqy8_TwPkbHJSsZH1Xz-MygUbnurqpZZwvU"; - private CompaniesHouseClientResponse _result; + private CompaniesHouseClientResponse _result = null!; - [SetUp] + protected override async Task When() => await DownloadingDocument(); private async Task DownloadingDocument() => _result = await Client.DownloadDocumentAsync(DocumentId); - [Test] + [Fact] public async Task ThenDocumentContentIsNotEmpty() { using var memoryStream = new MemoryStream(); await _result.Data.Content.CopyToAsync(memoryStream); - Assert.AreEqual(_result.Data.ContentLength, memoryStream.Length); - Assert.That(_result.Data.ContentType, Is.Not.Null.Or.Not.Empty); + _result.Data.ContentLength.ShouldNotBeNull(); + _result.Data.ContentLength.Value.ShouldBe(memoryStream.Length); + _result.Data.ContentType.ShouldNotBeNullOrEmpty(); } } - [TestFixture] + public class DocumentDownloadTestsInvalid : DocumentTestBase { private const string DocumentId = "000000000000000000000000000000"; - private CompaniesHouseClientResponse _result; + private CompaniesHouseClientResponse _result = null!; - [SetUp] + protected override async Task When() => await DownloadingDocument(); private async Task DownloadingDocument() => _result = await Client.DownloadDocumentAsync(DocumentId); - [Test] - public void ThenDocumentDataIsNull() => Assert.Null(_result.Data); + [Fact] + public void ThenDocumentDataIsNull() => _result.Data.ShouldBeNull(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs index d9841d4..d6e5dff 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs @@ -1,21 +1,22 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Document; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.DocumentTests { - [TestFixture] + public class DocumentTestsInvalid : DocumentTestBase { private const string DocumentId = "0000000000000000-00000000000000"; - [SetUp] - protected override async Task When() => await RetrievingDocumentMetadata().ConfigureAwait(false); + + protected override async Task When() => await RetrievingDocumentMetadata(); private async Task RetrievingDocumentMetadata() - => Result = await Client.GetDocumentMetadataAsync(DocumentId).ConfigureAwait(false); + => Result = await Client.GetDocumentMetadataAsync(DocumentId); - [Test] - public void ThenDocumentMetadataIsNull() => Assert.Null(Result.Data); + [Fact] + public void ThenDocumentMetadataIsNull() => Result.Data.ShouldBeNull(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs index a715943..7a85cb3 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs @@ -1,24 +1,26 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Document; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.DocumentTests { - [TestFixture] + public class DocumentTestsValid : DocumentTestBase { private const string DocumentId = "FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4"; - [SetUp] - protected override async Task When() => await RetrievingDocumentMetadata().ConfigureAwait(false); + + protected override async Task When() => await RetrievingDocumentMetadata(); - private async Task RetrievingDocumentMetadata() => Result = await Client.GetDocumentMetadataAsync(DocumentId).ConfigureAwait(false); + private async Task RetrievingDocumentMetadata() => Result = await Client.GetDocumentMetadataAsync(DocumentId); - [Test] + [Fact] public void ThenDocumentMetadataAreNotEmpty() { - Assert.That(Result.Data.CompanyNumber, Is.Not.Null.Or.Empty); - Assert.That(Result.Data.Resources, Is.Not.Null.Or.Empty); + Result.Data.CompanyNumber.ShouldNotBeNullOrEmpty(); + Result.Data.Resources.ShouldNotBeNull(); + Result.Data.Resources.ShouldNotBeEmpty(); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs index 6aaca7c..f9b944d 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs @@ -1,20 +1,21 @@ -using System.Threading.Tasks; -using NUnit.Framework; +using System.Threading.Tasks; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.DocumentTests { - public abstract class DocumentTestBase + public abstract class DocumentTestBase : IAsyncLifetime { - protected CompaniesHouseDocumentClient Client; - protected CompaniesHouseClientResponse Result; + protected CompaniesHouseDocumentClient Client = null!; + protected CompaniesHouseClientResponse Result = null!; - [SetUp] - public async Task Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - await When().ConfigureAwait(false); + await When(); } + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs index 0af2a98..874ca18 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs @@ -1,10 +1,11 @@ using System.Threading.Tasks; using CompaniesHouse.Response.Officers; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests { - [TestFixture] + public class OfficerByAppointmentTestsValid : OfficersTestBase { // Google UK company number, unlikely to go away soon @@ -13,19 +14,19 @@ public class OfficerByAppointmentTestsValid : OfficersTestBase //Sergey Brin's appointment private const string AppointmentId = "UmNUS-JYLQPmuNSz-DgKNbA2v7c"; - [SetUp] + protected override async Task When() => await WhenRetrievingAnCompanyFilingHistoryForAValidCompany() - .ConfigureAwait(false); + ; private async Task WhenRetrievingAnCompanyFilingHistoryForAValidCompany() => Result = await Client .GetOfficerByAppointmentIdAsync(ValidCompanyNumber, AppointmentId) - .ConfigureAwait(false); + ; - [Test] + [Fact] public void ThenTheDataIsNotNull() => - Assert.That(Result.Data, Is.Not.Null); + Result.Data.ShouldNotBeNull(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs index 640763c..a009e6b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs @@ -1,20 +1,21 @@ -using System.Threading.Tasks; -using NUnit.Framework; +using System.Threading.Tasks; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests { - public abstract class OfficersTestBase + public abstract class OfficersTestBase : IAsyncLifetime { - protected CompaniesHouseClient Client; - protected CompaniesHouseClientResponse Result; + protected CompaniesHouseClient Client = null!; + protected CompaniesHouseClientResponse Result = null!; - [SetUp] - public void Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - When(); + await When(); } + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() @@ -23,4 +24,4 @@ private void GivenACompaniesHouseClient() Client = new CompaniesHouseClient(settings); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs index ae86980..9289cdc 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs @@ -1,29 +1,30 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Officers; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests { - [TestFixture] + public class OfficersTestsInvalid : OfficersTestBase { private const string InvalidCompanyNumber = "ABC00000"; - [SetUp] + protected override async Task When() { - await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany().ConfigureAwait(false); + await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany(); } - [Test] + [Fact] public void ThenTheDataItemsAreNull() { - Assert.That(Result.Data, Is.Null); + Result.Data.ShouldBeNull(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() { - Result = await Client.GetOfficersAsync(InvalidCompanyNumber).ConfigureAwait(false); + Result = await Client.GetOfficersAsync(InvalidCompanyNumber); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs index b53d766..2e4d7b8 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs @@ -1,32 +1,33 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.Officers; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests { - [TestFixture] + public class OfficersTestsValid : OfficersTestBase { // Google UK company number, unlikely to go away soon private const string ValidCompanyNumber = "03977902"; - [SetUp] + protected override async Task When() { await WhenRetrievingAnCompanyFilingHistoryForAValidCompany() - .ConfigureAwait(false); + ; } - [Test] + [Fact] public void ThenTheDataItemsAreNotEmpty() { - Assert.That(Result.Data.Items, Is.Not.Empty); + Result.Data.Items.ShouldNotBeEmpty(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAValidCompany() { Result = await Client.GetOfficersAsync(ValidCompanyNumber) - .ConfigureAwait(false); + ; } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs index b85d617..4ddd89b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs @@ -1,21 +1,22 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Response.PersonsWithSignificantControl; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.PersonsWithSignificantControlTests { - public abstract class PersonsWithSignificantControlTestBase + public abstract class PersonsWithSignificantControlTestBase : IAsyncLifetime { - protected CompaniesHouseClient _client; - protected CompaniesHouseClientResponse _result; + protected CompaniesHouseClient _client = null!; + protected CompaniesHouseClientResponse _result = null!; - [SetUp] - public void Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - When(); + await When(); } + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() @@ -24,4 +25,4 @@ private void GivenACompaniesHouseClient() _client = new CompaniesHouseClient(settings); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs index 1e6fc87..6450521 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs @@ -1,30 +1,29 @@ -using System.Threading.Tasks; -using NUnit.Framework; - +using System.Threading.Tasks; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.PersonsWithSignificantControlTests { - [TestFixture] + public class PersonsWithSignificantControlTestsInValid : PersonsWithSignificantControlTestBase { private const string InvalidCompanyNumber = "ABC00000"; - [SetUp] + protected override async Task When() { - await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany().ConfigureAwait(false); + await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany(); } - - [Test] + [Fact] public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data, Is.Null); + _result.Data.ShouldBeNull(); } private async Task WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany() { - _result = await _client.GetPersonsWithSignificantControlAsync(InvalidCompanyNumber).ConfigureAwait(false); + _result = await _client.GetPersonsWithSignificantControlAsync(InvalidCompanyNumber); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs index b7c634b..07f3c47 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs @@ -1,30 +1,30 @@ -using System.Threading.Tasks; -using NUnit.Framework; - +using System.Threading.Tasks; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.PersonsWithSignificantControlTests { - [TestFixture] + public class PersonsWithSignificantControlTestsValid : PersonsWithSignificantControlTestBase { // Google UK company number, unlikely to go away soon private const string ValidCompanyNumber = "03977902"; - [SetUp] + protected override async Task When() { - await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnValidCompany().ConfigureAwait(false); + await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnValidCompany(); } - [Test] + [Fact] public void ThenTheDataItemsAreNotEmpty() { - Assert.That(_result.Data.Items, Is.Not.Empty); + _result.Data.Items.ShouldNotBeEmpty(); } private async Task WhenRetrievingAnCompanyPersonsWithSignificantControlForAnValidCompany() { - _result = await _client.GetPersonsWithSignificantControlAsync(ValidCompanyNumber).ConfigureAwait(false); + _result = await _client.GetPersonsWithSignificantControlAsync(ValidCompanyNumber); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs index 2ddced4..afc4536 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs @@ -1,20 +1,22 @@ using System.Threading.Tasks; using CompaniesHouse.Response.RegisteredOfficeAddress; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.RegisteredOfficeAddress { - public abstract class RegisteredOfficeAddressTestBase + public abstract class RegisteredOfficeAddressTestBase : IAsyncLifetime { - protected CompaniesHouseClient Client { get; set; } - protected CompaniesHouseClientResponse Result; + protected CompaniesHouseClient Client { get; set; } = null!; + protected CompaniesHouseClientResponse Result = null!; - [SetUp] - public async Task Setup() + public async Task InitializeAsync() { GivenACompaniesHouseClient(); - await When().ConfigureAwait(false); + await When(); } + + public Task DisposeAsync() => Task.CompletedTask; + protected abstract Task When(); private void GivenACompaniesHouseClient() diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs index b35e94e..9ba2a98 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs @@ -1,16 +1,17 @@ using System.Threading.Tasks; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.RegisteredOfficeAddress { - [TestFixture] + public class RegisteredOfficeAddressesTestsValid : RegisteredOfficeAddressTestBase { private const string CompanyNumber = "03977902"; protected override async Task When() => Result = await Client.GetRegisteredOfficeAddress(CompanyNumber); - [Test] - public void ThenRegisteredOfficeAddressIsNotNull() => Assert.NotNull(Result.Data); + [Fact] + public void ThenRegisteredOfficeAddressIsNotNull() => Result.Data.ShouldNotBeNull(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs index 61b8eda..ee591fb 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs @@ -1,42 +1,28 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.AllSearch; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests { - [TestFixture("British Gas")] - [TestFixture("Kevin")] public class AllSearchTests { - private readonly string _query; - private CompaniesHouseClient _client; - private CompaniesHouseClientResponse _result; + private readonly CompaniesHouseClient _client; - public AllSearchTests(string query) + public AllSearchTests() { - _query = query; + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [OneTimeSetUp] - public void GivenACompaniesHouseClient() + [Theory] + [InlineData("British Gas")] + [InlineData("Kevin")] + public async Task ThenItemsAreReturned(string query) { - var settings = new CompaniesHouseSettings(Keys.ApiKey); + var result = await _client.SearchAllAsync(new SearchAllRequest { Query = query }); - _client = new CompaniesHouseClient(settings); - } - - [SetUp] - public async Task WhenSearching() - { - _result = await _client.SearchAllAsync(new SearchAllRequest() { Query = _query }) - .ConfigureAwait(false); - } - - [Test] - public void ThenItemsAreReturned() - { - Assert.That(_result.Data.Items, Is.Not.Empty); + result.Data.Items.ShouldNotBeEmpty(); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs index ec67a8d..6d0192f 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs @@ -1,45 +1,29 @@ -using System; using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.CompanySearch; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests { - [TestFixture("brighouse computers")] - [TestFixture("British Gas")] - [TestFixture("Bay Horse")] - public class CompanySearchTests { - private readonly string _query; - private CompaniesHouseClient _client; - private CompaniesHouseClientResponse _result; + private readonly CompaniesHouseClient _client; - public CompanySearchTests(string query) + public CompanySearchTests() { - _query = query; + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [OneTimeSetUp] - public void GivenACompaniesHouseClient() + [Theory] + [InlineData("brighouse computers")] + [InlineData("British Gas")] + [InlineData("Bay Horse")] + public async Task ThenCompaniesAreReturned(string query) { - var settings = new CompaniesHouseSettings(Keys.ApiKey); - - _client = new CompaniesHouseClient(settings); - } + var result = await _client.SearchCompanyAsync(new SearchCompanyRequest { Query = query, StartIndex = 0, ItemsPerPage = 100 }); - [SetUp] - public async Task WhenSearchingForACompany() - { - _result = await _client.SearchCompanyAsync(new SearchCompanyRequest() { Query = _query, StartIndex = 0, ItemsPerPage = 100 }) - .ConfigureAwait(false); - } - - [Test] - public void ThenCompaniesAreReturned() - { - Assert.That(_result.Data.Companies, Is.Not.Empty); + result.Data.Companies.ShouldNotBeEmpty(); } } -} +} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs index 2481ffb..838da72 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs @@ -1,35 +1,26 @@ using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests { - [TestFixture] public class DisqualifiedOfficersSearchTests { - private CompaniesHouseClient _client; - private CompaniesHouseClientResponse _result; - - [OneTimeSetUp] - public void GivenACompaniesHouseClient() - { - var settings = new CompaniesHouseSettings(Keys.ApiKey); - - _client = new CompaniesHouseClient(settings); - } + private readonly CompaniesHouseClient _client; - [SetUp] - public async Task WhenSearchingForADisqualifiedOfficers() + public DisqualifiedOfficersSearchTests() { - _result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest() { Query = "Kevin" }) - .ConfigureAwait(false); + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Test] - public void ThenDisqualifiedOfficersAreReturned() + [Fact] + public async Task ThenDisqualifiedOfficersAreReturned() { - Assert.That(_result.Data.DisqualifiedOfficers, Is.Not.Empty); + var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "Kevin" }); + + result.Data.DisqualifiedOfficers.ShouldNotBeEmpty(); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs index 68d7a8c..740e52c 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs @@ -1,36 +1,26 @@ -using System; using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.OfficerSearch; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests { - [TestFixture] public class OfficersSearchTests { - private CompaniesHouseClient _client; - private CompaniesHouseClientResponse _result; - - [OneTimeSetUp] - public void GivenACompaniesHouseClient() - { - var settings = new CompaniesHouseSettings(Keys.ApiKey); - - _client = new CompaniesHouseClient(settings); - } + private readonly CompaniesHouseClient _client; - [SetUp] - public async Task WhenSearchingForAOfficer() + public OfficersSearchTests() { - _result = await _client.SearchOfficerAsync(new SearchOfficerRequest() { Query = "Kevin" }) - .ConfigureAwait(false); + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Test] - public void ThenOfficersAreReturned() + [Fact] + public async Task ThenOfficersAreReturned() { - Assert.That(_result.Data.Officers, Is.Not.Empty); + var result = await _client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Kevin" }); + + result.Data.Officers.ShouldNotBeEmpty(); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.ScenarioTests/CompaniesHouse.ScenarioTests.csproj b/tests/CompaniesHouse.ScenarioTests/CompaniesHouse.ScenarioTests.csproj index b2fec0e..4f85ee5 100644 --- a/tests/CompaniesHouse.ScenarioTests/CompaniesHouse.ScenarioTests.csproj +++ b/tests/CompaniesHouse.ScenarioTests/CompaniesHouse.ScenarioTests.csproj @@ -1,17 +1,18 @@ - net7.0 + net10.0 false latest - - - - - + + + + + + diff --git a/tests/CompaniesHouse.ScenarioTests/Keys.cs b/tests/CompaniesHouse.ScenarioTests/Keys.cs index 249ca9f..f50b988 100644 --- a/tests/CompaniesHouse.ScenarioTests/Keys.cs +++ b/tests/CompaniesHouse.ScenarioTests/Keys.cs @@ -4,6 +4,6 @@ namespace CompaniesHouse.ScenarioTests { public static class Keys { - public static string ApiKey { get; } = Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY"); + public static string ApiKey { get; } = Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY")!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs index a6bc44d..41c2bb5 100644 --- a/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs @@ -1,7 +1,8 @@ using System.Linq; using System.Threading.Tasks; using CompaniesHouse.Request; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.ScenarioTests { @@ -9,33 +10,29 @@ public class SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests { private ICompaniesHouseClient _client; - [SetUp] - public void Setup() + public SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests() { var settings = new CompaniesHouseSettings(Keys.ApiKey); _client = new CompaniesHouseClient(settings); } - [Test] + [Fact] public async Task RunScenario() { - var officersSearch = await _client.SearchOfficerAsync(new SearchOfficerRequest() {Query = "Richard Branson" }) - .ConfigureAwait(false); + var officersSearch = await _client.SearchOfficerAsync(new SearchOfficerRequest() { Query = "Richard Branson" }); var foundOfficer = officersSearch.Data.Officers.Single(x => x.DateOfBirth?.Year == 1950 && x.DateOfBirth?.Month == 7); - var officerAppointments = await _client.GetAppointmentsAsync(foundOfficer.OfficerId) - .ConfigureAwait(false); + var officerAppointments = await _client.GetAppointmentsAsync(foundOfficer.OfficerId); var companyNumber = officerAppointments.Data.Items .Single(x => x.Appointed.CompanyName == "VIRGIN LIMITED") .Appointed.CompanyNumber; - var companyProfile = await _client.GetCompanyProfileAsync(companyNumber) - .ConfigureAwait(false); + var companyProfile = await _client.GetCompanyProfileAsync(companyNumber); - Assert.NotNull(companyProfile.Data); - Assert.AreEqual("01946167", companyProfile.Data.CompanyNumber); + companyProfile.Data.ShouldNotBeNull(); + companyProfile.Data.CompanyNumber.ShouldBe("01946167"); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.ScenarioTests/UsingMicrosoftServiceContainerTests.cs b/tests/CompaniesHouse.ScenarioTests/UsingMicrosoftServiceContainerTests.cs index 188e6ea..508c428 100644 --- a/tests/CompaniesHouse.ScenarioTests/UsingMicrosoftServiceContainerTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/UsingMicrosoftServiceContainerTests.cs @@ -1,14 +1,14 @@ using System.Threading.Tasks; using CompaniesHouse.Request; using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.ScenarioTests { public class SearchingCompaniesUsingMicrosoftServiceContainerTests { - [Test] + [Fact] public async Task CanResolveCompaniesHouseClients() { var serviceCollection = new ServiceCollection(); @@ -17,11 +17,11 @@ public async Task CanResolveCompaniesHouseClients() var serviceProvider = serviceCollection.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); - var client = scope.ServiceProvider.GetService(); + var client = scope.ServiceProvider.GetRequiredService(); var response = await client.SearchCompanyAsync(new SearchCompanyRequest {Query = "Boon & Moil"}); - Assert.IsNotEmpty(response.Data.Companies); + response.Data.Companies.ShouldNotBeEmpty(); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj b/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj index d0245ba..2b685ac 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj +++ b/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj @@ -1,31 +1,21 @@  - net7.0 + net10.0 false - - - - - - 4.0.0 - - - 4.17.0 - - - - 13.0.2 - - - - + + + + + + + \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs index b718c57..1b3f56e 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs @@ -4,16 +4,16 @@ using System.Threading.Tasks; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseChargesClientTests { - [TestFixture] public class CompaniesHouseChargesClientTests { - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyCharges(CompaniesHouseChargesClientTestCase testCase) { var charges = CompanyChargesBuilder.Create(testCase); @@ -26,10 +26,15 @@ public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyCharges(Co var result = await client.GetChargesListAsync("1", 0, 25); - result.Data.ShouldBeEquivalentTo(charges); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, charges, "TransactionId"); + foreach (var (actual, expected) in result.Data.Items.Zip(charges.Items)) + { + actual.InsolvencyCases.Select(x => x.TransactionId).ShouldBe(expected.InsolvencyCases.Select(x => (long?)x.TransactionId)); + } } - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyChargeById(CompaniesHouseChargesClientTestCase testCase) { var charge = CompanyChargesBuilder.CreateOne(testCase); @@ -42,10 +47,11 @@ public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyChargeById var result = await client.GetChargeByIdAsync("1", "1"); - result.Data.ShouldBeEquivalentTo(charge); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, charge, "TransactionId"); + result.Data.InsolvencyCases.Select(x => x.TransactionId).ShouldBe(charge.InsolvencyCases.Select(x => (long?)x.TransactionId)); } - private static CompaniesHouseChargesClientTestCase[] TestCases() + public static IEnumerable TestCases() { var allAssetsCeasedReleased = EnumerationMappings.PossibleAssetsCeasedReleased.Keys.Select(x => new CompaniesHouseChargesClientTestCase { @@ -97,7 +103,7 @@ private static CompaniesHouseChargesClientTestCase[] TestCases() .Concat(allSecuredDetailTypes) .Concat(allClassificationChargeTypes) .Concat(allChargeStatuses) - .ToArray(); + .Select(testCase => new object[] { testCase }); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs index eb0b133..36d6e32 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs @@ -1,19 +1,20 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseCompanyFilingHistoryClientTests { - [TestFixture] public class CompaniesHouseCompanyFilingHistoryClientTests { - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile(CompaniesHouseCompanyFilingHistoryClientTestCase testCase) { var companyFilingHistory = CompanyFilingHistoryBuilder.Build(testCase); @@ -32,10 +33,11 @@ public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyPr var result = await client.GetCompanyFilingHistoryAsync("abc", 0, 25); - result.Data.ShouldBeEquivalentTo(companyFilingHistory); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, companyFilingHistory); } - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task GivenACompaniesHouseCompanyFilingHistoryClient_WhenGettingAFilingHistoryItem(CompaniesHouseCompanyFilingHistoryClientTestCase testCase) { var filingHistory = CompanyFilingHistoryBuilder.BuildOne(testCase); @@ -52,10 +54,10 @@ public async Task GivenACompaniesHouseCompanyFilingHistoryClient_WhenGettingAFil var result = await client.GetFilingHistoryByTransactionAsync("abc", "id1"); - result.Data.ShouldBeEquivalentTo(filingHistory); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, filingHistory); } - public static CompaniesHouseCompanyFilingHistoryClientTestCase[] TestCases() + public static IEnumerable TestCases() { var allFilingCategories = EnumerationMappings.PossibleFilingCategories.Keys .Select(x => new CompaniesHouseCompanyFilingHistoryClientTestCase @@ -97,7 +99,7 @@ public static CompaniesHouseCompanyFilingHistoryClientTestCase[] TestCases() .Concat(allFilingSubcategories) .Concat(allFilingHistoryStatus) .Concat(allFilingResolutionCategories) - .ToArray(); + .Select(testCase => new object[] { testCase }); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs index 080fc4b..19af8ec 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs @@ -1,16 +1,17 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Threading.Tasks; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; using CompanyProfile = CompaniesHouse.Response.CompanyProfile.CompanyProfile; namespace CompaniesHouse.Tests.CompaniesHouseCompanyProfileClientTests { - [TestFixture] public class CompaniesHouseCompanyProfileClientTests { private CompaniesHouseCompanyProfileClient _client; @@ -18,8 +19,9 @@ public class CompaniesHouseCompanyProfileClientTests private CompaniesHouseClientResponse _result; private ResourceBuilders.CompanyProfile _companyProfile; - [TestCaseSource(nameof(TestCases))] - public void GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile(CompaniesHouseCompanyProfileClientTestCase testCase) + [Theory] + [MemberData(nameof(TestCases))] + public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile(CompaniesHouseCompanyProfileClientTestCase testCase) { _companyProfile = new CompanyProfileBuilder().Build(testCase); var resource = new CompanyProfileResourceBuilder(_companyProfile) @@ -35,13 +37,13 @@ public void GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile( _client = new CompaniesHouseCompanyProfileClient(new HttpClient(handler), uriBuilder.Object); - _result = _client.GetCompanyProfileAsync("abc").Result; + _result = await _client.GetCompanyProfileAsync("abc"); - _result.Data.ShouldBeEquivalentTo(_companyProfile); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _companyProfile); } - public static CompaniesHouseCompanyProfileClientTestCase[] TestCases() + public static IEnumerable TestCases() { var allLastAccountsTypes = EnumerationMappings.PossibleLastAccountsTypes.Keys .Select(x => new CompaniesHouseCompanyProfileClientTestCase @@ -97,7 +99,7 @@ public static CompaniesHouseCompanyProfileClientTestCase[] TestCases() .Concat(allCompanyStatusDetails) .Concat(allJurisdictions) .Concat(allCompanyTypes) - .ToArray(); + .Select(testCase => new object[] { testCase }); } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs index 1546c58..f54681e 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs @@ -4,22 +4,20 @@ using System.Threading.Tasks; using CompaniesHouse.Response.Document; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseDocumentClientTests { - [TestFixture] - public class CompaniesHouseDocumentClientTests + public class CompaniesHouseDocumentClientTests : IAsyncLifetime { private CompaniesHouseClientResponse _result; private const string ExpectedMediaType = "application/pdf"; private const string ExpectedContent = "test pdf"; private const string DocumentId = "wibble"; - [SetUp] - public async Task GivenAClient_WhenDownloadingDocument() + public async Task InitializeAsync() { var requestUri = new Uri($"https://document-api.companieshouse.gov.uk/document/{DocumentId}/content"); var stubHttpMessageHandler = new StubHttpMessageHandler(requestUri, ExpectedContent, ExpectedMediaType); @@ -29,14 +27,16 @@ public async Task GivenAClient_WhenDownloadingDocument() _result = await new CompaniesHouseDocumentDownloadClient(new HttpClient(stubHttpMessageHandler), mockUriBuilder.Object).DownloadDocumentAsync(DocumentId); } - [Test] - public void ThenDocumentContentIsCorrect() + public Task DisposeAsync() => Task.CompletedTask; + + [Fact] + public async Task ThenDocumentContentIsCorrect() { using var memoryStream = new MemoryStream(); - _result.Data.Content.CopyToAsync(memoryStream); + await _result.Data.Content.CopyToAsync(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); - new StreamReader(memoryStream).ReadToEnd().Should().Be(ExpectedContent); + new StreamReader(memoryStream).ReadToEnd().ShouldBe(ExpectedContent); } } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs index 79c00e4..380ea5a 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs @@ -3,21 +3,19 @@ using System.Net.Http; using CompaniesHouse.Response.Document; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseDocumentMetadataClientTests { - [TestFixture] public class CompaniesHouseDocumentMetadataClientTests { private const string DocumentId = "wibble"; private DocumentMetadataTestCase _expected; private CompaniesHouseClientResponse _result; - [SetUp] - public void GivenAClient_WhenGettingDocumentMetadata() + public CompaniesHouseDocumentMetadataClientTests() { _expected = SetupExpectedDocumentMetadata(); var requestUri = new Uri($"https://document-api.companieshouse.gov.uk/document/{DocumentId}"); @@ -28,8 +26,12 @@ public void GivenAClient_WhenGettingDocumentMetadata() .GetDocumentMetadataAsync(DocumentId).Result; } - [Test] - public void ThenDocumentMetadataIsCorrect() => _result.Data.ShouldBeEquivalentTo(_expected); + [Fact] + public void ThenDocumentMetadataIsCorrect() + { + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _expected, nameof(DocumentMetadata.CreatedAt)); + _result.Data.CreatedAt.ShouldBe(_expected.CreatedAt.ToString("O")); + } private static Mock SetupRequestUri(Uri catchUri) { diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs index 2104d5e..18b6f79 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs @@ -1,20 +1,21 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseOfficersAppointmentClientTests { - [TestFixture] public class CompaniesHouseOfficersAppointmentClientTests { - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task GivenACompaniesHouseOffficerAppointmentClient_WhenGettingAnOfficerByAppointmentId(CompaniesHouseOfficerByAppointmentTestCase testCase) { var officersAppointment = OfficerBuilder.Build(testCase); @@ -30,15 +31,16 @@ public async Task GivenACompaniesHouseOffficerAppointmentClient_WhenGettingAnOff var client = new CompaniesHouseOfficerByByAppointmentClient(new HttpClient(handler), uriBuilder.Object); var result = await client.GetOfficerByAppointmentIdAsync("abc", "1"); - result.Data.ShouldBeEquivalentTo(officersAppointment); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, officersAppointment); } - public static CompaniesHouseOfficerByAppointmentTestCase[] TestCases() => + public static IEnumerable TestCases() => EnumerationMappings.PossibleOfficerRoles.Keys .Select(x => new CompaniesHouseOfficerByAppointmentTestCase { OfficerRole = x - }).ToArray(); + }) + .Select(testCase => new object[] { testCase }); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs index f9718bd..97c526c 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs @@ -1,15 +1,15 @@ using System; using System.Net.Http; +using System.Threading.Tasks; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; using Officers = CompaniesHouse.Response.Officers.Officers; namespace CompaniesHouse.Tests.CompaniesHouseOfficersTests { - [TestFixture] public class CompaniesHouseCompanyOfficersClientTests { private CompaniesHouseOfficersClient _client; @@ -17,8 +17,8 @@ public class CompaniesHouseCompanyOfficersClientTests private CompaniesHouseClientResponse _result; private ResourceBuilders.Officers _officers; - [Test] - public void GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile() + [Fact] + public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile() { _officers = new OfficersBuilder().Build(); var resource = new OfficersResourceBuilder(_officers).Create(); @@ -33,9 +33,9 @@ public void GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile( _client = new CompaniesHouseOfficersClient(new HttpClient(handler), uriBuilder.Object); - _result = _client.GetOfficersAsync("abc", 0, 25).Result; + _result = await _client.GetOfficersAsync("abc", 0, 25); - _result.Data.ShouldBeEquivalentTo(_officers); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _officers); } } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs index daae6c7..068d5f2 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs @@ -1,15 +1,15 @@ using System; using System.Net.Http; +using System.Threading.Tasks; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; using PersonsWithSignificantControl = CompaniesHouse.Response.PersonsWithSignificantControl.PersonsWithSignificantControl; namespace CompaniesHouse.Tests.CompaniesHousePersonsWithSignificantControlTests { - [TestFixture] public class CompaniesHousePersonsWithSignificantControlTests { private CompaniesHousePersonsWithSignificantControlClient _client; @@ -17,8 +17,8 @@ public class CompaniesHousePersonsWithSignificantControlTests private CompaniesHouseClientResponse _result; private ResourceBuilders.PersonsWithSignificantControl _personsWithSignificantControl; - [Test] - public void GivenACompaniesHouseCompanyProfileClient_WhenGettingPersonsWithSignificantControl() + [Fact] + public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingPersonsWithSignificantControl() { _personsWithSignificantControl = new PersonsWithSignificantControlBuilder().Build(); var resource = new PersonsWithSignificantControlResourceBuilder(_personsWithSignificantControl).Create(); @@ -33,9 +33,9 @@ public void GivenACompaniesHouseCompanyProfileClient_WhenGettingPersonsWithSigni _client = new CompaniesHousePersonsWithSignificantControlClient(new HttpClient(handler), uriBuilder.Object); - _result = _client.GetPersonsWithSignificantControlAsync("abc", 0, 25).Result; + _result = await _client.GetPersonsWithSignificantControlAsync("abc", 0, 25); - _result.Data.ShouldBeEquivalentTo(_personsWithSignificantControl); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _personsWithSignificantControl); } } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs index 99ef9ae..d80a1b7 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs @@ -1,20 +1,21 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Runtime.Serialization; using System.Threading.Tasks; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseRegisteredOfficeAddressTests { - [TestFixture] public class CompaniesHouseRegisteredOfficeAddressTests { - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task GivenACompaniesHouseRegistereOfficeAddressClient_WhenGettingARegisteredOfficeAddress(CompaniesHouseRegisteredOfficeAddressTestCase testCase) { var registeredOfficeAddress = RegisteredOfficeAddressBuilder.Build(testCase); @@ -31,18 +32,18 @@ public async Task GivenACompaniesHouseRegistereOfficeAddressClient_WhenGettingAR var result = await client.GetRegisteredOfficeAddress("abc"); - result.Data.ShouldBeEquivalentTo(registeredOfficeAddress, opt => opt.Excluding(x => x.Country)); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, registeredOfficeAddress, nameof(RegisteredOfficeAddress.Country)); - result.Data.Country.GetEnumMemberValue().Should().Be(registeredOfficeAddress.Country); + result.Data.Country.GetEnumMemberValue().ShouldBe(registeredOfficeAddress.Country); } - public static CompaniesHouseRegisteredOfficeAddressTestCase[] TestCases() => + public static IEnumerable TestCases() => EnumerationMappings.PossibleRegisteredOfficeAddressCountry.Keys .Select(x => new CompaniesHouseRegisteredOfficeAddressTestCase { Country = x }) - .ToArray(); + .Select(testCase => new object[] { testCase }); } internal static class EnumExtensions diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs index 5c456a1..2d85b34 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs @@ -3,11 +3,11 @@ using CompaniesHouse.Response; using CompaniesHouse.Response.Search.CompanySearch; using CompaniesHouse.Tests.ResourceBuilders.CompanySearchResource; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests { - [TestFixture] public class CompaniesHouseSearchClientTestsForCompanySearch { private CompaniesHouseSearchClient _client; @@ -17,8 +17,7 @@ public class CompaniesHouseSearchClientTestsForCompanySearch private List _expectedCompanies; - [OneTimeSetUp] - public void GivenACompanyHouseSearchCompanyClient_WhenSearchingForACompany() + public CompaniesHouseSearchClientTestsForCompanySearch() { var fixture = new Fixture(); _resourceDetails = fixture.Create(); @@ -46,7 +45,7 @@ public void GivenACompanyHouseSearchCompanyClient_WhenSearchingForACompany() .With(x => x.CompanyType, "private-unlimited").With(x => x.Kind, "searchresults#company").Create(), fixture.Build().With(x => x.CompanyStatus, "closed-on") .With(x => x.CompanyType, "private-unlimited").With(x => x.Kind, "searchresults#company").Create(), - fixture.Build().With(x => x.CompanyStatus, null) + fixture.Build().With(x => x.CompanyStatus, () => (string)null) .With(x => x.CompanyType, "private-unlimited").With(x => x.Kind, "searchresults#company").Create(), }; @@ -68,83 +67,80 @@ public void GivenACompanyHouseSearchCompanyClient_WhenSearchingForACompany() _result = _client.SearchAsync(new SearchCompanyRequest()).Result; } - [Test] + [Fact] public void ThenTheRootIsCorrect() { - Assert.That(_result.Data.ETag, Is.EqualTo(_resourceDetails.ETag)); - Assert.That(_result.Data.ItemsPerPage, Is.EqualTo(_resourceDetails.ItemsPerPage)); - Assert.That(_result.Data.Kind, Is.EqualTo(_resourceDetails.Kind)); - Assert.That(_result.Data.PageNumber, Is.EqualTo(_resourceDetails.PageNumber)); - Assert.That(_result.Data.StartIndex, Is.EqualTo(_resourceDetails.StartIndex)); - Assert.That(_result.Data.TotalResults, Is.EqualTo(_resourceDetails.TotalResults)); + _result.Data.ETag.ShouldBe(_resourceDetails.ETag); + _result.Data.ItemsPerPage.ShouldBe(_resourceDetails.ItemsPerPage); + _result.Data.Kind.ShouldBe(_resourceDetails.Kind); + _result.Data.PageNumber.ShouldBe(_resourceDetails.PageNumber); + _result.Data.StartIndex.ShouldBe(_resourceDetails.StartIndex); + _result.Data.TotalResults.ShouldBe(_resourceDetails.TotalResults); } - [Test] + [Fact] public void ThenTheCompanyWithUnknownDateOfCessationIsReturned() { var actual = _result.Data.Companies.First(x => x.CompanyNumber == _companyWithUnknownDateOfCessation.CompanyNumber); - Assert.That(actual.CompanyNumber, Is.EqualTo(_companyWithUnknownDateOfCessation.CompanyNumber)); - - Assert.That(actual.Address.AddressLine1, Is.EqualTo(_companyWithUnknownDateOfCessation.AddressLine1)); - Assert.That(actual.Address.AddressLine2, Is.EqualTo(_companyWithUnknownDateOfCessation.AddressLine2)); - Assert.That(actual.Address.CareOf, Is.EqualTo(_companyWithUnknownDateOfCessation.CareOf)); - Assert.That(actual.Address.Country, Is.EqualTo(_companyWithUnknownDateOfCessation.Country)); - Assert.That(actual.Address.Locality, Is.EqualTo(_companyWithUnknownDateOfCessation.Locality)); - Assert.That(actual.Address.PoBox, Is.EqualTo(_companyWithUnknownDateOfCessation.PoBox)); - Assert.That(actual.Address.PostalCode, Is.EqualTo(_companyWithUnknownDateOfCessation.PostalCode)); - Assert.That(actual.Address.Region, Is.EqualTo(_companyWithUnknownDateOfCessation.Region)); - - Assert.That(actual.CompanyStatus, - Is.EqualTo(ExpectedCompanyStatus[_companyWithUnknownDateOfCessation.CompanyStatus])); - Assert.That(actual.CompanyType, - Is.EqualTo(ExpectedCompanyType[_companyWithUnknownDateOfCessation.CompanyType])); - Assert.That(actual.DateOfCessation, Is.Null); - Assert.That(actual.DateOfCreation, Is.EqualTo(_companyWithUnknownDateOfCessation.DateOfCreation.Date)); - Assert.That(actual.Description, Is.EqualTo(_companyWithUnknownDateOfCessation.Description)); - Assert.That(actual.Kind, Is.EqualTo(_companyWithUnknownDateOfCessation.Kind)); - Assert.That(actual.Links.Self, Is.EqualTo(_companyWithUnknownDateOfCessation.LinksSelf)); - Assert.That(actual.Matches.Title, Is.EqualTo(_companyWithUnknownDateOfCessation.MatchesTitle)); - Assert.That(actual.Snippet, Is.EqualTo(_companyWithUnknownDateOfCessation.Snippet)); - Assert.That(actual.Title, Is.EqualTo(_companyWithUnknownDateOfCessation.Title)); + actual.CompanyNumber.ShouldBe(_companyWithUnknownDateOfCessation.CompanyNumber); + + actual.Address.AddressLine1.ShouldBe(_companyWithUnknownDateOfCessation.AddressLine1); + actual.Address.AddressLine2.ShouldBe(_companyWithUnknownDateOfCessation.AddressLine2); + actual.Address.CareOf.ShouldBe(_companyWithUnknownDateOfCessation.CareOf); + actual.Address.Country.ShouldBe(_companyWithUnknownDateOfCessation.Country); + actual.Address.Locality.ShouldBe(_companyWithUnknownDateOfCessation.Locality); + actual.Address.PoBox.ShouldBe(_companyWithUnknownDateOfCessation.PoBox); + actual.Address.PostalCode.ShouldBe(_companyWithUnknownDateOfCessation.PostalCode); + actual.Address.Region.ShouldBe(_companyWithUnknownDateOfCessation.Region); + + actual.CompanyStatus.ShouldBe(ExpectedCompanyStatus[_companyWithUnknownDateOfCessation.CompanyStatus]); + actual.CompanyType.ShouldBe(ExpectedCompanyType[_companyWithUnknownDateOfCessation.CompanyType]); + actual.DateOfCessation.ShouldBeNull(); + actual.DateOfCreation.ShouldBe(_companyWithUnknownDateOfCessation.DateOfCreation.Date); + actual.Description.ShouldBe(_companyWithUnknownDateOfCessation.Description); + actual.Kind.ShouldBe(_companyWithUnknownDateOfCessation.Kind); + actual.Links.Self.ShouldBe(_companyWithUnknownDateOfCessation.LinksSelf); + actual.Matches.Title.ShouldBe(_companyWithUnknownDateOfCessation.MatchesTitle); + actual.Snippet.ShouldBe(_companyWithUnknownDateOfCessation.Snippet); + actual.Title.ShouldBe(_companyWithUnknownDateOfCessation.Title); } - [Test] + [Fact] public void ThenTheNumberOfReturnedCompaniesIsCorrect() { - Assert.That(_result.Data.Companies.Length, Is.EqualTo(13)); + _result.Data.Companies.Length.ShouldBe(13); } - [Test] + [Fact] public void ThenTheCompaniesAreCorrect() { foreach (var companyDetails in _expectedCompanies) { var actual = _result.Data.Companies.First(x => x.CompanyNumber == companyDetails.CompanyNumber); - Assert.That(actual.CompanyNumber, Is.EqualTo(companyDetails.CompanyNumber)); - - Assert.That(actual.Address.AddressLine1, Is.EqualTo(companyDetails.AddressLine1)); - Assert.That(actual.Address.AddressLine2, Is.EqualTo(companyDetails.AddressLine2)); - Assert.That(actual.Address.CareOf, Is.EqualTo(companyDetails.CareOf)); - Assert.That(actual.Address.Country, Is.EqualTo(companyDetails.Country)); - Assert.That(actual.Address.Locality, Is.EqualTo(companyDetails.Locality)); - Assert.That(actual.Address.PoBox, Is.EqualTo(companyDetails.PoBox)); - Assert.That(actual.Address.PostalCode, Is.EqualTo(companyDetails.PostalCode)); - Assert.That(actual.Address.Region, Is.EqualTo(companyDetails.Region)); - - Assert.That(actual.CompanyStatus, - Is.EqualTo(ExpectedCompanyStatus[companyDetails.CompanyStatus ?? ""])); - Assert.That(actual.CompanyType, Is.EqualTo(ExpectedCompanyType[companyDetails.CompanyType])); - Assert.That(actual.DateOfCessation, Is.EqualTo(companyDetails.DateOfCessation.Date)); - Assert.That(actual.DateOfCreation, Is.EqualTo(companyDetails.DateOfCreation.Date)); - Assert.That(actual.Description, Is.EqualTo(companyDetails.Description)); - Assert.That(actual.Kind, Is.EqualTo(companyDetails.Kind)); - Assert.That(actual.Links.Self, Is.EqualTo(companyDetails.LinksSelf)); - Assert.That(actual.Matches.Title, Is.EqualTo(companyDetails.MatchesTitle)); - Assert.That(actual.Snippet, Is.EqualTo(companyDetails.Snippet)); - Assert.That(actual.Title, Is.EqualTo(companyDetails.Title)); + actual.CompanyNumber.ShouldBe(companyDetails.CompanyNumber); + + actual.Address.AddressLine1.ShouldBe(companyDetails.AddressLine1); + actual.Address.AddressLine2.ShouldBe(companyDetails.AddressLine2); + actual.Address.CareOf.ShouldBe(companyDetails.CareOf); + actual.Address.Country.ShouldBe(companyDetails.Country); + actual.Address.Locality.ShouldBe(companyDetails.Locality); + actual.Address.PoBox.ShouldBe(companyDetails.PoBox); + actual.Address.PostalCode.ShouldBe(companyDetails.PostalCode); + actual.Address.Region.ShouldBe(companyDetails.Region); + + actual.CompanyStatus.ShouldBe(ExpectedCompanyStatus[companyDetails.CompanyStatus ?? ""]); + actual.CompanyType.ShouldBe(ExpectedCompanyType[companyDetails.CompanyType]); + actual.DateOfCessation.ShouldBe(companyDetails.DateOfCessation.Date); + actual.DateOfCreation.ShouldBe(companyDetails.DateOfCreation.Date); + actual.Description.ShouldBe(companyDetails.Description); + actual.Kind.ShouldBe(companyDetails.Kind); + actual.Links.Self.ShouldBe(companyDetails.LinksSelf); + actual.Matches.Title.ShouldBe(companyDetails.MatchesTitle); + actual.Snippet.ShouldBe(companyDetails.Snippet); + actual.Title.ShouldBe(companyDetails.Title); } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs index c5e3ecf..999402b 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs @@ -4,19 +4,17 @@ using CompaniesHouse.Request; using CompaniesHouse.Response.Search.CompanySearch; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests { - [TestFixture] - public class CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests + public class CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests : IAsyncLifetime { private Exception _caughtException; - [OneTimeSetUp] - public async Task GivenACompanyHouseSearchCompanyClient_WhenSearchingForACompanyAndApiReturnsTooManyRequests() + public async Task InitializeAsync() { var uri = new Uri("https://wibble.com/search/companies"); @@ -37,12 +35,13 @@ public async Task GivenACompanyHouseSearchCompanyClient_WhenSearchingForACompany } } - [Test] + public Task DisposeAsync() => Task.CompletedTask; + + [Fact] public void ThenExceptionIsThrown() { - _caughtException.Should().BeOfType(); - - _caughtException.As().Message.Should().StartWith("Response status code does not indicate success: 429"); + var exception = _caughtException.ShouldBeOfType(); + exception.Message.ShouldStartWith("Response status code does not indicate success: 429"); } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs index f96a8ef..04cd8a2 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs @@ -1,28 +1,25 @@ -using System; +using System; using System.Linq; using System.Net.Http; -using System.Text.RegularExpressions; using System.Threading.Tasks; +using AutoFixture; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.OfficerSearch; using CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource; using CompaniesHouse.UriBuilders; -using FluentAssertions; using Moq; -using NUnit.Framework; -using AutoFixture; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests { - [TestFixture] - public class CompaniesHouseSearchClientTestsForOfficerSearch + public class CompaniesHouseSearchClientTestsForOfficerSearch : IAsyncLifetime { private CompaniesHouseSearchClient _client; private CompaniesHouseClientResponse _result; private ResourceDetails _resourceDetails; - [OneTimeSetUp] - public async Task GivenACompanyHouseSearchClient_WhenSearchingForAOfficer() + public async Task InitializeAsync() { var fixture = new Fixture(); var items = fixture.Build() @@ -48,10 +45,12 @@ public async Task GivenACompanyHouseSearchClient_WhenSearchingForAOfficer() _result = await _client.SearchAsync(new SearchOfficerRequest()); } - [Test] + public Task DisposeAsync() => Task.CompletedTask; + + [Fact] public void ThenResultDataIsCorrect() { - _result.Data.ShouldBeEquivalentTo(_resourceDetails, opt => opt.Excluding(su => Regex.IsMatch(su.SelectedMemberPath, @"Officers\[.+\]\.OfficerId"))); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _resourceDetails, "OfficerId"); } } } diff --git a/tests/CompaniesHouse.Tests/ComparingArrayEnumWith.cs b/tests/CompaniesHouse.Tests/ComparingArrayEnumWith.cs deleted file mode 100644 index f1e72a8..0000000 --- a/tests/CompaniesHouse.Tests/ComparingArrayEnumWith.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using CompaniesHouse.Tests.MapProviders; -using FluentAssertions.Equivalency; - -namespace CompaniesHouse.Tests -{ - public class ComparingArrayEnumWith : IEquivalencyStep - where TMapProvider : IEnumDataMapProvider, new() - where TEnum : struct - { - private readonly IReadOnlyDictionary _dictionary; - private readonly Type _enumType; - - public ComparingArrayEnumWith() - { - _enumType = typeof(TEnum[]); - if (!typeof(TEnum).IsEnum) - { - throw new ArgumentException("TEnum must be an enum"); - } - - var provider = Activator.CreateInstance(); - - _dictionary = provider.Map; - } - - public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config) - { - var subjectType = config.GetSubjectType(context); - - return subjectType != null && subjectType == _enumType && context.Expectation is string; - } - - public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config) - { - var expected = _dictionary[(string)context.Expectation]; - - return ((TEnum[])context.Subject).Contains(expected); - } - } -} \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ComparingEnumWith.cs b/tests/CompaniesHouse.Tests/ComparingEnumWith.cs deleted file mode 100644 index c1b2702..0000000 --- a/tests/CompaniesHouse.Tests/ComparingEnumWith.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using CompaniesHouse.Tests.MapProviders; -using FluentAssertions.Equivalency; - -namespace CompaniesHouse.Tests -{ - public class ComparingEnumWith : IEquivalencyStep - where TMapProvider : IEnumDataMapProvider, new() - where TEnum : struct - { - private readonly IReadOnlyDictionary _dictionary; - private readonly Type _enumType; - - public ComparingEnumWith() - { - _enumType = typeof(TEnum); - if (!_enumType.IsEnum) - { - throw new ArgumentException("TEnum must be an enum"); - } - - var provider = Activator.CreateInstance(); - - _dictionary = provider.Map; - } - - public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config) - { - var subjectType = config.GetSubjectType(context); - - return subjectType != null && subjectType == _enumType && context.Expectation is string; - } - - public bool Handle(IEquivalencyValidationContext context, IEquivalencyValidator parent, IEquivalencyAssertionOptions config) - { - var expected = _dictionary[(string)context.Expectation]; - - return ((TEnum)context.Subject).Equals(expected); - } - } -} \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs b/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs index ad944ab..2ef03a8 100644 --- a/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs +++ b/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs @@ -4,19 +4,18 @@ using CompaniesHouse.DelegatingHandlers; using Moq; using Moq.Protected; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.DelegatingHandlers { - [TestFixture] public class CompaniesHouseAuthorizationHandlerTests { private CompaniesHouseAuthorizationHandler _handler; private string _apiKey; private HttpRequestMessage _actual; - [OneTimeSetUp] - public void GivenACompaniesHouseAuthorizationHandler() + public CompaniesHouseAuthorizationHandlerTests() { var innerHandler = new Mock(MockBehavior.Strict); innerHandler.Protected() @@ -29,20 +28,15 @@ public void GivenACompaniesHouseAuthorizationHandler() { InnerHandler = innerHandler.Object }; - } - - [SetUp] - public void When() - { var client = new HttpClient(_handler); - client.GetAsync("http://liberislabs.com/"); + client.GetAsync("http://liberislabs.com/").GetAwaiter().GetResult(); } - [Test] + [Fact] public void ThenAuthorizationHeaderIsCorrect() { - Assert.That(_actual.Headers.Authorization.Scheme, Is.EqualTo("Basic")); - Assert.That(_actual.Headers.Authorization.Parameter, Is.EqualTo("NDJjODE1NGUtOTgyZC00YTYyLTkxM2QtODlhYWZiMzIwZGJj")); + _actual.Headers.Authorization.Scheme.ShouldBe("Basic"); + _actual.Headers.Authorization.Parameter.ShouldBe("NDJjODE1NGUtOTgyZC00YTYyLTkxM2QtODlhYWZiMzIwZGJj"); } } diff --git a/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs b/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs index c8dbe57..e95546e 100644 --- a/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs +++ b/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs @@ -1,59 +1,58 @@ using CompaniesHouse.Description; -using FluentAssertions; using Newtonsoft.Json.Linq; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.DescriptionTests { - [TestFixture] public class DescriptionProviderTests { - [Test] + [Fact] public void GivenFormatAndMatchingStringVariable() { var format = "some value: {variable}"; var values = JObject.Parse(@"{ ""variable"": ""Value"" }"); var result = DescriptionProvider.GetDescription(format, values); - result.Should().Be(@"some value: Value"); + result.ShouldBe(@"some value: Value"); } - [Test] + [Fact] public void GivenFormatAndMatchingStringVariables() { var format = "some value: {variable1}, other value: {variable2}"; var values = JObject.Parse(@"{ ""variable1"": ""Value1"", ""variable2"": ""Value2"" }"); var result = DescriptionProvider.GetDescription(format, values); - result.Should().Be(@"some value: Value1, other value: Value2"); + result.ShouldBe(@"some value: Value1, other value: Value2"); } - [Test] + [Fact] public void GivenFormatAndNotMatchingStringVariable() { var format = "some value: {variable}"; var values = JObject.Parse(@"{ ""otherVariable"": ""Value"" }"); var result = DescriptionProvider.GetDescription(format, values); - result.Should().Be(@"some value: {variable}"); + result.ShouldBe(@"some value: {variable}"); } - [Test] + [Fact] public void GivenFormatAndMatchingCompoundVariable() { var format = "some value: {parent.variable}"; var values = JObject.Parse(@"{ ""parent"": { ""variable"": ""Value"" }}"); var result = DescriptionProvider.GetDescription(format, values); - result.Should().Be(@"some value: Value"); + result.ShouldBe(@"some value: Value"); } - [Test] + [Fact] public void GivenFormatAndNoVariable() { var format = "some value: {nullvariable}"; var result = DescriptionProvider.GetDescription(format, null); - result.Should().Be(@"some value: {nullvariable}"); + result.ShouldBe(@"some value: {nullvariable}"); } } } diff --git a/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs new file mode 100644 index 0000000..653be8e --- /dev/null +++ b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Shouldly; + +namespace CompaniesHouse.Tests +{ + /// + /// A small, dependency-free replacement for FluentAssertions' BeEquivalentTo. + /// Recursively compares public properties of two object graphs, with built-in bridging + /// between production enum values and the raw wire strings used by the test + /// ResourceBuilders fixtures (matched via each enum member's ). + /// This removes the need for the old FluentAssertions IEquivalencyStep/MapProviders machinery. + /// + public static class EquivalencyAssertionExtensions + { + public static void ShouldBeEquivalentTo(this T actual, T expected, params string[] excludingPropertyNames) + { + var differences = new List(); + Compare(actual, expected, typeof(T).Name, excludingPropertyNames ?? Array.Empty(), differences); + + if (differences.Count > 0) + { + throw new ShouldAssertException( + $"Objects were not equivalent:{Environment.NewLine}{string.Join(Environment.NewLine, differences)}"); + } + } + + private static void Compare(object actual, object expected, string path, string[] excluding, List differences) + { + if (ReferenceEquals(actual, expected)) + { + return; + } + + if (actual is null || expected is null) + { + differences.Add($"{path}: expected <{Describe(expected)}> but was <{Describe(actual)}>"); + return; + } + + // Enum <-> raw wire string bridging (either direction). + if (actual is Enum actualEnum && expected is string expectedString) + { + var actualWireValue = GetEnumMemberValue(actualEnum); + if (actualWireValue != expectedString) + { + differences.Add($"{path}: expected <{expectedString}> but was <{actualWireValue}>"); + } + + return; + } + + if (expected is Enum expectedEnum && actual is string actualString) + { + var expectedWireValue = GetEnumMemberValue(expectedEnum); + if (expectedWireValue != actualString) + { + differences.Add($"{path}: expected <{expectedWireValue}> but was <{actualString}>"); + } + + return; + } + + // Array/collection of enums compared against a single raw wire string: + // replicates the old "does this collection contain the mapped value" behaviour. + if (actual is IEnumerable actualContainer && actual is not string && expected is string containsExpected) + { + var found = actualContainer.Cast().Any(item => ValuesEqual(item, containsExpected)); + if (!found) + { + differences.Add($"{path}: expected collection to contain <{containsExpected}> but was <{Describe(actual)}>"); + } + + return; + } + + var actualType = actual.GetType(); + + if (actualType.IsEnum || actualType.IsPrimitive || actual is string || actual is DateTime + || actual is DateTimeOffset || actual is decimal || actual is Guid || actual is TimeSpan) + { + if (!Equals(actual, expected)) + { + differences.Add($"{path}: expected <{Describe(expected)}> but was <{Describe(actual)}>"); + } + + return; + } + + if (actual is IEnumerable actualEnumerable && expected is IEnumerable expectedEnumerable) + { + var actualList = actualEnumerable.Cast().ToList(); + var expectedList = expectedEnumerable.Cast().ToList(); + + if (actualList.Count != expectedList.Count) + { + differences.Add($"{path}: expected {expectedList.Count} item(s) but found {actualList.Count}"); + return; + } + + for (var i = 0; i < actualList.Count; i++) + { + Compare(actualList[i], expectedList[i], $"{path}[{i}]", excluding, differences); + } + + return; + } + + // Complex object: recurse over public instance properties. + foreach (var property in actualType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (property.GetIndexParameters().Length > 0 || excluding.Contains(property.Name)) + { + continue; + } + + var expectedProperty = expected.GetType().GetProperty(property.Name); + if (expectedProperty is null) + { + continue; + } + + var actualValue = property.GetValue(actual); + var expectedValue = expectedProperty.GetValue(expected); + + Compare(actualValue, expectedValue, $"{path}.{property.Name}", excluding, differences); + } + } + + private static bool ValuesEqual(object actual, object expected) + { + if (actual is Enum actualEnum && expected is string expectedString) + { + return GetEnumMemberValue(actualEnum) == expectedString; + } + + return Equals(actual, expected); + } + + private static string Describe(object value) => value?.ToString() ?? "null"; + + private static string GetEnumMemberValue(Enum enumValue) + { + var type = enumValue.GetType(); + var info = type.GetField(enumValue.ToString()); + var enumMember = (EnumMemberAttribute[])info?.GetCustomAttributes(typeof(EnumMemberAttribute), false); + + return enumMember is { Length: > 0 } ? enumMember[0].Value : enumValue.ToString(); + } + } +} diff --git a/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs index 547af01..43413e2 100644 --- a/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs +++ b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs @@ -5,31 +5,32 @@ using System.Net.Http; using System.Net.Http.Headers; using CompaniesHouse.Extensions; - using NUnit.Framework; + using Shouldly; + using Xunit; - [TestFixture] public class HttpResponseMessageExtensionsTests { - [Test] + [Fact] public void GivenAnHttpResponse_WhenTheStatusCodeIsSuccess_ThenEnsureSuccessStatusCode2ReturnsTheHttpResponse() { for (var statusCode = 200; statusCode < 299; statusCode++) { var sut = new HttpResponseMessage((HttpStatusCode)200); var responseMessage = sut.EnsureSuccessStatusCode2(); - Assert.AreEqual(responseMessage, sut); + responseMessage.ShouldBe(sut); } } - [TestCase(410, "Gone", 0, null)] - [TestCase(429, "Too Many Requests", 300, null)] - [TestCase(503, "Service Unavailable", 0, "2015-10-08T12:34:56.000+1")] - [TestCase(503, "Service Unavailable", -1, null)] + [Theory] + [InlineData(410, "Gone", 0, null)] + [InlineData(429, "Too Many Requests", 300, null)] + [InlineData(503, "Service Unavailable", 0, "2015-10-08T12:34:56.000+1")] + [InlineData(503, "Service Unavailable", -1, null)] public void GivenAnHttpResponse_WhenTheStatusCodeIsNotSuccess_ThenEnsureSuccessStatusCode2ThrowsHttpRequestExceptionWithData( int statusCode, string reasonPhrase, int retryAfterSeconds, - string retryAfterDate) + string? retryAfterDate) { var sut = new HttpResponseMessage((HttpStatusCode)statusCode) { ReasonPhrase = reasonPhrase }; var retryAfterDateTimeOffset = DateTimeOffset.MinValue; @@ -45,20 +46,20 @@ public void GivenAnHttpResponse_WhenTheStatusCodeIsNotSuccess_ThenEnsureSuccessS : new RetryConditionHeaderValue(retryAfterDateTimeOffset); } - var exception = Assert.Throws(() => sut.EnsureSuccessStatusCode2()); - Assert.AreEqual(statusCode, exception.Data["StatusCode"]); - Assert.AreEqual(reasonPhrase, exception.Data["ReasonPhrase"]); + var exception = Should.Throw(() => sut.EnsureSuccessStatusCode2()); + exception.Data["StatusCode"].ShouldBe(statusCode); + exception.Data["ReasonPhrase"].ShouldBe(reasonPhrase); if (retryAfterSeconds >= 0 || !string.IsNullOrWhiteSpace(retryAfterDate)) { - Assert.AreEqual(string.IsNullOrWhiteSpace(retryAfterDate) + exception.Data["RetryAfter"].ShouldBe( + string.IsNullOrWhiteSpace(retryAfterDate) ? retryAfterSeconds.ToString() - : retryAfterDateTimeOffset.ToString("R"), - exception.Data["RetryAfter"]); + : retryAfterDateTimeOffset.ToString("R")); } else { - Assert.AreEqual(null, exception.Data["RetryAfter"]); + exception.Data["RetryAfter"].ShouldBeNull(); } } } diff --git a/tests/CompaniesHouse.Tests/Initializer.cs b/tests/CompaniesHouse.Tests/Initializer.cs deleted file mode 100644 index 5a8ccfd..0000000 --- a/tests/CompaniesHouse.Tests/Initializer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using CompaniesHouse.Response; -using CompaniesHouse.Response.CompanyProfile; -using CompaniesHouse.Response.Officers; -using CompaniesHouse.Response.PersonsWithSignificantControl; -using CompaniesHouse.Tests.MapProviders; -using FluentAssertions; -using NUnit.Framework; - -namespace CompaniesHouse.Tests -{ - [SetUpFixture] - public class Initializer - { - [OneTimeSetUp] - public void OneTimeSetUp() - { - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - AssertionOptions.EquivalencySteps.Insert>(); - } - } -} diff --git a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs index 95eca46..df6afeb 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs @@ -2,25 +2,17 @@ using CompaniesHouse.JsonConverters; using CompaniesHouse.Response; using Newtonsoft.Json; -using NUnit.Framework; namespace CompaniesHouse.Tests.JsonConverters.FilingSubcategoryConverterTests { - [TestFixture] public abstract class StringArrayOrFieldEnumConverterTestsBase { private StringArrayOrFieldEnumConverter _convertor; protected object Result; - [OneTimeSetUp] - public void GivenAFilingSubcategoryConverter() + protected StringArrayOrFieldEnumConverterTestsBase() { _convertor = new StringArrayOrFieldEnumConverter(); - } - - [SetUp] - public void WhenReadingJson() - { var json = GetJson(); var jsonTextReader = new JsonTextReader(new StringReader(json)); jsonTextReader.Read(); diff --git a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs index 6336502..8e99359 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs @@ -1,9 +1,9 @@ using CompaniesHouse.Response; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.JsonConverters.FilingSubcategoryConverterTests { - [TestFixture] public class StringArrayOrFieldEnumConverterTestsForMultipleValues : StringArrayOrFieldEnumConverterTestsBase { protected override string GetJson() @@ -11,10 +11,10 @@ protected override string GetJson() return @"[""compulsory"",""court-order""]"; } - [Test] + [Fact] public void ThenMultipleItemsAreReturned() { - Assert.That(Result, Is.EqualTo(new[] {FilingSubcategory.Compulsory, FilingSubcategory.CourtOrder})); + Result.ShouldBe(new[] { FilingSubcategory.Compulsory, FilingSubcategory.CourtOrder }); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs index e0626d6..c97020c 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CompaniesHouse.Response; -using Moq; -using NUnit.Framework; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.JsonConverters.FilingSubcategoryConverterTests { - [TestFixture] public class StringArrayOrFieldEnumConverterTestsForSingleValue : StringArrayOrFieldEnumConverterTestsBase { protected override string GetJson() @@ -17,10 +11,10 @@ protected override string GetJson() return @"""change"""; } - [Test] + [Fact] public void ThenSingleItemInAnArrayIsReturned() { - Assert.That(Result, Is.EqualTo( new [] {FilingSubcategory.Change })); + Result.ShouldBe(new[] { FilingSubcategory.Change }); } } diff --git a/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs b/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs index 27160a5..9e9644d 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs @@ -1,34 +1,28 @@ using CompaniesHouse.JsonConverters; using Moq; using Newtonsoft.Json; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.JsonConverters.OptionalDateJsonConverterTests { - [TestFixture] public class OptionalDateJsonConverterTestsForUnknownValue { private OptionalDateJsonConverter _convertor; private object _result; - [OneTimeSetUp] - public void GivenADateOfCessationJsonConverter() + public OptionalDateJsonConverterTestsForUnknownValue() { _convertor = new OptionalDateJsonConverter(); - } - - [SetUp] - public void WhenReadingJsonWhenValueIsUnknown() - { var jsonReader = new Mock(); jsonReader.Setup(x => x.Value).Returns("Unknown"); _result = _convertor.ReadJson(jsonReader.Object, null, null, null); } - [Test] + [Fact] public void ThenTheResultIsNull() { - Assert.That(_result, Is.Null); + _result.ShouldBeNull(); } } } diff --git a/tests/CompaniesHouse.Tests/UriBuilders/CompanyFilingUriBuilderTests/CompanyFilingHistoryUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/CompanyFilingUriBuilderTests/CompanyFilingHistoryUriBuilderTests.cs index 9d92112..59be57a 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/CompanyFilingUriBuilderTests/CompanyFilingHistoryUriBuilderTests.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/CompanyFilingUriBuilderTests/CompanyFilingHistoryUriBuilderTests.cs @@ -1,6 +1,7 @@ using System; using CompaniesHouse.UriBuilders; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.CompanyFilingUriBuilderTests { @@ -14,41 +15,35 @@ public class CompanyFilingHistoryUriBuilderTests private int _startIndex; - [OneTimeSetUp] - public void GivenAUriBuilder() + public CompanyFilingHistoryUriBuilderTests() { _uriBuilder = new CompanyFilingHistoryUriBuilder(); - } - - [SetUp] - public void WhenBuildingUriWithCompanyNumber() - { _pageSize = 10; _startIndex = 5; _companyNumber = "123456789"; _actualUri = _uriBuilder.Build(_companyNumber, _startIndex, _pageSize); } - [Test] + [Fact] public void ThenTheUriIsNotAbsolute() { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); + _actualUri.IsAbsoluteUri.ShouldBeFalse(); } - [Test] + [Fact] public void ThenTheUriPathIsCorrect() { var uri = new Uri(_baseUri, _actualUri); var expected = $"/bla1/bla2/company/{_companyNumber}/filing-history"; - Assert.That(uri.AbsolutePath, Is.EqualTo(expected)); + uri.AbsolutePath.ShouldBe(expected); } - [Test] + [Fact] public void ThenTheUriQueryStringIsCorrect() { var uri = new Uri(_baseUri, _actualUri); var expected = $"?items_per_page={_pageSize}&start_index={_startIndex}"; - Assert.That(uri.Query, Is.EqualTo(expected)); + uri.Query.ShouldBe(expected); } } } diff --git a/tests/CompaniesHouse.Tests/UriBuilders/CompanyProfileUriBuilderTests/CompanyProfileUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/CompanyProfileUriBuilderTests/CompanyProfileUriBuilderTests.cs index 3b882a0..a966e34 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/CompanyProfileUriBuilderTests/CompanyProfileUriBuilderTests.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/CompanyProfileUriBuilderTests/CompanyProfileUriBuilderTests.cs @@ -1,6 +1,7 @@ using System; using CompaniesHouse.UriBuilders; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.CompanyProfileUriBuilderTests { @@ -12,30 +13,24 @@ public class CompanyProfileUriBuilderTests private string _companyNumber; - [OneTimeSetUp] - public void GivenACompanyProfileUriBuilder() + public CompanyProfileUriBuilderTests() { _uriBuilder = new CompanyProfileUriBuilder(); - } - - [SetUp] - public void WhenBuildingUriWithCompanySearchRequest() - { _companyNumber = "123456789"; _actualUri = _uriBuilder.Build(_companyNumber); } - [Test] + [Fact] public void ThenTheUriIsNotAbsolute() { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); + _actualUri.IsAbsoluteUri.ShouldBeFalse(); } - [Test] + [Fact] public void ThenTheUriPathIsCorrect() { var uri = new Uri(_baseUri, _actualUri); - Assert.That(uri.AbsolutePath, Is.EqualTo("/bla1/bla2/company/" + _companyNumber)); + uri.AbsolutePath.ShouldBe("/bla1/bla2/company/" + _companyNumber); } } } diff --git a/tests/CompaniesHouse.Tests/UriBuilders/OfficeAddressUriBuilderTests/RegisteredOfficeUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/OfficeAddressUriBuilderTests/RegisteredOfficeUriBuilderTests.cs index 8c667dc..04c071b 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/OfficeAddressUriBuilderTests/RegisteredOfficeUriBuilderTests.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/OfficeAddressUriBuilderTests/RegisteredOfficeUriBuilderTests.cs @@ -1,10 +1,10 @@ using System; using CompaniesHouse.UriBuilders; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.OfficeAddressUriBuilderTests { - [TestFixture] public class RegisteredOfficeUriBuilderTests { @@ -13,30 +13,24 @@ public class RegisteredOfficeUriBuilderTests private readonly Uri _baseUri = new Uri("https://company.co.uk/bla1/bla2/"); private string _companyNumber; - [OneTimeSetUp] - public void GivenACompanyProfileUriBuilder() + public RegisteredOfficeUriBuilderTests() { _uriBuilder = new RegisteredOfficeAddressUriBuilder(); - } - - [SetUp] - public void WhenBuildingUriWithCompanySearchRequest() - { _companyNumber = "123456789"; _actualUri = _uriBuilder.Build(_companyNumber); } - [Test] + [Fact] public void ThenTheUriIsNotAbsolute() { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); + _actualUri.IsAbsoluteUri.ShouldBeFalse(); } - [Test] + [Fact] public void ThenTheUriPathIsCorrect() { var uri = new Uri(_baseUri, _actualUri); - Assert.That(uri.AbsolutePath, Is.EqualTo($"/bla1/bla2/company/{_companyNumber}/registered-office-address")); + uri.AbsolutePath.ShouldBe($"/bla1/bla2/company/{_companyNumber}/registered-office-address"); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/UriBuilders/OfficersAppointmentUriBuilderTests/OfficersAppointmentUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/OfficersAppointmentUriBuilderTests/OfficersAppointmentUriBuilderTests.cs index ab7b3d8..b3ab376 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/OfficersAppointmentUriBuilderTests/OfficersAppointmentUriBuilderTests.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/OfficersAppointmentUriBuilderTests/OfficersAppointmentUriBuilderTests.cs @@ -1,10 +1,10 @@ using System; using CompaniesHouse.UriBuilders; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.OfficersAppointmentUriBuilderTests { - [TestFixture] public class OfficersAppointmentUriBuilderTests { @@ -14,31 +14,25 @@ public class OfficersAppointmentUriBuilderTests private string _companyNumber; private string _appointmentId; - [OneTimeSetUp] - public void GivenACompanyProfileUriBuilder() + public OfficersAppointmentUriBuilderTests() { _uriBuilder = new OfficersAppointmentUriBuilder(); - } - - [SetUp] - public void WhenBuildingUriWithCompanySearchRequest() - { _companyNumber = "123456789"; _appointmentId = "appointmentId"; _actualUri = _uriBuilder.Build(_companyNumber, _appointmentId); } - [Test] + [Fact] public void ThenTheUriIsNotAbsolute() { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); + _actualUri.IsAbsoluteUri.ShouldBeFalse(); } - [Test] + [Fact] public void ThenTheUriPathIsCorrect() { var uri = new Uri(_baseUri, _actualUri); - Assert.That(uri.AbsolutePath, Is.EqualTo($"/bla1/bla2/company/{_companyNumber}/appointments/{_appointmentId}")); + uri.AbsolutePath.ShouldBe($"/bla1/bla2/company/{_companyNumber}/appointments/{_appointmentId}"); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs index 5c30990..3fb789e 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs @@ -1,6 +1,7 @@ using System; using CompaniesHouse.UriBuilders; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.OfficersUriBuilderTests { @@ -14,41 +15,35 @@ public class OfficersUriBuilderTests private int _startIndex; - [OneTimeSetUp] - public void GivenAUriBuilder() + public OfficersUriBuilderTests() { _uriBuilder = new OfficersUriBuilder(); - } - - [SetUp] - public void WhenBuildingUriWithCompanyNumber() - { _pageSize = 10; _startIndex = 5; _companyNumber = "123456789"; _actualUri = _uriBuilder.Build(_companyNumber, _startIndex, _pageSize); } - [Test] + [Fact] public void ThenTheUriIsNotAbsolute() { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); + _actualUri.IsAbsoluteUri.ShouldBeFalse(); } - [Test] + [Fact] public void ThenTheUriPathIsCorrect() { var uri = new Uri(_baseUri, _actualUri); var expected = $"/bla1/bla2/company/{_companyNumber}/officers"; - Assert.That(uri.AbsolutePath, Is.EqualTo(expected)); + uri.AbsolutePath.ShouldBe(expected); } - [Test] + [Fact] public void ThenTheUriQueryStringIsCorrect() { var uri = new Uri(_baseUri, _actualUri); var expected = $"?items_per_page={_pageSize}&start_index={_startIndex}"; - Assert.That(uri.Query, Is.EqualTo(expected)); + uri.Query.ShouldBe(expected); } } } diff --git a/tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlBuilderTests/PersonsWithSignificantControlBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlBuilderTests/PersonsWithSignificantControlBuilderTests.cs index eb2fa35..1efb825 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlBuilderTests/PersonsWithSignificantControlBuilderTests.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlBuilderTests/PersonsWithSignificantControlBuilderTests.cs @@ -1,6 +1,7 @@ using System; using CompaniesHouse.UriBuilders; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.OfficersUriBuilderTests { @@ -14,41 +15,35 @@ public class PersonsWithSignificantControlBuilderTests private int _startIndex; - [OneTimeSetUp] - public void GivenAUriBuilder() + public PersonsWithSignificantControlBuilderTests() { _uriBuilder = new PersonsWithSignificantControlBuilder(); - } - - [SetUp] - public void WhenBuildingUriWithCompanyNumber() - { _pageSize = 10; _startIndex = 5; _companyNumber = "123456789"; _actualUri = _uriBuilder.Build(_companyNumber, _startIndex, _pageSize); } - [Test] + [Fact] public void ThenTheUriIsNotAbsolute() { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); + _actualUri.IsAbsoluteUri.ShouldBeFalse(); } - [Test] + [Fact] public void ThenTheUriPathIsCorrect() { var uri = new Uri(_baseUri, _actualUri); var expected = $"/bla1/bla2/company/{_companyNumber}/persons-with-significant-control"; - Assert.That(uri.AbsolutePath, Is.EqualTo(expected)); + uri.AbsolutePath.ShouldBe(expected); } - [Test] + [Fact] public void ThenTheUriQueryStringIsCorrect() { var uri = new Uri(_baseUri, _actualUri); var expected = $"?items_per_page={_pageSize}&start_index={_startIndex}"; - Assert.That(uri.Query, Is.EqualTo(expected)); + uri.Query.ShouldBe(expected); } } } diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/CompanySearchUriBuilderTestsBase+Thens.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/CompanySearchUriBuilderTestsBase+Thens.cs index f055c85..3c77818 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/CompanySearchUriBuilderTestsBase+Thens.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/CompanySearchUriBuilderTestsBase+Thens.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using NUnit.Framework; +using Shouldly; namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests @@ -21,40 +21,40 @@ public void TheUriQueryStringContainsTheQuery() { var query = GetQuery(); - Assert.That(query["q"], Is.EqualTo(_searchUriBuilderTestsBase.Query)); + query["q"].ShouldBe(_searchUriBuilderTestsBase.Query); } public void TheUriQueryStringDoesNotContainsTheItemsPerPage() { var query = GetQuery(); - Assert.That(query.ContainsKey("items_per_page"), Is.False); + query.ContainsKey("items_per_page").ShouldBeFalse(); } public void TheUriQueryStringContainsTheItemsPerPage() { var query = GetQuery(); - Assert.That(query["items_per_page"], Is.EqualTo(_searchUriBuilderTestsBase.ItemsPerPage.ToString())); + query["items_per_page"].ShouldBe(_searchUriBuilderTestsBase.ItemsPerPage.ToString()); } public void TheUriQueryStringContainsTheStartIndex() { var query = GetQuery(); - Assert.That(query["start_index"], Is.EqualTo(_searchUriBuilderTestsBase.StartIndex.ToString())); + query["start_index"].ShouldBe(_searchUriBuilderTestsBase.StartIndex.ToString()); } public void TheUriQueryStringDoesNotContainsTheStartIndex() { var query = GetQuery(); - Assert.That(query.ContainsKey("start_index"), Is.False); + query.ContainsKey("start_index").ShouldBeFalse(); } protected Dictionary GetQuery() { - return new Uri(_searchUriBuilderTestsBase._baseUri, _searchUriBuilderTestsBase._actualUri) + return new Uri(_searchUriBuilderTestsBase._baseUri, _searchUriBuilderTestsBase.ActualUri) .ToString() .Split('?') .Last() diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBase.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBase.cs index dbc4a32..e1382d2 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBase.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBase.cs @@ -1,14 +1,14 @@ using System; using CompaniesHouse.Request; using CompaniesHouse.UriBuilders; -using NUnit.Framework; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests { public abstract partial class SearchUriBuilderTestsBase { - private SearchUriBuilder _uriBuilder; - private Uri _actualUri; + private readonly SearchUriBuilder _uriBuilder; private readonly Uri _baseUri = new Uri("http://testing123.co.uk/bla1/bla2/"); private string Query { get; } = Guid.NewGuid().ToString(); @@ -17,40 +17,32 @@ public abstract partial class SearchUriBuilderTestsBase protected virtual int? StartIndex { get; } = null; - private string _path; + private readonly string _path; - - [OneTimeSetUp] - public void GivenACompanySearchUriBuilder() + protected SearchUriBuilderTestsBase() { _path = "wat/wat/1"; _uriBuilder = new SearchUriBuilder(_path); } - [SetUp] - public void WhenBuildingUriWithCompanySearchRequest() + private Uri ActualUri => _uriBuilder.Build(new SearchCompanyRequest { - var request = new SearchCompanyRequest - { - Query = Query, - ItemsPerPage = ItemsPerPage, - StartIndex = StartIndex - }; - - _actualUri = _uriBuilder.Build(request); - } + Query = Query, + ItemsPerPage = ItemsPerPage, + StartIndex = StartIndex + }); - [Test] + [Fact] public void ThenTheUriIsNotAbsolute() { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); + ActualUri.IsAbsoluteUri.ShouldBeFalse(); } - [Test] + [Fact] public void ThenTheUriPathIsCorrect() { - var uri = new Uri(_baseUri, _actualUri); - Assert.That(uri.AbsolutePath, Is.EqualTo($"/bla1/bla2/{_path}")); + var uri = new Uri(_baseUri, ActualUri); + uri.AbsolutePath.ShouldBe($"/bla1/bla2/{_path}"); } public Thens Then => new Thens(this); diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBaseForQuery.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBaseForQuery.cs index 7aa52f9..91315ac 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBaseForQuery.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBaseForQuery.cs @@ -1,17 +1,16 @@ -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests { - [TestFixture] public class SearchUriBuilderTestsBaseForQuery : SearchUriBuilderTestsBase { - [Test] + [Fact] public void ThenTheUriQueryStringContainsTheQuery() => Then.TheUriQueryStringContainsTheQuery(); - [Test] + [Fact] public void ThenTheUriQueryStringDoesNotContainsTheItemsPerPage() => Then.TheUriQueryStringDoesNotContainsTheItemsPerPage(); - [Test] + [Fact] public void ThenTheUriQueryStringDoesNotContainsTheStartIndex() => Then.TheUriQueryStringDoesNotContainsTheStartIndex(); } diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForItemsPerPage.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForItemsPerPage.cs index c291631..28996ba 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForItemsPerPage.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForItemsPerPage.cs @@ -1,20 +1,19 @@ using System; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests { - [TestFixture] public class SearchUriBuilderTestsForItemsPerPage : SearchUriBuilderTestsBase { protected override int? ItemsPerPage { get; } = new Random().Next(); - [Test] + [Fact] public void ThenTheUriQueryStringContainsTheQuery() => Then.TheUriQueryStringContainsTheQuery(); - [Test] + [Fact] public void ThenTheUriQueryStringContainsTheItemsPerPage() => Then.TheUriQueryStringContainsTheItemsPerPage(); - [Test] + [Fact] public void ThenTheUriQueryStringDoesNotContainsTheStartIndex() => Then.TheUriQueryStringDoesNotContainsTheStartIndex(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForStartIndex.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForStartIndex.cs index 26112f1..345ab72 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForStartIndex.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsForStartIndex.cs @@ -1,20 +1,19 @@ using System; -using NUnit.Framework; +using Xunit; namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests { - [TestFixture] public class SearchUriBuilderTestsForStartIndex : SearchUriBuilderTestsBase { protected override int? StartIndex { get; } = new Random().Next(); - [Test] + [Fact] public void ThenTheUriQueryStringContainsTheQuery() => Then.TheUriQueryStringContainsTheQuery(); - [Test] + [Fact] public void ThenTheUriQueryStringDoesNotContainsTheItemsPerPage() => Then.TheUriQueryStringDoesNotContainsTheItemsPerPage(); - [Test] + [Fact] public void ThenTheUriQueryStringContainsTheStartIndex() => Then.TheUriQueryStringContainsTheStartIndex(); } From 43ac3df4ac2ac4f741943e18349a4bb2c9356a4d Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 19:32:16 +0100 Subject: [PATCH 03/38] Plan 01: STJ core client architecture (response wrapper, shared pipeline, remove Newtonsoft) - Redesign CompaniesHouseClientResponse 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) --- Directory.Packages.props | 4 - src/CompaniesHouse/CompaniesHouse.csproj | 1 - .../CompaniesHouseApiException.cs | 29 +++++++ .../CompaniesHouseAppointmentsClient.cs | 6 +- .../CompaniesHouseChargesClient.cs | 19 +---- .../CompaniesHouseClientResponse.cs | 32 +++++++- ...ompaniesHouseCompanyFilingHistoryClient.cs | 20 +---- ...HouseCompanyInsolvencyInformationClient.cs | 6 +- .../CompaniesHouseCompanyProfileClient.cs | 10 +-- .../CompaniesHouseDocumentDownloadClient.cs | 17 +++-- .../CompaniesHouseDocumentMetadataClient.cs | 9 +-- .../CompaniesHouseJsonSerializerOptions.cs | 36 +++++++++ ...paniesHouseOfficerByByAppointmentClient.cs | 10 +-- .../CompaniesHouseOfficersClient.cs | 10 +-- ...ousePersonsWithSignificantControlClient.cs | 10 +-- ...aniesHouseRegisteredOfficeAddressClient.cs | 10 +-- .../CompaniesHouseSearchClient.cs | 6 +- src/CompaniesHouse/CompaniesHouseUris.cs | 4 +- .../Description/DescriptionProvider.cs | 30 ++++++-- .../HttpResponseMessageExtensions.cs | 52 ++++++++++--- src/CompaniesHouse/HttpContentExtensions.cs | 21 ----- .../EnumArrayOrSingleJsonConverterFactory.cs | 64 ++++++++++++++++ .../EnumMemberJsonConverterFactory.cs | 67 ++++++++++++++++ .../FilingSubcategoryConverter.cs | 68 ----------------- .../FlexibleBooleanJsonConverterFactory.cs | 67 ++++++++++++++++ .../JsonConverters/JsonCreationConverter.cs | 34 --------- .../OptionalDateJsonConverter.cs | 41 ++++++---- .../OptionalStringEnumConverter.cs | 28 ------- .../JsonConverters/SearchItemConverter.cs | 44 ++++++----- src/CompaniesHouse/Response/Address.cs | 22 +++--- .../Response/Appointments/AppointedTo.cs | 10 +-- .../Response/Appointments/Appointment.cs | 28 ++++--- .../Response/Appointments/Appointments.cs | 14 ++-- .../Response/Appointments/NameElements.cs | 12 +-- .../Response/AssetsCeasedReleased.cs | 2 +- src/CompaniesHouse/Response/ChargeStatus.cs | 2 +- src/CompaniesHouse/Response/Charges/Charge.cs | 48 ++++++------ .../Response/Charges/Charges.cs | 16 ++-- .../Response/Charges/Classification.cs | 9 +-- .../Response/Charges/InsolvencyCase.cs | 10 +-- .../Response/Charges/InsolvencyCaseLinks.cs | 6 +- src/CompaniesHouse/Response/Charges/Links.cs | 6 +- .../Response/Charges/Particular.cs | 19 +++-- .../Response/Charges/PersonEntitled.cs | 6 +- .../Response/Charges/ScottishAlterations.cs | 10 +-- .../Response/Charges/SecuredDetail.cs | 9 +-- .../Response/Charges/Transaction.cs | 14 ++-- .../Response/Charges/TransactionLinks.cs | 8 +- .../Response/ClassificationChargeType.cs | 2 +- .../CompanyFiling/CompanyFilingHistory.cs | 20 +++-- .../CompanyFiling/FilingHistoryItem.cs | 40 +++++----- .../FilingHistoryItemAnnotation.cs | 17 +++-- .../FilingHistoryItemAssociatedFiling.cs | 12 +-- .../FilingHistoryItemResolution.cs | 26 +++---- .../Response/CompanyFiling/Links.cs | 8 +- .../CompanyProfile/AccountingReferenceDate.cs | 8 +- .../Response/CompanyProfile/Accounts.cs | 16 ++-- .../Response/CompanyProfile/AnnualReturn.cs | 14 ++-- .../CompanyProfile/BranchCompanyDetails.cs | 10 +-- .../Response/CompanyProfile/CompanyProfile.cs | 57 +++++++------- .../CompanyProfile/CompanyProfileLinks.cs | 20 ++--- .../CompanyProfile/ConfirmationStatement.cs | 12 +-- .../Response/CompanyProfile/Jurisdiction.cs | 2 +- .../Response/CompanyProfile/LastAccounts.cs | 16 ++-- .../CompanyProfile/LastAccountsType.cs | 2 +- .../Response/CompanyProfile/NextAccounts.cs | 12 +-- .../CompanyProfile/PreviousCompanyName.cs | 10 +-- .../Response/CompanyStatusDetail.cs | 2 +- src/CompaniesHouse/Response/DateOfBirth.cs | 8 +- .../Response/Document/DocumentDownload.cs | 4 +- .../Response/Document/DocumentMetadata.cs | 26 +++---- .../Document/DocumentMetadataContentLength.cs | 6 +- src/CompaniesHouse/Response/Document/Links.cs | 8 +- .../Response/Insolvency/Address.cs | 16 ++-- .../Response/Insolvency/Case.cs | 16 ++-- .../Response/Insolvency/CaseDate.cs | 10 +-- .../Response/Insolvency/CaseDateType.cs | 2 +- .../CompanyInsolvencyInformation.cs | 8 +- .../Response/Insolvency/InsolvencyStatus.cs | 5 +- .../Response/Insolvency/Links.cs | 6 +- .../Response/Insolvency/Practitioner.cs | 14 ++-- .../Response/Officers/Officer.cs | 33 ++++---- .../Officers/OfficerAppointmentLink.cs | 6 +- .../Response/Officers/OfficerDateOfBirth.cs | 12 +-- .../Response/Officers/OfficerFormerName.cs | 8 +- .../Officers/OfficerIdentification.cs | 14 ++-- .../Response/Officers/OfficerLinks.cs | 6 +- .../Response/Officers/OfficerRole.cs | 4 +- .../Response/Officers/Officers.cs | 14 ++-- src/CompaniesHouse/Response/ParticularType.cs | 2 +- .../PersonWithSignificantControl.cs | 32 ++++---- ...sonWithSignificantControlIdentification.cs | 14 ++-- .../PersonWithSignificantControlKind.cs | 4 +- .../PersonWithSignificantControlLinks.cs | 6 +- ...onWithSignificantControlNatureOfControl.cs | 4 +- .../PersonsWithSignificantControl.cs | 8 +- .../Response/RegisteredOfficeAddress/Links.cs | 6 +- .../RegisteredOfficeAddress/OfficeAddress.cs | 28 ++++--- .../OfficeAddressCountry.cs | 2 +- .../Response/Search/AllSearch/Address.cs | 20 ++--- .../Response/Search/AllSearch/AllSearch.cs | 22 +++--- .../Response/Search/AllSearch/Item.cs | 2 +- .../Response/Search/AllSearch/Links.cs | 6 +- .../Response/Search/AllSearch/Matches.cs | 10 +-- .../Response/Search/CompanySearch/Company.cs | 29 ++++--- .../Search/CompanySearch/CompanySearch.cs | 18 ++--- .../Response/Search/CompanySearch/Matches.cs | 4 +- .../DisqualifiedOfficersSearch/Address.cs | 18 ++--- .../DisqualifiedOfficer.cs | 22 +++--- .../DisqualifiedOfficerSearch.cs | 12 +-- .../DisqualifiedOfficersSearch/Match.cs | 10 +-- src/CompaniesHouse/Response/Search/Links.cs | 6 +- .../Response/Search/OfficerSearch/Address.cs | 22 +++--- .../Search/OfficerSearch/DateOfBirth.cs | 8 +- .../Response/Search/OfficerSearch/Match.cs | 10 +-- .../Response/Search/OfficerSearch/Officer.cs | 22 +++--- .../Search/OfficerSearch/OfficerSearch.cs | 12 +-- .../Response/Search/SearchItem.cs | 8 +- .../Response/SecuredDetailType.cs | 2 +- src/CompaniesHouse/app.config | 11 --- src/CompaniesHouse/packages.config | 5 -- .../CompaniesHouse.IntegrationTests.csproj | 1 - .../CompanyInsolvencyInformationTestBase.cs | 27 +++++++ .../CompanyInsolvencyInformationTests.cs | 25 ------ ...ompanyInsolvencyInformationTestsInvalid.cs | 18 +++++ .../CompanyInsolvencyInformationTestsValid.cs | 18 +++++ .../OfficerByAppointmentTestsInvalid.cs | 20 +++++ .../RegisteredOfficeAddressesTestsInValid.cs | 17 +++++ .../app.config | 11 --- .../CompaniesHouse.Tests.csproj | 1 - ...estsForCompanySearchWithTooManyRequests.cs | 20 ++--- .../DescriptionProviderTests.cs | 10 +-- .../HttpResponseMessageExtensionsTests.cs | 76 ++++++++++++++----- ...tringArrayOrFieldEnumConverterTestsBase.cs | 15 ++-- ...alDateJsonConverterTestsForUnknownValue.cs | 16 ++-- .../ResourceBuilders/Charges.cs | 24 ++---- .../ResourceBuilders/InsolvencyCaseLinks.cs | 6 +- tests/CompaniesHouse.Tests/app.config | 11 --- 138 files changed, 1187 insertions(+), 1052 deletions(-) create mode 100644 src/CompaniesHouse/CompaniesHouseApiException.cs create mode 100644 src/CompaniesHouse/CompaniesHouseJsonSerializerOptions.cs delete mode 100644 src/CompaniesHouse/HttpContentExtensions.cs create mode 100644 src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs create mode 100644 src/CompaniesHouse/JsonConverters/EnumMemberJsonConverterFactory.cs delete mode 100644 src/CompaniesHouse/JsonConverters/FilingSubcategoryConverter.cs create mode 100644 src/CompaniesHouse/JsonConverters/FlexibleBooleanJsonConverterFactory.cs delete mode 100644 src/CompaniesHouse/JsonConverters/JsonCreationConverter.cs delete mode 100644 src/CompaniesHouse/JsonConverters/OptionalStringEnumConverter.cs delete mode 100644 src/CompaniesHouse/app.config delete mode 100644 src/CompaniesHouse/packages.config create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs delete mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs delete mode 100644 tests/CompaniesHouse.IntegrationTests/app.config delete mode 100644 tests/CompaniesHouse.Tests/app.config diff --git a/Directory.Packages.props b/Directory.Packages.props index 4eb02f6..7ad8762 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,10 +7,6 @@ - - diff --git a/src/CompaniesHouse/CompaniesHouse.csproj b/src/CompaniesHouse/CompaniesHouse.csproj index 225f01e..3bba0be 100644 --- a/src/CompaniesHouse/CompaniesHouse.csproj +++ b/src/CompaniesHouse/CompaniesHouse.csproj @@ -24,7 +24,6 @@ - diff --git a/src/CompaniesHouse/CompaniesHouseApiException.cs b/src/CompaniesHouse/CompaniesHouseApiException.cs new file mode 100644 index 0000000..c31148b --- /dev/null +++ b/src/CompaniesHouse/CompaniesHouseApiException.cs @@ -0,0 +1,29 @@ +using System; + +namespace CompaniesHouse +{ + /// + /// Thrown for genuine transport/server failures (5xx) so callers can rely on a + /// being returned for every other outcome + /// (including expected 4xx responses such as 404). + /// + public sealed class CompaniesHouseApiException : Exception + { + public CompaniesHouseApiException(int statusCode, string? reasonPhrase, TimeSpan? retryAfter) + : base($"Companies House API request failed with status code {statusCode} ({reasonPhrase}).") + { + StatusCode = statusCode; + ReasonPhrase = reasonPhrase; + RetryAfter = retryAfter; + } + + /// The HTTP status code returned by the API. + public int StatusCode { get; } + + /// The HTTP reason phrase returned by the API, if any. + public string? ReasonPhrase { get; } + + /// The value of the Retry-After header, if present. + public TimeSpan? RetryAfter { get; } + } +} diff --git a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs index aca17e8..ee17e34 100644 --- a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs +++ b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs @@ -22,11 +22,7 @@ public async Task> GetAppointmentsAsy var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - response.EnsureSuccessStatusCode2(); - - var result = await response.Content.ReadAsJsonAsync().ConfigureAwait(false); - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseChargesClient.cs b/src/CompaniesHouse/CompaniesHouseChargesClient.cs index cfe0c75..a384328 100644 --- a/src/CompaniesHouse/CompaniesHouseChargesClient.cs +++ b/src/CompaniesHouse/CompaniesHouseChargesClient.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -25,14 +24,7 @@ public async Task> GetChargesListAsync(str var requestUri = _chargesUriBuilder.Build(companyNumber, startIndex, pageSize); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var data = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync() - : null; - - return new CompaniesHouseClientResponse(data); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } public async Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken = default) @@ -40,14 +32,7 @@ public async Task> GetChargeByIdAsync(strin var requestUri = _chargesUriBuilder.Build(companyNumber, chargeId); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var data = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync() - : null; - - return new CompaniesHouseClientResponse(data); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseClientResponse.cs b/src/CompaniesHouse/CompaniesHouseClientResponse.cs index 2b23722..af06913 100644 --- a/src/CompaniesHouse/CompaniesHouseClientResponse.cs +++ b/src/CompaniesHouse/CompaniesHouseClientResponse.cs @@ -1,12 +1,38 @@ +using System.Net.Http.Headers; + namespace CompaniesHouse { + /// + /// Wraps the result of a Companies House API call, exposing transport metadata (status + /// code, reason phrase, retry-after, headers) alongside the deserialized . + /// public class CompaniesHouseClientResponse { - public CompaniesHouseClientResponse(T data) + public CompaniesHouseClientResponse(T? data, int statusCode, string? reasonPhrase, System.TimeSpan? retryAfter, HttpResponseHeaders? headers) { Data = data; + StatusCode = statusCode; + ReasonPhrase = reasonPhrase; + RetryAfter = retryAfter; + Headers = headers; } - public T Data { get; } + /// The deserialized response body, or default for non-success responses. + public T? Data { get; } + + /// The HTTP status code of the response. + public int StatusCode { get; } + + /// The HTTP reason phrase of the response, if any. + public string? ReasonPhrase { get; } + + /// The value of the Retry-After header, if present (see #181/#182). + public System.TimeSpan? RetryAfter { get; } + + /// Whether the response status code was in the 2xx range. + public bool IsSuccess => StatusCode is >= 200 and < 300; + + /// The response headers, exposed read-only. + public HttpResponseHeaders? Headers { get; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs index 0a5359a..9ae94ac 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs @@ -25,15 +25,7 @@ public CompaniesHouseCompanyFilingHistoryClient(HttpClient httpClient, ICompanyF var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - // Return a null profile on 404s, but raise exception for all other error codes - if (response.StatusCode != System.Net.HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - CompanyFilingHistory result = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync().ConfigureAwait(false) - : null; - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } public async Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default) @@ -42,15 +34,7 @@ public async Task> GetFilingHist var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - // Return a null profile on 404s, but raise exception for all other error codes - if (response.StatusCode != System.Net.HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var result = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync().ConfigureAwait(false) - : null; - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs index 4e3a764..7b11202 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs @@ -21,12 +21,8 @@ public CompaniesHouseCompanyInsolvencyInformationClient(HttpClient httpClient) var requestUri = $"company/{companyNumber}/insolvency"; var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - - response.EnsureSuccessStatusCode2(); - var result = await response.Content.ReadAsJsonAsync().ConfigureAwait(false); - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs index dfd6a85..15c5464 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs @@ -25,15 +25,7 @@ public CompaniesHouseCompanyProfileClient(HttpClient httpClient, ICompanyProfile var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - // Return a null profile on 404s, but raise exception for all other error codes - if (response.StatusCode != System.Net.HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - CompanyProfile result = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync().ConfigureAwait(false) - : null; - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs b/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs index 5358bcb..4453e81 100644 --- a/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs +++ b/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs @@ -1,5 +1,4 @@ -using System.Net; -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Document; @@ -25,19 +24,23 @@ public async Task> DownloadDocume var requestUri = _documentUriBuilder.Build(documentId); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); + await response.EnsureNotServerErrorAsync().ConfigureAwait(false); var data = response.IsSuccessStatusCode ? new DocumentDownload { - Content = await response.Content.ReadAsStreamAsync(), + Content = await response.Content.ReadAsStreamAsync(cancellationToken), ContentLength = response.Content.Headers.ContentLength, - ContentType = response.Content.Headers.ContentType.MediaType + ContentType = response.Content.Headers.ContentType?.MediaType } : null; - return new CompaniesHouseClientResponse(data); + return new CompaniesHouseClientResponse( + data, + (int)response.StatusCode, + response.ReasonPhrase, + response.Headers.RetryAfter?.Delta, + response.Headers); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs b/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs index cfa48aa..2f1c0f3 100644 --- a/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs +++ b/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs @@ -24,14 +24,7 @@ public async Task> GetDocumentMet var requestUri = _documentUriBuilder.Build(documentId); var response = await _httpClient.GetAsync(requestUri, caneCancellationToken).ConfigureAwait(false); - if (response.StatusCode != System.Net.HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var result = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync().ConfigureAwait(false) - : null; - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(caneCancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseJsonSerializerOptions.cs b/src/CompaniesHouse/CompaniesHouseJsonSerializerOptions.cs new file mode 100644 index 0000000..60cc639 --- /dev/null +++ b/src/CompaniesHouse/CompaniesHouseJsonSerializerOptions.cs @@ -0,0 +1,36 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using CompaniesHouse.JsonConverters; + +namespace CompaniesHouse +{ + /// + /// Central factory shared by every sub-client. One + /// instance is used for the whole assembly rather than per-endpoint options. + /// + public static class CompaniesHouseJsonSerializerOptions + { + /// + /// The shared, immutable options instance used for every request/response in the client. + /// + public static JsonSerializerOptions Default { get; } = Create(); + + private static JsonSerializerOptions Create() + { + var options = new JsonSerializerOptions + { + AllowTrailingCommas = true, + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + PropertyNameCaseInsensitive = true, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + }; + + options.Converters.Add(new FlexibleBooleanJsonConverterFactory()); + options.Converters.Add(new EnumMemberJsonConverterFactory()); + options.Converters.Add(new EnumArrayOrSingleJsonConverterFactory()); + options.Converters.Add(new SearchItemConverter()); + + return options; + } + } +} diff --git a/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs b/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs index dce5ab3..4dcba93 100644 --- a/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs +++ b/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs @@ -25,15 +25,7 @@ public async Task> GetOfficerByAppointment var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - // Return a null profile on 404s, but raise exception for all other error codes - if (response.StatusCode != System.Net.HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var result = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync().ConfigureAwait(false) - : null; - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs index 2121e73..552b590 100644 --- a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs +++ b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs @@ -25,15 +25,7 @@ public CompaniesHouseOfficersClient(HttpClient httpClient, IOfficersUriBuilder o var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - // Return a null profile on 404s, but raise exception for all other error codes - if (response.StatusCode != System.Net.HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var result = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync().ConfigureAwait(false) - : null; - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs b/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs index 8c2cac1..09443f4 100644 --- a/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs +++ b/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs @@ -25,15 +25,7 @@ public CompaniesHousePersonsWithSignificantControlClient(HttpClient httpClient, var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - // Return a null profile on 404s, but raise exception for all other error codes - if (response.StatusCode != System.Net.HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var result = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync().ConfigureAwait(false) - : null; - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs b/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs index b47b0c9..48576a1 100644 --- a/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs +++ b/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -25,14 +24,7 @@ public async Task> GetRegisteredOffi var requestUri = _registeredOfficeAddressUriBuilder.Build(companyNumber); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var data = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync() - : null; - - return new CompaniesHouseClientResponse(data); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseSearchClient.cs b/src/CompaniesHouse/CompaniesHouseSearchClient.cs index 4ed82ae..d5fc5e0 100644 --- a/src/CompaniesHouse/CompaniesHouseSearchClient.cs +++ b/src/CompaniesHouse/CompaniesHouseSearchClient.cs @@ -27,11 +27,7 @@ public async Task> SearchAsync().ConfigureAwait(false); - - return new CompaniesHouseClientResponse(result); + return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseUris.cs b/src/CompaniesHouse/CompaniesHouseUris.cs index 5b21304..30a83c9 100644 --- a/src/CompaniesHouse/CompaniesHouseUris.cs +++ b/src/CompaniesHouse/CompaniesHouseUris.cs @@ -4,7 +4,9 @@ namespace CompaniesHouse { public static class CompaniesHouseUris { - public static readonly Uri Default = new Uri("https://api.companieshouse.gov.uk/"); + // Breaking change: the old default host (api.companieshouse.gov.uk) is superseded by + // the current Companies House Public Data API host. + public static readonly Uri Default = new Uri("https://api.company-information.service.gov.uk/"); public static readonly Uri DocumentApi = new Uri("https://document-api.companieshouse.gov.uk/"); } } \ No newline at end of file diff --git a/src/CompaniesHouse/Description/DescriptionProvider.cs b/src/CompaniesHouse/Description/DescriptionProvider.cs index 3186d0a..ff6a05c 100644 --- a/src/CompaniesHouse/Description/DescriptionProvider.cs +++ b/src/CompaniesHouse/Description/DescriptionProvider.cs @@ -1,5 +1,5 @@ -using System.Text.RegularExpressions; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; namespace CompaniesHouse.Description { @@ -7,24 +7,40 @@ public class DescriptionProvider { private static readonly Regex _pattern = new Regex(@"({[a-zA-Z0-9.-_]*})"); - public static string GetDescription(string format, JObject values) + public static string GetDescription(string format, JsonElement? values) { - if (values != null) + if (values is { ValueKind: JsonValueKind.Object } element) { foreach (Match match in _pattern.Matches(format)) { var placeHolder = match.Value; var variableName = placeHolder.TrimStart('{').TrimEnd('}'); - var variableValue = values.SelectToken(variableName); + var variableValue = SelectToken(element, variableName); - if (variableValue != null) + if (variableValue is { ValueKind: JsonValueKind.String }) { - format = format.Replace(placeHolder, variableValue.Value()); + format = format.Replace(placeHolder, variableValue.Value.GetString()); } } } return format; } + + private static JsonElement? SelectToken(JsonElement root, string path) + { + var current = root; + + foreach (var segment in path.Split('.')) + { + if (current.ValueKind != JsonValueKind.Object || !current.TryGetProperty(segment, out current)) + { + return null; + } + } + + return current; + } } } + diff --git a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs index a4f0db2..7becd49 100644 --- a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs +++ b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs @@ -1,23 +1,53 @@ namespace CompaniesHouse.Extensions; using System.Net.Http; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; +/// +/// The shared send/deserialize pipeline used by every sub-client: non-5xx responses are always +/// returned as a (including 404s, with +/// Data == default); 5xx responses raise . +/// public static class HttpResponseMessageExtensions { - public static HttpResponseMessage EnsureSuccessStatusCode2(this HttpResponseMessage responseMessage) + /// + /// Deserializes the response body (for success status codes) and wraps it, along with + /// transport metadata, in a . + /// + public static async Task> ToCompaniesHouseClientResponseAsync( + this HttpResponseMessage response, CancellationToken cancellationToken = default) { - try - { - responseMessage.EnsureSuccessStatusCode(); - } - catch (HttpRequestException e) + await response.EnsureNotServerErrorAsync().ConfigureAwait(false); + + var data = response.IsSuccessStatusCode + ? await response.Content.ReadFromJsonAsync(CompaniesHouseJsonSerializerOptions.Default, cancellationToken).ConfigureAwait(false) + : default; + + return new CompaniesHouseClientResponse( + data, + (int)response.StatusCode, + response.ReasonPhrase, + response.Headers.RetryAfter?.Delta, + response.Headers); + } + + /// + /// Throws for genuine server errors (5xx); returns + /// normally for everything else, including expected 4xx responses. + /// + public static Task EnsureNotServerErrorAsync(this HttpResponseMessage response) + { + if ((int)response.StatusCode >= 500) { - e.Data["StatusCode"] = (int)responseMessage.StatusCode; - e.Data["ReasonPhrase"] = responseMessage.ReasonPhrase; - e.Data["RetryAfter"] = responseMessage.Headers?.RetryAfter?.ToString(); - throw; + throw new CompaniesHouseApiException( + (int)response.StatusCode, + response.ReasonPhrase, + response.Headers.RetryAfter?.Delta); } - return responseMessage; + return Task.CompletedTask; } } + diff --git a/src/CompaniesHouse/HttpContentExtensions.cs b/src/CompaniesHouse/HttpContentExtensions.cs deleted file mode 100644 index 2a3a62a..0000000 --- a/src/CompaniesHouse/HttpContentExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace CompaniesHouse -{ - public static class HttpContentExtensions - { - public static async Task ReadAsJsonAsync(this HttpContent content) - { - using var s = await content.ReadAsStreamAsync() - .ConfigureAwait(false); - using var sr = new StreamReader(s); - using var reader = new JsonTextReader(sr); - var serializer = new JsonSerializer(); - - return serializer.Deserialize(reader); - } - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs b/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs new file mode 100644 index 0000000..b523bde --- /dev/null +++ b/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs @@ -0,0 +1,64 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.JsonConverters +{ + /// + /// Reads an enum array property that the Companies House API sometimes returns as a single + /// string instead of an array (e.g. filing history subcategory). Replaces the old + /// custom string-or-array enum converter. Registered globally so no + /// per-property [JsonConverter] attribute is required. + /// + public sealed class EnumArrayOrSingleJsonConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => + typeToConvert.IsArray && typeToConvert.GetElementType() is { IsEnum: true }; + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var elementType = typeToConvert.GetElementType()!; + var converterType = typeof(EnumArrayOrSingleJsonConverter<>).MakeGenericType(elementType); + return (JsonConverter)Activator.CreateInstance(converterType)!; + } + + private sealed class EnumArrayOrSingleJsonConverter : JsonConverter + where TEnum : struct, Enum + { + public override TEnum[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType == JsonTokenType.StartArray) + { + var items = new System.Collections.Generic.List(); + + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + items.Add(JsonSerializer.Deserialize(ref reader, options)); + } + + return items.ToArray(); + } + + var value = JsonSerializer.Deserialize(ref reader, options); + return new[] { value }; + } + + public override void Write(Utf8JsonWriter writer, TEnum[] value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + + foreach (var item in value) + { + JsonSerializer.Serialize(writer, item, options); + } + + writer.WriteEndArray(); + } + } + } +} diff --git a/src/CompaniesHouse/JsonConverters/EnumMemberJsonConverterFactory.cs b/src/CompaniesHouse/JsonConverters/EnumMemberJsonConverterFactory.cs new file mode 100644 index 0000000..55f1e20 --- /dev/null +++ b/src/CompaniesHouse/JsonConverters/EnumMemberJsonConverterFactory.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.JsonConverters +{ + /// + /// Converts C# enums decorated with to/from their wire + /// string value, matching the previous string-enum wire format behaviour. + /// Registered globally so no per-property [JsonConverter] attribute is required. + /// + public sealed class EnumMemberJsonConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum; + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var converterType = typeof(EnumMemberJsonConverter<>).MakeGenericType(typeToConvert); + return (JsonConverter)Activator.CreateInstance(converterType)!; + } + + private sealed class EnumMemberJsonConverter : JsonConverter + where TEnum : struct, Enum + { + private static readonly Dictionary ValueToEnum = new(StringComparer.Ordinal); + private static readonly Dictionary EnumToValue = new(); + + static EnumMemberJsonConverter() + { + foreach (var field in typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var enumValue = (TEnum)field.GetValue(null)!; + var name = field.GetCustomAttribute()?.Value ?? field.Name; + + ValueToEnum[name] = enumValue; + ValueToEnum[field.Name] = enumValue; + EnumToValue[enumValue] = name; + } + } + + public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var raw = reader.GetString() ?? string.Empty; + + if (ValueToEnum.TryGetValue(raw, out var value)) + { + return value; + } + + throw new JsonException($"Unable to convert \"{raw}\" to enum \"{typeof(TEnum).Name}\"."); + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(EnumToValue.TryGetValue(value, out var raw) ? raw : value.ToString()); + } + } + } +} diff --git a/src/CompaniesHouse/JsonConverters/FilingSubcategoryConverter.cs b/src/CompaniesHouse/JsonConverters/FilingSubcategoryConverter.cs deleted file mode 100644 index e393c66..0000000 --- a/src/CompaniesHouse/JsonConverters/FilingSubcategoryConverter.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace CompaniesHouse.JsonConverters -{ - public class StringArrayOrFieldEnumConverter : JsonConverter - { - private readonly StringEnumConverter _stringEnumConverter; - - public StringArrayOrFieldEnumConverter() - { - _stringEnumConverter = new StringEnumConverter(); - } - - public override bool CanConvert(Type objectType) - { - if (objectType.IsArray && _stringEnumConverter.CanConvert(objectType.GetElementType())) - { - return true; - } - - return false; - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var elementType = objectType.GetElementType(); - - var list = new List() as IList; - - if (reader.TokenType == JsonToken.StartArray) - { - reader.Read(); - while (reader.TokenType != JsonToken.EndArray) - { - var value = ReadValue(reader, elementType, existingValue, serializer); - list.Add(value); - - reader.Read(); - } - } - else - { - var value = ReadValue(reader, elementType, existingValue, serializer); - list.Add(value); - } - - var array = (Array)Activator.CreateInstance(elementType.MakeArrayType(), list.Count); - list.CopyTo(array, 0); - - return array; - } - - private object ReadValue(JsonReader reader, Type elementType, object existingValue, JsonSerializer serializer) - { - return _stringEnumConverter.ReadJson(reader, elementType, existingValue, serializer); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/JsonConverters/FlexibleBooleanJsonConverterFactory.cs b/src/CompaniesHouse/JsonConverters/FlexibleBooleanJsonConverterFactory.cs new file mode 100644 index 0000000..7228e70 --- /dev/null +++ b/src/CompaniesHouse/JsonConverters/FlexibleBooleanJsonConverterFactory.cs @@ -0,0 +1,67 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.JsonConverters +{ + internal sealed class FlexibleBooleanJsonConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => + typeToConvert == typeof(bool) || typeToConvert == typeof(bool?); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + typeToConvert == typeof(bool) + ? new FlexibleBooleanJsonConverter() + : new NullableFlexibleBooleanJsonConverter(); + + private sealed class FlexibleBooleanJsonConverter : JsonConverter + { + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.True) + { + return true; + } + + if (reader.TokenType == JsonTokenType.False) + { + return false; + } + + if (reader.TokenType == JsonTokenType.String && bool.TryParse(reader.GetString(), out var value)) + { + return value; + } + + throw new JsonException("Unable to convert the JSON value to Boolean."); + } + + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) => + writer.WriteBooleanValue(value); + } + + private sealed class NullableFlexibleBooleanJsonConverter : JsonConverter + { + public override bool? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + return new FlexibleBooleanJsonConverter().Read(ref reader, typeof(bool), options); + } + + public override void Write(Utf8JsonWriter writer, bool? value, JsonSerializerOptions options) + { + if (value.HasValue) + { + writer.WriteBooleanValue(value.Value); + return; + } + + writer.WriteNullValue(); + } + } + } +} diff --git a/src/CompaniesHouse/JsonConverters/JsonCreationConverter.cs b/src/CompaniesHouse/JsonConverters/JsonCreationConverter.cs deleted file mode 100644 index 6878c5c..0000000 --- a/src/CompaniesHouse/JsonConverters/JsonCreationConverter.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace CompaniesHouse.JsonConverters -{ - public abstract class JsonCreationConverter : JsonConverter - { - protected abstract T Create(Type objectType, JObject jObject); - - public override bool CanConvert(Type objectType) - { - return typeof(T) == objectType; - } - - public override bool CanWrite => false; - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var jObject = JObject.Load(reader); - - var target = Create(objectType, jObject); - - serializer.Populate(jObject.CreateReader(), target); - - return target; - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/JsonConverters/OptionalDateJsonConverter.cs b/src/CompaniesHouse/JsonConverters/OptionalDateJsonConverter.cs index 3c10f2f..2a460ba 100644 --- a/src/CompaniesHouse/JsonConverters/OptionalDateJsonConverter.cs +++ b/src/CompaniesHouse/JsonConverters/OptionalDateJsonConverter.cs @@ -1,34 +1,43 @@ using System; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.JsonConverters { - public class OptionalDateJsonConverter : JsonConverter + /// + /// Handles Companies House dates that are sometimes returned as the literal string + /// "Unknown" instead of a date, or as a partial date. Applied per-property (not every + /// DateTime? needs this handling). + /// + public class OptionalDateJsonConverter : JsonConverter { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if ( value is DateTime? ) { - if ( value != null ) { - writer.WriteValue(((DateTime)value).ToString("yyyy-MM-dd")); - } + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + var raw = reader.GetString(); + + if (string.IsNullOrEmpty(raw) || raw == "Unknown") + { + return null; } + + return DateTime.Parse(raw); } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) { - if (reader.Value as string == "Unknown") + if (value is null) { - return null; + writer.WriteNullValue(); } else { - return serializer.Deserialize(reader); + writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd")); } } - - public override bool CanConvert(Type objectType) - { - return true; - } } } diff --git a/src/CompaniesHouse/JsonConverters/OptionalStringEnumConverter.cs b/src/CompaniesHouse/JsonConverters/OptionalStringEnumConverter.cs deleted file mode 100644 index b8944dd..0000000 --- a/src/CompaniesHouse/JsonConverters/OptionalStringEnumConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace CompaniesHouse.JsonConverters -{ - public class OptionalStringEnumConverter : StringEnumConverter - { - private readonly T _defaultValue; - - public OptionalStringEnumConverter(T defaultValue) - { - _defaultValue = defaultValue; - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - { - return _defaultValue; - } - - return base.ReadJson(reader, objectType, existingValue, serializer); - } - } -} diff --git a/src/CompaniesHouse/JsonConverters/SearchItemConverter.cs b/src/CompaniesHouse/JsonConverters/SearchItemConverter.cs index ba8f84a..d8a49d2 100644 --- a/src/CompaniesHouse/JsonConverters/SearchItemConverter.cs +++ b/src/CompaniesHouse/JsonConverters/SearchItemConverter.cs @@ -1,32 +1,42 @@ using System; +using System.Text.Json; +using System.Text.Json.Serialization; using CompaniesHouse.Response.Search; using CompaniesHouse.Response.Search.CompanySearch; using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch; using CompaniesHouse.Response.Search.OfficerSearch; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace CompaniesHouse.JsonConverters { - public class SearchItemConverter : JsonCreationConverter + /// + /// Polymorphic reader for : picks the concrete type based on the + /// "kind" discriminator field, mirroring the previous discriminator-based implementation. + /// + public class SearchItemConverter : JsonConverter { - protected override SearchItem Create(Type objectType, JObject jObject) + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(SearchItem); + + public override SearchItem Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var kind = jObject.Value("kind"); - if (kind == "searchresults#company") - { - return new Company(); - } - else if (kind == "searchresults#officer") - { - return new Officer(); - } - else if (kind == "searchresults#disqualified-officer") + using var document = JsonDocument.ParseValue(ref reader); + var root = document.RootElement; + + var kind = root.TryGetProperty("kind", out var kindElement) ? kindElement.GetString() : null; + + SearchItem item = kind switch { - return new DisqualifiedOfficer(); - } + "searchresults#company" => root.Deserialize(options)!, + "searchresults#officer" => root.Deserialize(options)!, + "searchresults#disqualified-officer" => root.Deserialize(options)!, + _ => throw new NotImplementedException($"Unknown search item kind \"{kind}\".") + }; + + return item; + } - throw new NotImplementedException(); + public override void Write(Utf8JsonWriter writer, SearchItem value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); } } } diff --git a/src/CompaniesHouse/Response/Address.cs b/src/CompaniesHouse/Response/Address.cs index 145abb3..391ec07 100644 --- a/src/CompaniesHouse/Response/Address.cs +++ b/src/CompaniesHouse/Response/Address.cs @@ -1,34 +1,34 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response { public class Address { - [JsonProperty(PropertyName = "address_line_1")] + [JsonPropertyName("address_line_1")] public string AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] + [JsonPropertyName("address_line_2")] public string AddressLine2 { get; set; } - [JsonProperty(PropertyName = "care_of")] + [JsonPropertyName("care_of")] public string CareOf { get; set; } - [JsonProperty(PropertyName = "country")] + [JsonPropertyName("country")] public string Country { get; set; } - [JsonProperty(PropertyName = "locality")] + [JsonPropertyName("locality")] public string Locality { get; set; } - [JsonProperty(PropertyName = "po_box")] + [JsonPropertyName("po_box")] public string PoBox { get; set; } - [JsonProperty(PropertyName = "postal_code")] + [JsonPropertyName("postal_code")] public string PostalCode { get; set; } - [JsonProperty(PropertyName = "Premises")] + [JsonPropertyName("premises")] public string Premises { get; set; } - [JsonProperty(PropertyName = "region")] + [JsonPropertyName("region")] public string Region { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Appointments/AppointedTo.cs b/src/CompaniesHouse/Response/Appointments/AppointedTo.cs index 279388c..58a709e 100644 --- a/src/CompaniesHouse/Response/Appointments/AppointedTo.cs +++ b/src/CompaniesHouse/Response/Appointments/AppointedTo.cs @@ -1,17 +1,17 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Appointments { public class AppointedTo { - [JsonProperty(PropertyName = "company_status")] + [JsonPropertyName("company_status")] public string CompanyStatus { get; set; } - [JsonProperty(PropertyName = "company_number")] + [JsonPropertyName("company_number")] public string CompanyNumber { get; set; } - [JsonProperty(PropertyName = "company_name")] + [JsonPropertyName("company_name")] public string CompanyName { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Appointments/Appointment.cs b/src/CompaniesHouse/Response/Appointments/Appointment.cs index 528b3c4..690a6fe 100644 --- a/src/CompaniesHouse/Response/Appointments/Appointment.cs +++ b/src/CompaniesHouse/Response/Appointments/Appointment.cs @@ -1,41 +1,39 @@ -using System; +using System; using CompaniesHouse.Response.Officers; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Appointments { public class Appointment { - [JsonProperty(PropertyName = "officer_role")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("officer_role")] public OfficerRole OfficerRole { get; set; } - [JsonProperty(PropertyName = "name_elements")] + [JsonPropertyName("name_elements")] public NameElements NameElements { get; set; } - [JsonProperty(PropertyName = "name")] + [JsonPropertyName("name")] public string Name { get; set; } - [JsonProperty(PropertyName = "appointed_to")] + [JsonPropertyName("appointed_to")] public AppointedTo Appointed { get; set; } - [JsonProperty(PropertyName = "nationality")] + [JsonPropertyName("nationality")] public string Nationality { get; set; } - [JsonProperty(PropertyName = "country_of_residence")] + [JsonPropertyName("country_of_residence")] public string CountryOfResidence { get; set; } - [JsonProperty(PropertyName = "occupation")] + [JsonPropertyName("occupation")] public string Occupation { get; set; } - [JsonProperty(PropertyName = "address")] + [JsonPropertyName("address")] public Address Address { get; set; } - [JsonProperty(PropertyName = "appointed_on")] + [JsonPropertyName("appointed_on")] public DateTime? AppointedOn { get; set; } - [JsonProperty(PropertyName = "resigned_on")] + [JsonPropertyName("resigned_on")] public DateTime? ResignedOn { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Appointments/Appointments.cs b/src/CompaniesHouse/Response/Appointments/Appointments.cs index 5fbb2ab..b66ba3a 100644 --- a/src/CompaniesHouse/Response/Appointments/Appointments.cs +++ b/src/CompaniesHouse/Response/Appointments/Appointments.cs @@ -1,25 +1,25 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Text; using CompaniesHouse.Response.Search.OfficerSearch; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Appointments { public class Appointments { - [JsonProperty(PropertyName = "total_results")] + [JsonPropertyName("total_results")] public int TotalResults { get; set; } - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } - [JsonProperty(PropertyName = "is_corporate_officer")] + [JsonPropertyName("is_corporate_officer")] public bool IsCorporateOfficer { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] + [JsonPropertyName("date_of_birth")] public DateOfBirth DateOfBirth { get; set; } - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public Appointment[] Items { get; set; } } diff --git a/src/CompaniesHouse/Response/Appointments/NameElements.cs b/src/CompaniesHouse/Response/Appointments/NameElements.cs index 7cd5f33..0eaafbb 100644 --- a/src/CompaniesHouse/Response/Appointments/NameElements.cs +++ b/src/CompaniesHouse/Response/Appointments/NameElements.cs @@ -1,20 +1,20 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Appointments { public class NameElements { - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public string Title { get; set; } - [JsonProperty(PropertyName = "forename")] + [JsonPropertyName("forename")] public string Forename { get; set; } - [JsonProperty(PropertyName = "surname")] + [JsonPropertyName("surname")] public string Surname { get; set; } - [JsonProperty(PropertyName = "other_forenames")] + [JsonPropertyName("other_forenames")] public string OtherForenames { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/AssetsCeasedReleased.cs b/src/CompaniesHouse/Response/AssetsCeasedReleased.cs index 3c0218d..df05258 100644 --- a/src/CompaniesHouse/Response/AssetsCeasedReleased.cs +++ b/src/CompaniesHouse/Response/AssetsCeasedReleased.cs @@ -28,4 +28,4 @@ public enum AssetsCeasedReleased [EnumMember(Value = "whole-property-released-and-ceased-to-belong")] WholePropertyReleasedAndCeasedToBelong } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/ChargeStatus.cs b/src/CompaniesHouse/Response/ChargeStatus.cs index 2b20662..8c07f38 100644 --- a/src/CompaniesHouse/Response/ChargeStatus.cs +++ b/src/CompaniesHouse/Response/ChargeStatus.cs @@ -19,4 +19,4 @@ public enum ChargeStatus [EnumMember(Value = "satisfied")] Satisfied, } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/Charge.cs b/src/CompaniesHouse/Response/Charges/Charge.cs index fc5372c..c1c5b7a 100644 --- a/src/CompaniesHouse/Response/Charges/Charge.cs +++ b/src/CompaniesHouse/Response/Charges/Charge.cs @@ -1,74 +1,72 @@ using System; using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class Charge { - [JsonProperty("acquired_on")] + [JsonPropertyName("acquired_on")] public DateTime? AcquiredOn { get; set; } - [JsonProperty("assets_ceased_released")] - [JsonConverter(typeof(OptionalStringEnumConverter), AssetsCeasedReleased.None)] + [JsonPropertyName("assets_ceased_released")] public AssetsCeasedReleased AssetsCeasedReleased { get; set; } - [JsonProperty("charge_code")] + [JsonPropertyName("charge_code")] public string ChargeCode { get; set; } - [JsonProperty("charge_number")] + [JsonPropertyName("charge_number")] public int? ChargeNumber { get; set; } - [JsonProperty("classification")] + [JsonPropertyName("classification")] public Classification Classification { get; set; } - [JsonProperty("covering_instrument_date")] + [JsonPropertyName("covering_instrument_date")] public DateTime? CoveringInstrumentDate { get; set; } - [JsonProperty("created_on")] + [JsonPropertyName("created_on")] public DateTime? CreatedOn { get; set; } - [JsonProperty("delivered_on")] + [JsonPropertyName("delivered_on")] public DateTime? DeliveredOn { get; set; } - [JsonProperty("etag")] + [JsonPropertyName("etag")] public string Etag { get; set; } - [JsonProperty("id")] + [JsonPropertyName("id")] public string Id { get; set; } - [JsonProperty("insolvency_cases")] + [JsonPropertyName("insolvency_cases")] public InsolvencyCase[] InsolvencyCases { get; set; } - [JsonProperty("links")] + [JsonPropertyName("links")] public Links Links { get; set; } - [JsonProperty("more_than_four_persons_entitled")] + [JsonPropertyName("more_than_four_persons_entitled")] public bool? MoreThanFourPersonsEntitled { get; set; } - [JsonProperty("particulars")] + [JsonPropertyName("particulars")] public Particular Particular { get; set; } - [JsonProperty("persons_entitled")] + [JsonPropertyName("persons_entitled")] public PersonEntitled[] PersonsEntitled { get; set; } - [JsonProperty("resolved_on")] + [JsonPropertyName("resolved_on")] public DateTime? ResolvedOn { get; set; } - [JsonProperty("satisfied_on")] + [JsonPropertyName("satisfied_on")] public DateTime? SatisfiedOn { get; set; } - [JsonProperty("scottish_alterations")] + [JsonPropertyName("scottish_alterations")] public ScottishAlterations ScottishAlterations { get; set; } - [JsonProperty("secured_details")] + [JsonPropertyName("secured_details")] public SecuredDetail SecuredDetail { get; set; } - [JsonProperty("status")] - [JsonConverter(typeof(OptionalStringEnumConverter), ChargeStatus.None)] + [JsonPropertyName("status")] public ChargeStatus Status { get; set; } - [JsonProperty("transactions")] + [JsonPropertyName("transactions")] public Transaction[] Transactions { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/Charges.cs b/src/CompaniesHouse/Response/Charges/Charges.cs index 56be664..0d2fbb4 100644 --- a/src/CompaniesHouse/Response/Charges/Charges.cs +++ b/src/CompaniesHouse/Response/Charges/Charges.cs @@ -1,25 +1,25 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class Charges { - [JsonProperty("Etag")] + [JsonPropertyName("Etag")] public string Etag { get; set; } - [JsonProperty("items")] + [JsonPropertyName("items")] public Charge[] Items { get; set; } - [JsonProperty("part_satisfied_count")] + [JsonPropertyName("part_satisfied_count")] public int? PartSatisfiedCount { get; set; } - [JsonProperty("satisfied_count")] + [JsonPropertyName("satisfied_count")] public int? SatisfiedCount { get; set; } - [JsonProperty("total_count")] + [JsonPropertyName("total_count")] public int? TotalCount { get; set; } - [JsonProperty("unfiletered_count")] + [JsonPropertyName("unfiletered_count")] public int? UnfileteredCount { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/Classification.cs b/src/CompaniesHouse/Response/Charges/Classification.cs index 3312881..f38d4f8 100644 --- a/src/CompaniesHouse/Response/Charges/Classification.cs +++ b/src/CompaniesHouse/Response/Charges/Classification.cs @@ -1,15 +1,14 @@ using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class Classification { - [JsonProperty("description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty("type")] - [JsonConverter(typeof(OptionalStringEnumConverter), ClassificationChargeType.None)] + [JsonPropertyName("type")] public ClassificationChargeType Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs b/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs index 26cff82..bf946be 100644 --- a/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs +++ b/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class InsolvencyCase { - [JsonProperty("case_number")] + [JsonPropertyName("case_number")] public string CaseNumber { get; set; } - [JsonProperty("links")] + [JsonPropertyName("links")] public InsolvencyCaseLinks Links { get; set; } - [JsonProperty("transaction_id")] + [JsonPropertyName("transaction_id")] public long? TransactionId { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs b/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs index 66be7ae..4259240 100644 --- a/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs +++ b/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class InsolvencyCaseLinks { - [JsonProperty("case")] + [JsonPropertyName("case")] public string Case { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/Links.cs b/src/CompaniesHouse/Response/Charges/Links.cs index cb3af78..c079fcd 100644 --- a/src/CompaniesHouse/Response/Charges/Links.cs +++ b/src/CompaniesHouse/Response/Charges/Links.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class Links { - [JsonProperty("self")] + [JsonPropertyName("self")] public string Self { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/Particular.cs b/src/CompaniesHouse/Response/Charges/Particular.cs index 5ccf9d0..e66d7bf 100644 --- a/src/CompaniesHouse/Response/Charges/Particular.cs +++ b/src/CompaniesHouse/Response/Charges/Particular.cs @@ -1,30 +1,29 @@ using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class Particular { - [JsonProperty("chargor_acting_as_bare_trustee")] + [JsonPropertyName("chargor_acting_as_bare_trustee")] public bool? ChargorActingAsBareTrustee { get; set; } - [JsonProperty("contains_fixed_charge")] + [JsonPropertyName("contains_fixed_charge")] public bool? ContainsFixedCharge { get; set; } - [JsonProperty("contains_floating_charge")] + [JsonPropertyName("contains_floating_charge")] public bool? ContainsFloatingCharge { get; set; } - [JsonProperty("contains_negative_pledge")] + [JsonPropertyName("contains_negative_pledge")] public bool? ContainsNegativePledge { get; set; } - [JsonProperty("description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty("floating_charge_covers_all")] + [JsonPropertyName("floating_charge_covers_all")] public bool? FloatingChargeCoversAll { get; set; } - [JsonProperty("type")] - [JsonConverter(typeof(OptionalStringEnumConverter), ParticularType.None)] + [JsonPropertyName("type")] public ParticularType Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/PersonEntitled.cs b/src/CompaniesHouse/Response/Charges/PersonEntitled.cs index 61c2b54..1666008 100644 --- a/src/CompaniesHouse/Response/Charges/PersonEntitled.cs +++ b/src/CompaniesHouse/Response/Charges/PersonEntitled.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class PersonEntitled { - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/ScottishAlterations.cs b/src/CompaniesHouse/Response/Charges/ScottishAlterations.cs index b6ac2fe..2592359 100644 --- a/src/CompaniesHouse/Response/Charges/ScottishAlterations.cs +++ b/src/CompaniesHouse/Response/Charges/ScottishAlterations.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class ScottishAlterations { - [JsonProperty("has_alterations_to_order")] + [JsonPropertyName("has_alterations_to_order")] public bool? HasAlterationsToOrder { get; set; } - [JsonProperty("has_alterations_to_prohibitions")] + [JsonPropertyName("has_alterations_to_prohibitions")] public bool? HasAlterationsToProhibitions { get; set; } - [JsonProperty("has_restricting_provisions")] + [JsonPropertyName("has_restricting_provisions")] public bool? HasRestrictingProvisions { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/SecuredDetail.cs b/src/CompaniesHouse/Response/Charges/SecuredDetail.cs index 23bca48..3b3f2e1 100644 --- a/src/CompaniesHouse/Response/Charges/SecuredDetail.cs +++ b/src/CompaniesHouse/Response/Charges/SecuredDetail.cs @@ -1,15 +1,14 @@ using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class SecuredDetail { - [JsonProperty("description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty("type")] - [JsonConverter(typeof(OptionalStringEnumConverter), SecuredDetailType.None)] + [JsonPropertyName("type")] public SecuredDetailType Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/Transaction.cs b/src/CompaniesHouse/Response/Charges/Transaction.cs index bf39d48..8456867 100644 --- a/src/CompaniesHouse/Response/Charges/Transaction.cs +++ b/src/CompaniesHouse/Response/Charges/Transaction.cs @@ -1,23 +1,23 @@ using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class Transaction { - [JsonProperty("delivered_on")] + [JsonPropertyName("delivered_on")] public DateTime? DeliveredOn { get; set; } - [JsonProperty("filing_type")] + [JsonPropertyName("filing_type")] public string FilingType { get; set; } - [JsonProperty("insolvency_case_number")] + [JsonPropertyName("insolvency_case_number")] public int? InsolvencyCaseNumber { get; set; } - [JsonProperty("links")] + [JsonPropertyName("links")] public TransactionLinks Links { get; set; } - [JsonProperty("transaction_id")] + [JsonPropertyName("transaction_id")] public int? TransactionId { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Charges/TransactionLinks.cs b/src/CompaniesHouse/Response/Charges/TransactionLinks.cs index c3c7db3..38b75ca 100644 --- a/src/CompaniesHouse/Response/Charges/TransactionLinks.cs +++ b/src/CompaniesHouse/Response/Charges/TransactionLinks.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Charges { public class TransactionLinks { - [JsonProperty("filing")] + [JsonPropertyName("filing")] public string Filing { get; set; } - [JsonProperty("insolvency_case")] + [JsonPropertyName("insolvency_case")] public string InsolvencyCase { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/ClassificationChargeType.cs b/src/CompaniesHouse/Response/ClassificationChargeType.cs index f8a718a..128a4fd 100644 --- a/src/CompaniesHouse/Response/ClassificationChargeType.cs +++ b/src/CompaniesHouse/Response/ClassificationChargeType.cs @@ -13,4 +13,4 @@ public enum ClassificationChargeType [EnumMember(Value="nature-of-charge")] NatureOfCharge } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs b/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs index 5f0d79d..c353435 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs @@ -1,31 +1,29 @@ -using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using CompaniesHouse.JsonConverters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling { public class CompanyFilingHistory { - [JsonProperty(PropertyName = "filing_history_status")] - [JsonConverter(typeof(OptionalStringEnumConverter), FilingHistoryStatus.None)] + [JsonPropertyName("filing_history_status")] public FilingHistoryStatus HistoryStatus { get; set; } - [JsonProperty(PropertyName = "etag")] + [JsonPropertyName("etag")] public string ETag { get; set; } - [JsonProperty(PropertyName = "total_count")] + [JsonPropertyName("total_count")] public int TotalCount { get; set; } - [JsonProperty(PropertyName = "items_per_page")] + [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "start_index")] + [JsonPropertyName("start_index")] public int StartIndex { get; set; } - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public FilingHistoryItem[] Items { get; set; } - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs index 00d698e..912e974 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs @@ -1,56 +1,54 @@ -using System; +using System; using CompaniesHouse.Description; using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling { public class FilingHistoryItem : IDescriptable { - [JsonProperty(PropertyName = "category")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("category")] public FilingCategory Category { get; set; } - [JsonProperty(PropertyName = "subcategory")] - [JsonConverter(typeof(StringArrayOrFieldEnumConverter))] + [JsonPropertyName("subcategory")] public FilingSubcategory[] Subcategory { get; set; } - [JsonProperty(PropertyName = "transaction_id")] + [JsonPropertyName("transaction_id")] public string TransactionId { get; set; } - [JsonProperty(PropertyName = "type")] + [JsonPropertyName("type")] public string FilingType { get; set; } - [JsonProperty(PropertyName = "barcode")] + [JsonPropertyName("barcode")] public string Barcode { get; set; } - [JsonProperty(PropertyName = "date")] + [JsonPropertyName("date")] public DateTime? DateOfProcessing { get; set; } - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty(PropertyName = "description_values")] - private JObject DescriptionValues { get; set; } + [JsonInclude] + [JsonPropertyName("description_values")] + private JsonElement? DescriptionValues { get; set; } - [JsonProperty(PropertyName = "pages")] + [JsonPropertyName("pages")] public int? PageCount { get; set; } - [JsonProperty(PropertyName = "paper_filed")] + [JsonPropertyName("paper_filed")] public bool? PaperFiled { get; set; } - [JsonProperty(PropertyName = "annotations")] + [JsonPropertyName("annotations")] public FilingHistoryItemAnnotation[] Annotations { get; set; } - [JsonProperty(PropertyName = "associated_filings")] + [JsonPropertyName("associated_filings")] public FilingHistoryItemAssociatedFiling[] AssociatedFilings { get; set; } - [JsonProperty(PropertyName = "resolutions")] + [JsonPropertyName("resolutions")] public FilingHistoryItemResolution[] Resolutions { get; set; } - [JsonProperty(PropertyName = "links")] + [JsonPropertyName("links")] public Links Links { get; set; } public string GetDescription(string format) diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs index d0bb06e..5229674 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs @@ -1,23 +1,24 @@ -using System; +using System; using CompaniesHouse.Description; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling { public class FilingHistoryItemAnnotation : IDescriptable { - [JsonProperty(PropertyName = "annotation")] + [JsonPropertyName("annotation")] public string Annotation { get; set; } - [JsonProperty(PropertyName = "date")] + [JsonPropertyName("date")] public DateTime? DateOfAnnotation { get; set; } - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty(PropertyName = "description_values")] - private JObject DescriptionValues { get; set; } + [JsonInclude] + [JsonPropertyName("description_values")] + private JsonElement? DescriptionValues { get; set; } public string GetDescription(string format) { diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs index 7c00b84..c99b616 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs @@ -1,21 +1,21 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling { public class FilingHistoryItemAssociatedFiling { - [JsonProperty(PropertyName = "type")] + [JsonPropertyName("type")] public string FilingType { get; set; } - [JsonProperty(PropertyName = "date")] + [JsonPropertyName("date")] public DateTime? Date { get; set; } - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty(PropertyName = "description_values")] + [JsonPropertyName("description_values")] private Dictionary DescriptionValues { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs index 3a79407..eeb6edd 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs @@ -1,36 +1,34 @@ -using System; +using System; using CompaniesHouse.Description; using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling { public class FilingHistoryItemResolution : IDescriptable { - [JsonProperty(PropertyName = "category")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("category")] public ResolutionCategory Category { get; set; } - [JsonProperty(PropertyName = "subcategory")] - [JsonConverter(typeof(StringArrayOrFieldEnumConverter))] + [JsonPropertyName("subcategory")] public FilingSubcategory[] Subcategory { get; set; } - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty(PropertyName = "document_id")] + [JsonPropertyName("document_id")] public string DocumentId { get; set; } - [JsonProperty(PropertyName = "receive_date")] + [JsonPropertyName("receive_date")] public DateTime? DateOfProcessing { get; set; } - [JsonProperty(PropertyName = "type")] + [JsonPropertyName("type")] public string ResolutionType { get; set; } - [JsonProperty(PropertyName = "description_values")] - private JObject DescriptionValues { get; set; } + [JsonInclude] + [JsonPropertyName("description_values")] + private JsonElement? DescriptionValues { get; set; } public string GetDescription(string format) { diff --git a/src/CompaniesHouse/Response/CompanyFiling/Links.cs b/src/CompaniesHouse/Response/CompanyFiling/Links.cs index 80b8d5b..f0d86e0 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/Links.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/Links.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling { public class Links { - [JsonProperty(PropertyName = "self")] + [JsonPropertyName("self")] public string Self { get; set; } - [JsonProperty(PropertyName = "document_metadata")] + [JsonPropertyName("document_metadata")] public string DocumentMetaData { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/AccountingReferenceDate.cs b/src/CompaniesHouse/Response/CompanyProfile/AccountingReferenceDate.cs index c80fecf..25e1658 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/AccountingReferenceDate.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/AccountingReferenceDate.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class AccountingReferenceDate { - [JsonProperty(PropertyName = "day")] + [JsonPropertyName("day")] public int Day { get; set; } - [JsonProperty(PropertyName = "month")] + [JsonPropertyName("month")] public int Month { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs index c58f2da..9ebd3d7 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs @@ -1,31 +1,31 @@ -using Newtonsoft.Json; using System; using CompaniesHouse.JsonConverters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class Accounts { - [JsonProperty(PropertyName = "accounting_reference_date")] + [JsonPropertyName("accounting_reference_date")] public AccountingReferenceDate AccountingReferenceDate { get; set; } - [JsonProperty(PropertyName = "last_accounts")] + [JsonPropertyName("last_accounts")] public LastAccounts LastAccounts { get; set; } - [JsonProperty(PropertyName = "next_accounts")] + [JsonPropertyName("next_accounts")] public NextAccounts NextAccounts { get; set; } - [JsonProperty(PropertyName = "next_due")] + [JsonPropertyName("next_due")] [JsonConverter(typeof(OptionalDateJsonConverter))] [Obsolete("Deprecated - use NextAccounts.DueOn")] public DateTime? NextDue { get; set; } - [JsonProperty(PropertyName = "next_made_up_to")] + [JsonPropertyName("next_made_up_to")] [Obsolete("Deprecated - use NextAccounts.PeriodEndOn")] public DateTime? NextMadeUpTo { get; set; } - [JsonProperty(PropertyName = "overdue")] + [JsonPropertyName("overdue")] [Obsolete("Deprecated - use NextAccounts.Overdue")] public bool? Overdue { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/AnnualReturn.cs b/src/CompaniesHouse/Response/CompanyProfile/AnnualReturn.cs index 0d63b6d..9ddf2af 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/AnnualReturn.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/AnnualReturn.cs @@ -1,22 +1,22 @@ -using System; +using System; using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class AnnualReturn { - [JsonProperty(PropertyName = "last_made_up_to")] + [JsonPropertyName("last_made_up_to")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? LastMadeUpTo { get; set; } - [JsonProperty(PropertyName = "next_due")] + [JsonPropertyName("next_due")] public DateTime? NextDue { get; set; } - [JsonProperty(PropertyName = "next_made_up_to")] + [JsonPropertyName("next_made_up_to")] public DateTime? NextMadeUpTo { get; set; } - [JsonProperty(PropertyName = "overdue")] + [JsonPropertyName("overdue")] public bool? Overdue { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs b/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs index 05dcc1c..cf5d16a 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs @@ -1,14 +1,14 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class BranchCompanyDetails { - [JsonProperty(PropertyName = "business_activity")] + [JsonPropertyName("business_activity")] public string BusinessActivity { get; set; } - [JsonProperty(PropertyName = "parent_company_name")] + [JsonPropertyName("parent_company_name")] public string ParentCompanyName { get; set; } - [JsonProperty(PropertyName = "parent_company_number")] + [JsonPropertyName("parent_company_number")] public string ParentCompanyNumber { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs index e36b5c8..f52213c 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs @@ -1,91 +1,86 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using System; using CompaniesHouse.JsonConverters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class CompanyProfile { - [JsonProperty(PropertyName = "type")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("type")] public CompanyType Type { get; set; } - [JsonProperty(PropertyName = "etag")] + [JsonPropertyName("etag")] public string ETag { get; set; } - [JsonProperty(PropertyName = "accounts")] + [JsonPropertyName("accounts")] public Accounts Accounts { get; set; } - [JsonProperty(PropertyName = "annual_return")] + [JsonPropertyName("annual_return")] public AnnualReturn AnnualReturn { get; set; } - [JsonProperty(PropertyName = "confirmation_statement")] + [JsonPropertyName("confirmation_statement")] public ConfirmationStatement ConfirmationStatement { get; set; } - [JsonProperty(PropertyName = "can_file")] + [JsonPropertyName("can_file")] public bool? CanFile { get; set; } - [JsonProperty(PropertyName = "company_name")] + [JsonPropertyName("company_name")] public string CompanyName { get; set; } - [JsonProperty(PropertyName = "company_number")] + [JsonPropertyName("company_number")] public string CompanyNumber { get; set; } - [JsonProperty(PropertyName = "company_status")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } - [JsonProperty(PropertyName = "company_status_detail")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("company_status_detail")] public CompanyStatusDetail CompanyStatusDetail { get; set; } - [JsonProperty(PropertyName = "date_of_creation")] + [JsonPropertyName("date_of_creation")] public DateTime? DateOfCreation { get; set; } - [JsonProperty(PropertyName = "date_of_cessation")] + [JsonPropertyName("date_of_cessation")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? DateOfCessation { get; set; } - [JsonProperty(PropertyName = "has_been_liquidated")] + [JsonPropertyName("has_been_liquidated")] public bool? HasBeenLiquidated { get; set; } - [JsonProperty(PropertyName = "has_charges")] + [JsonPropertyName("has_charges")] public bool? HasCharges { get; set; } - [JsonProperty(PropertyName = "has_insolvency_history")] + [JsonPropertyName("has_insolvency_history")] public bool? HasInsolvencyHistory { get; set; } - [JsonProperty(PropertyName = "is_community_interest_company")] + [JsonPropertyName("is_community_interest_company")] public bool? IsCommunityInterestCompany { get; set; } - [JsonProperty(PropertyName = "jurisdiction")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("jurisdiction")] public Jurisdiction Jurisdiction { get; set; } - [JsonProperty(PropertyName = "last_full_members_list_date")] + [JsonPropertyName("last_full_members_list_date")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? LastFullMembersListDate { get; set; } - [JsonProperty(PropertyName = "links")] + [JsonPropertyName("links")] public CompanyProfileLinks Links { get; set; } - [JsonProperty(PropertyName = "previous_company_names")] + [JsonPropertyName("previous_company_names")] public PreviousCompanyName[] PreviousCompanyNames { get; set; } - [JsonProperty(PropertyName = "registered_office_address")] + [JsonPropertyName("registered_office_address")] public Address RegisteredOfficeAddress { get; set; } - [JsonProperty(PropertyName = "registered_office_is_in_dispute")] + [JsonPropertyName("registered_office_is_in_dispute")] public bool? RegisteredOfficeIsInDispute { get; set; } - [JsonProperty(PropertyName = "sic_codes")] + [JsonPropertyName("sic_codes")] public string[] SicCodes { get; set; } - [JsonProperty(PropertyName = "undeliverable_registered_office_address")] + [JsonPropertyName("undeliverable_registered_office_address")] public bool? UndeliverableRegisteredOfficeAddress { get; set; } - [JsonProperty(PropertyName = "branch_company_details")] + [JsonPropertyName("branch_company_details")] public BranchCompanyDetails BranchCompanyDetails { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs index d1ea894..a031cc7 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs @@ -1,31 +1,31 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class CompanyProfileLinks { - [JsonProperty(PropertyName = "charges")] + [JsonPropertyName("charges")] public string Charges { get; set; } - [JsonProperty(PropertyName = "filing_history")] + [JsonPropertyName("filing_history")] public string FilingHistory { get; set; } - [JsonProperty(PropertyName = "insolvency")] + [JsonPropertyName("insolvency")] public string Insolvency { get; set; } - [JsonProperty(PropertyName = "officers")] + [JsonPropertyName("officers")] public string Officers { get; set; } - [JsonProperty(PropertyName = "persons_with_significant_control")] + [JsonPropertyName("persons_with_significant_control")] public string PersonsWithSignificantControl { get; set; } - [JsonProperty(PropertyName = "persons_with_significant_control_statements")] + [JsonPropertyName("persons_with_significant_control_statements")] public string PersonsWithSignificantControlStatements { get; set; } - [JsonProperty(PropertyName = "registers")] + [JsonPropertyName("registers")] public string Registers { get; set; } - [JsonProperty(PropertyName = "self")] + [JsonPropertyName("self")] public string Self { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/ConfirmationStatement.cs b/src/CompaniesHouse/Response/CompanyProfile/ConfirmationStatement.cs index 8821964..146305d 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/ConfirmationStatement.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/ConfirmationStatement.cs @@ -1,24 +1,24 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class ConfirmationStatement { - [JsonProperty(PropertyName = "last_made_up_to")] + [JsonPropertyName("last_made_up_to")] public DateTime? LastMadeUpTo { get; set; } - [JsonProperty(PropertyName = "next_due")] + [JsonPropertyName("next_due")] public DateTime? NextDue { get; set; } - [JsonProperty(PropertyName = "next_made_up_to")] + [JsonPropertyName("next_made_up_to")] public DateTime? NextMadeUpTo { get; set; } - [JsonProperty(PropertyName = "overdue")] + [JsonPropertyName("overdue")] public bool? Overdue { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs b/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs index 3901b9d..ca623a0 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs @@ -30,4 +30,4 @@ public enum Jurisdiction [EnumMember(Value = "noneu")] NonEu } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/LastAccounts.cs b/src/CompaniesHouse/Response/CompanyProfile/LastAccounts.cs index 1b51b72..79881e4 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/LastAccounts.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/LastAccounts.cs @@ -1,26 +1,24 @@ -using System; +using System; using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class LastAccounts { - [JsonProperty(PropertyName = "made_up_to")] + [JsonPropertyName("made_up_to")] [Obsolete("Deprecated - use PeriodEndOn")] public DateTime? MadeUpTo { get; set; } - [JsonProperty(PropertyName = "period_end_on")] + [JsonPropertyName("period_end_on")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? PeriodEndOn { get; set; } - [JsonProperty(PropertyName = "period_start_on")] + [JsonPropertyName("period_start_on")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? PeriodStartOn { get; set; } - [JsonProperty(PropertyName = "type")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("type")] public LastAccountsType Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/LastAccountsType.cs b/src/CompaniesHouse/Response/CompanyProfile/LastAccountsType.cs index cb12e85..4dfbab4 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/LastAccountsType.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/LastAccountsType.cs @@ -48,4 +48,4 @@ public enum LastAccountsType [EnumMember(Value = "no-accounts-type-available")] NoAccountsTypeAvailable } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/NextAccounts.cs b/src/CompaniesHouse/Response/CompanyProfile/NextAccounts.cs index f2c7e5c..ec1b70a 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/NextAccounts.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/NextAccounts.cs @@ -1,25 +1,25 @@ -using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using CompaniesHouse.JsonConverters; using System; using System.Collections.Generic; using System.Text; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class NextAccounts { - [JsonProperty(PropertyName = "due_on")] + [JsonPropertyName("due_on")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? DueOn { get; set; } - [JsonProperty(PropertyName = "overdue")] + [JsonPropertyName("overdue")] public bool? Overdue { get; set; } - [JsonProperty(PropertyName = "period_end_on")] + [JsonPropertyName("period_end_on")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? PeriodEndOn { get; set; } - [JsonProperty(PropertyName = "period_start_on")] + [JsonPropertyName("period_start_on")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? PeriodStartOn { get; set; } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs index 3510b31..4baa49d 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs @@ -1,20 +1,20 @@ -using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using CompaniesHouse.JsonConverters; using System; using System.Collections.Generic; using System.Text; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class PreviousCompanyName { - [JsonProperty(PropertyName = "name")] + [JsonPropertyName("name")] public string Name { get; set; } - [JsonProperty(PropertyName = "ceased_on")] + [JsonPropertyName("ceased_on")] public DateTime CeasedOn { get; set; } - [JsonProperty(PropertyName = "effective_from")] + [JsonPropertyName("effective_from")] public DateTime EffectiveFrom { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyStatusDetail.cs b/src/CompaniesHouse/Response/CompanyStatusDetail.cs index e2d6cc8..8a67f2c 100644 --- a/src/CompaniesHouse/Response/CompanyStatusDetail.cs +++ b/src/CompaniesHouse/Response/CompanyStatusDetail.cs @@ -27,4 +27,4 @@ public enum CompanyStatusDetail [EnumMember(Value = "converted-to-uk-societas")] ConvertedToUnitedKingdomSocietas } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/DateOfBirth.cs b/src/CompaniesHouse/Response/DateOfBirth.cs index 8a6f4b3..7c172c5 100644 --- a/src/CompaniesHouse/Response/DateOfBirth.cs +++ b/src/CompaniesHouse/Response/DateOfBirth.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response { public class DateOfBirth { - [JsonProperty(PropertyName = "day")] + [JsonPropertyName("day")] public int? Day { get; set; } - [JsonProperty(PropertyName = "month")] + [JsonPropertyName("month")] public int? Month { get; set; } - [JsonProperty(PropertyName = "year")] + [JsonPropertyName("year")] public int? Year { get; set; } } } diff --git a/src/CompaniesHouse/Response/Document/DocumentDownload.cs b/src/CompaniesHouse/Response/Document/DocumentDownload.cs index e674590..14a08e2 100644 --- a/src/CompaniesHouse/Response/Document/DocumentDownload.cs +++ b/src/CompaniesHouse/Response/Document/DocumentDownload.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; namespace CompaniesHouse.Response.Document { @@ -8,4 +8,4 @@ public class DocumentDownload public string ContentType { get; set; } public long? ContentLength { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Document/DocumentMetadata.cs b/src/CompaniesHouse/Response/Document/DocumentMetadata.cs index cd76ecb..c719640 100644 --- a/src/CompaniesHouse/Response/Document/DocumentMetadata.cs +++ b/src/CompaniesHouse/Response/Document/DocumentMetadata.cs @@ -1,29 +1,29 @@ -using System.Collections.Generic; -using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Document { public class DocumentMetadata { - [JsonProperty("company_number")] + [JsonPropertyName("company_number")] public string CompanyNumber { get; set; } - [JsonProperty("barcode")] + [JsonPropertyName("barcode")] public string Barcode { get; set; } - [JsonProperty("significant_date")] + [JsonPropertyName("significant_date")] public object SignificantDate { get; set; } - [JsonProperty("significant_date_type")] + [JsonPropertyName("significant_date_type")] public string SignificantDateType { get; set; } - [JsonProperty("category")] + [JsonPropertyName("category")] public string Category { get; set; } - [JsonProperty("pages")] + [JsonPropertyName("pages")] public int Pages { get; set; } - [JsonProperty("created_at")] + [JsonPropertyName("created_at")] public string CreatedAt { get; set; } - [JsonProperty("etag")] + [JsonPropertyName("etag")] public string Etag { get; set; } - [JsonProperty("links")] + [JsonPropertyName("links")] public Links Links { get; set; } - [JsonProperty("resources")] + [JsonPropertyName("resources")] public Dictionary Resources { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs b/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs index 49d0f4f..29a64bc 100644 --- a/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs +++ b/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Document { public class DocumentMetadataContentLength { - [JsonProperty("content_length")] + [JsonPropertyName("content_length")] public int ContentLength { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Document/Links.cs b/src/CompaniesHouse/Response/Document/Links.cs index b3de004..be07db8 100644 --- a/src/CompaniesHouse/Response/Document/Links.cs +++ b/src/CompaniesHouse/Response/Document/Links.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Document { public class Links { - [JsonProperty("self")] + [JsonPropertyName("self")] public string Self { get; set; } - [JsonProperty("document")] + [JsonPropertyName("document")] public string Document { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Insolvency/Address.cs b/src/CompaniesHouse/Response/Insolvency/Address.cs index 2125d12..c7ea930 100644 --- a/src/CompaniesHouse/Response/Insolvency/Address.cs +++ b/src/CompaniesHouse/Response/Insolvency/Address.cs @@ -1,25 +1,25 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Insolvency { public class Address { - [JsonProperty("address_line_1")] + [JsonPropertyName("address_line_1")] public string AddressLine1 { get; set; } - [JsonProperty("address_line_2")] + [JsonPropertyName("address_line_2")] public string AddressLine2 { get; set; } - [JsonProperty("country")] + [JsonPropertyName("country")] public string Country { get; set; } - [JsonProperty("locality")] + [JsonPropertyName("locality")] public string Locality { get; set; } - [JsonProperty("postal_code")] + [JsonPropertyName("postal_code")] public string PostalCode { get; set; } - [JsonProperty("region")] + [JsonPropertyName("region")] public string Region { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Insolvency/Case.cs b/src/CompaniesHouse/Response/Insolvency/Case.cs index b94c88d..3db1ea8 100644 --- a/src/CompaniesHouse/Response/Insolvency/Case.cs +++ b/src/CompaniesHouse/Response/Insolvency/Case.cs @@ -1,25 +1,25 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Insolvency { public class Case { - [JsonProperty("dates")] + [JsonPropertyName("dates")] public CaseDate[] Dates { get; set; } - [JsonProperty("links")] + [JsonPropertyName("links")] public Links Links { get; set; } - [JsonProperty("notes")] + [JsonPropertyName("notes")] public string[] Notes { get; set; } - [JsonProperty("number")] + [JsonPropertyName("number")] public int Number { get; set; } - [JsonProperty("practitioners")] + [JsonPropertyName("practitioners")] public Practitioner[] Practitioners { get; set; } - [JsonProperty("type")] + [JsonPropertyName("type")] public string Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Insolvency/CaseDate.cs b/src/CompaniesHouse/Response/Insolvency/CaseDate.cs index b3aaa14..0bbeaf2 100644 --- a/src/CompaniesHouse/Response/Insolvency/CaseDate.cs +++ b/src/CompaniesHouse/Response/Insolvency/CaseDate.cs @@ -1,16 +1,14 @@ using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Insolvency { public class CaseDate { - [JsonProperty("date")] + [JsonPropertyName("date")] public DateTime Date { get; set; } - [JsonProperty("type")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("type")] public CaseDateType Type { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Insolvency/CaseDateType.cs b/src/CompaniesHouse/Response/Insolvency/CaseDateType.cs index e5e07b1..8cd9841 100644 --- a/src/CompaniesHouse/Response/Insolvency/CaseDateType.cs +++ b/src/CompaniesHouse/Response/Insolvency/CaseDateType.cs @@ -55,4 +55,4 @@ public enum CaseDateType [EnumMember(Value = "dissolved-on")] DissolvedOn, } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs b/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs index 5052ac8..3f289a6 100644 --- a/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs +++ b/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Insolvency { public class CompanyInsolvencyInformation { - [JsonProperty("cases")] + [JsonPropertyName("cases")] public Case[] Cases { get; set; } - [JsonProperty("etag")] + [JsonPropertyName("etag")] public string Etag { get; set; } - [JsonProperty("status")] + [JsonPropertyName("status")] public InsolvencyStatus[] Status { get; set; } } } diff --git a/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs b/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs index db486e0..cec9473 100644 --- a/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs +++ b/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs @@ -1,10 +1,7 @@ using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; namespace CompaniesHouse.Response.Insolvency { - [JsonConverter(typeof(StringEnumConverter))] public enum InsolvencyStatus { [EnumMember(Value = "")] @@ -43,4 +40,4 @@ public enum InsolvencyStatus [EnumMember(Value = "liquidation")] Liquidation, } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Insolvency/Links.cs b/src/CompaniesHouse/Response/Insolvency/Links.cs index bb3cf21..3d38762 100644 --- a/src/CompaniesHouse/Response/Insolvency/Links.cs +++ b/src/CompaniesHouse/Response/Insolvency/Links.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Insolvency { public class Links { - [JsonProperty("charge")] + [JsonPropertyName("charge")] public string Charge { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Insolvency/Practitioner.cs b/src/CompaniesHouse/Response/Insolvency/Practitioner.cs index fe56bd5..9d2eea7 100644 --- a/src/CompaniesHouse/Response/Insolvency/Practitioner.cs +++ b/src/CompaniesHouse/Response/Insolvency/Practitioner.cs @@ -1,23 +1,23 @@ using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Insolvency { public class Practitioner { - [JsonProperty("address")] + [JsonPropertyName("address")] public Address Address { get; set; } - [JsonProperty("appointed_on")] + [JsonPropertyName("appointed_on")] public DateTime AppointedOn { get; set; } - [JsonProperty("ceased_to_act_on")] + [JsonPropertyName("ceased_to_act_on")] public DateTime CeasedToActOn { get; set; } - [JsonProperty("name")] + [JsonPropertyName("name")] public string Name { get; set; } - [JsonProperty("role")] + [JsonPropertyName("role")] public string Role { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/Officer.cs b/src/CompaniesHouse/Response/Officers/Officer.cs index 7741a96..aa74b15 100644 --- a/src/CompaniesHouse/Response/Officers/Officer.cs +++ b/src/CompaniesHouse/Response/Officers/Officer.cs @@ -1,47 +1,44 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using System; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class Officer { - [JsonProperty(PropertyName = "appointed_on")] + [JsonPropertyName("appointed_on")] public DateTime? AppointedOn { get; set; } - [JsonProperty(PropertyName = "resigned_on")] + [JsonPropertyName("resigned_on")] public DateTime? ResignedOn { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] + [JsonPropertyName("date_of_birth")] public OfficerDateOfBirth DateOfBirth { get; set; } - [JsonProperty(PropertyName = "name")] + [JsonPropertyName("name")] public string Name { get; set; } - [JsonProperty(PropertyName = "officer_role")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("officer_role")] public OfficerRole OfficerRole { get; set; } - [JsonProperty(PropertyName = "nationality")] + [JsonPropertyName("nationality")] public string Nationality { get; set; } - [JsonProperty(PropertyName = "occupation")] + [JsonPropertyName("occupation")] public string Occupation { get; set; } - [JsonProperty(PropertyName = "address")] + [JsonPropertyName("address")] public Address Address { get; set; } - [JsonProperty(PropertyName = "country_of_residence")] + [JsonPropertyName("country_of_residence")] public string CountryOfResidence { get; set; } - [JsonProperty(PropertyName = "former_names")] + [JsonPropertyName("former_names")] public OfficerFormerName[] FormerNames { get; set; } - [JsonProperty(PropertyName = "identification")] + [JsonPropertyName("identification")] public OfficerIdentification Identification { get; set; } - [JsonProperty(PropertyName = "links")] + [JsonPropertyName("links")] public OfficerLinks Links { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs b/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs index 6c54051..accdce4 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs @@ -1,12 +1,12 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class OfficerAppointmentLink { - [JsonProperty(PropertyName = "appointments")] + [JsonPropertyName("appointments")] public string AppointmentsResource { get; set; } public string OfficerId => AppointmentsResource?.Split('/')[2]; } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs b/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs index 8a50c3c..df9f082 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs @@ -1,17 +1,17 @@ -using System; -using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class OfficerDateOfBirth { - [JsonProperty(PropertyName = "day")] + [JsonPropertyName("day")] public int? Day { get; set; } - [JsonProperty(PropertyName = "month")] + [JsonPropertyName("month")] public int? Month { get; set; } - [JsonProperty(PropertyName = "year")] + [JsonPropertyName("year")] public int? Year { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs b/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs index 9800df6..fa7f475 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class OfficerFormerName { - [JsonProperty(PropertyName = "forenames")] + [JsonPropertyName("forenames")] public string ForeNames { get; set; } - [JsonProperty(PropertyName = "surname")] + [JsonPropertyName("surname")] public string Surname { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs b/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs index 7a26400..dd858ef 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs @@ -1,22 +1,22 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class OfficerIdentification { - [JsonProperty(PropertyName = "identification_type")] + [JsonPropertyName("identification_type")] public string IdentificationType { get; set; } - [JsonProperty(PropertyName = "legal_authority")] + [JsonPropertyName("legal_authority")] public string LegalAuthority { get; set; } - [JsonProperty(PropertyName = "legal_form")] + [JsonPropertyName("legal_form")] public string LegalForm { get; set; } - [JsonProperty(PropertyName = "place_registered")] + [JsonPropertyName("place_registered")] public string PlaceRegistered { get; set; } - [JsonProperty(PropertyName = "registration_number")] + [JsonPropertyName("registration_number")] public string RegistrationNumber { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerLinks.cs b/src/CompaniesHouse/Response/Officers/OfficerLinks.cs index ed58eec..9776420 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerLinks.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerLinks.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class OfficerLinks { - [JsonProperty(PropertyName = "officer")] + [JsonPropertyName("officer")] public OfficerAppointmentLink Officer { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerRole.cs b/src/CompaniesHouse/Response/Officers/OfficerRole.cs index af1ff61..0cd8950 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerRole.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerRole.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace CompaniesHouse.Response.Officers { @@ -96,4 +96,4 @@ public enum OfficerRole [EnumMember(Value = "secretary")] Secretary } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/Officers.cs b/src/CompaniesHouse/Response/Officers/Officers.cs index 2114001..8507be2 100644 --- a/src/CompaniesHouse/Response/Officers/Officers.cs +++ b/src/CompaniesHouse/Response/Officers/Officers.cs @@ -1,22 +1,22 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class Officers { - [JsonProperty(PropertyName = "active_count")] + [JsonPropertyName("active_count")] public int? ActiveCount { get; set; } - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public Officer[] Items { get; set; } - [JsonProperty(PropertyName = "resigned_count")] + [JsonPropertyName("resigned_count")] public int? ResignedCount { get; set; } - [JsonProperty(PropertyName = "total_results")] + [JsonPropertyName("total_results")] public int TotalResults { get; set; } - [JsonProperty(PropertyName = "start_index")] + [JsonPropertyName("start_index")] public int StartIndex { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/ParticularType.cs b/src/CompaniesHouse/Response/ParticularType.cs index c908eba..e33491f 100644 --- a/src/CompaniesHouse/Response/ParticularType.cs +++ b/src/CompaniesHouse/Response/ParticularType.cs @@ -19,4 +19,4 @@ public enum ParticularType [EnumMember(Value = "brief-description")] BriefDescription } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs index 49f03d2..38ee7d0 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs @@ -1,50 +1,48 @@ -using CompaniesHouse.Response.Appointments; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using CompaniesHouse.Response.Appointments; using System; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.PersonsWithSignificantControl { public class PersonWithSignificantControl { - [JsonProperty(PropertyName = "address")] + [JsonPropertyName("address")] public Address Address { get; set; } - [JsonProperty(PropertyName = "ceased_on")] + [JsonPropertyName("ceased_on")] public DateTime CeasedOn { get; set; } - [JsonProperty(PropertyName = "country_of_residence")] + [JsonPropertyName("country_of_residence")] public string CountryOfResidence { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] + [JsonPropertyName("date_of_birth")] public DateOfBirth DateOfBirth { get; set; } - [JsonProperty(PropertyName = "etag")] + [JsonPropertyName("etag")] public string ETag { get; set; } - [JsonProperty(PropertyName = "kind")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("kind")] public PersonWithSignificantControlKind Kind { get; set; } - [JsonProperty(PropertyName = "links")] + [JsonPropertyName("links")] public PersonWithSignificantControlLinks Links { get; set; } - [JsonProperty(PropertyName = "name")] + [JsonPropertyName("name")] public string Name { get; set; } - [JsonProperty(PropertyName = "name_elements")] + [JsonPropertyName("name_elements")] public NameElements NameElements { get; set; } - [JsonProperty(PropertyName = "nationality")] + [JsonPropertyName("nationality")] public string Nationality { get; set; } - [JsonProperty(PropertyName = "natures_of_control", ItemConverterType = typeof(StringEnumConverter))] + [JsonPropertyName("natures_of_control")] public PersonWithSignificantControlNatureOfControl[] NaturesOfControl { get; set; } - [JsonProperty(PropertyName = "notified_on")] + [JsonPropertyName("notified_on")] public DateTime NotifiedOn { get; set; } - [JsonProperty(PropertyName = "identification")] + [JsonPropertyName("identification")] public PersonWithSignificantControlIdentification Identification { get; set; } } } diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs index 738f5eb..94dbac3 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs @@ -1,21 +1,21 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.PersonsWithSignificantControl { public class PersonWithSignificantControlIdentification { - [JsonProperty(PropertyName = "legal_authority")] + [JsonPropertyName("legal_authority")] public string LegalAuthority { get; set; } - [JsonProperty(PropertyName = "legal_form")] + [JsonPropertyName("legal_form")] public string LegalForm { get; set; } - [JsonProperty(PropertyName = "place_registered")] + [JsonPropertyName("place_registered")] public string PlaceRegistered { get; set; } - [JsonProperty(PropertyName = "registration_number")] + [JsonPropertyName("registration_number")] public string RegistrationNumber { get; set; } - [JsonProperty("country_registered")] public string CountryRegistered { get; set; } + [JsonPropertyName("country_registered")] public string CountryRegistered { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs index 79a67fa..c572bbe 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace CompaniesHouse.Response.PersonsWithSignificantControl { @@ -28,4 +28,4 @@ public enum PersonWithSignificantControlKind [EnumMember(Value = "legal-person-beneficial-owner")] LegalPersonBeneficialOwner, } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs index 4dfb17f..0557596 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.PersonsWithSignificantControl { public class PersonWithSignificantControlLinks { - [JsonProperty(PropertyName = "self")] + [JsonPropertyName("self")] public string Self { get; set; } - [JsonProperty(PropertyName = "statement")] + [JsonPropertyName("statement")] public string Statement { get; set; } } } diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs index c89c1ed..7225ce0 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs @@ -1,4 +1,4 @@ -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace CompaniesHouse.Response.PersonsWithSignificantControl { @@ -149,4 +149,4 @@ public enum PersonWithSignificantControlNatureOfControl [EnumMember(Value = "right-to-appoint-and-remove-directors-as-firm-registered-overseas-entity")] RightToAppointAndRemoveDirectorsAsFirmRegisteredOverseasEntity, } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs index bc763b7..bfe9c2d 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.PersonsWithSignificantControl { public class PersonsWithSignificantControl { - [JsonProperty(PropertyName = "active_count")] + [JsonPropertyName("active_count")] public int? ActiveCount { get; set; } - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public PersonWithSignificantControl[] Items { get; set; } - [JsonProperty(PropertyName = "ceased_count")] + [JsonPropertyName("ceased_count")] public int? CeasedCount { get; set; } } } diff --git a/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs b/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs index 7c3aa6f..b0290f4 100644 --- a/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs +++ b/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.RegisteredOfficeAddress { public class Links { - [JsonProperty(PropertyName = "self")] + [JsonPropertyName("self")] public string Self { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs b/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs index b6ab419..1073c3c 100644 --- a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs +++ b/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs @@ -1,42 +1,40 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.RegisteredOfficeAddress { public class OfficeAddress { - [JsonProperty(PropertyName = "address_line_1")] + [JsonPropertyName("address_line_1")] public string AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] + [JsonPropertyName("address_line_2")] public string AddressLine2 { get; set; } - [JsonProperty(PropertyName = "country")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("country")] public OfficeAddressCountry Country { get; set; } - [JsonProperty(PropertyName = "etag")] + [JsonPropertyName("etag")] public string Etag { get; set; } - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } - [JsonProperty(PropertyName = "links")] + [JsonPropertyName("links")] public Links Links { get; set; } - [JsonProperty(PropertyName = "locality")] + [JsonPropertyName("locality")] public string Locality { get; set; } - [JsonProperty(PropertyName = "po_box")] + [JsonPropertyName("po_box")] public string PoBox { get; set; } - [JsonProperty(PropertyName = "postal_code")] + [JsonPropertyName("postal_code")] public string PostalCode { get; set; } - [JsonProperty(PropertyName = "Premises")] + [JsonPropertyName("premises")] public string Premises { get; set; } - [JsonProperty(PropertyName = "region")] + [JsonPropertyName("region")] public string Region { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs b/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs index 2d0a903..c93e08a 100644 --- a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs +++ b/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs @@ -25,4 +25,4 @@ public enum OfficeAddressCountry [EnumMember(Value = "United Kingdom")] UnitedKingdom } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Address.cs b/src/CompaniesHouse/Response/Search/AllSearch/Address.cs index 4881785..842a262 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Address.cs @@ -1,31 +1,31 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.AllSearch { public class Address { - [JsonProperty(PropertyName = "address_line_1")] + [JsonPropertyName("address_line_1")] public string AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] + [JsonPropertyName("address_line_2")] public string AddressLine2 { get; set; } - [JsonProperty(PropertyName = "care_of")] + [JsonPropertyName("care_of")] public string CareOf { get; set; } - [JsonProperty(PropertyName = "country")] + [JsonPropertyName("country")] public string Country { get; set; } - [JsonProperty(PropertyName = "locality")] + [JsonPropertyName("locality")] public string Locality { get; set; } - [JsonProperty(PropertyName = "po_box")] + [JsonPropertyName("po_box")] public string PoBox { get; set; } - [JsonProperty(PropertyName = "postal_code")] + [JsonPropertyName("postal_code")] public string PostalCode { get; set; } - [JsonProperty(PropertyName = "region")] + [JsonPropertyName("region")] public string Region { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs index 4e02e65..f4faa07 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs @@ -1,26 +1,26 @@ -using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; +using CompaniesHouse.JsonConverters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.AllSearch { public class AllSearch { - [JsonProperty(PropertyName = "etag")] + [JsonPropertyName("etag")] public string Etag { get; set; } - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public SearchItem[] Items { get; set; } - [JsonProperty(PropertyName = "items_per_page")] - public string ItemsPerPage { get; set; } + [JsonPropertyName("items_per_page")] + public int? ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } - [JsonProperty(PropertyName = "start_index")] - public string StartIndex { get; set; } + [JsonPropertyName("start_index")] + public int? StartIndex { get; set; } - [JsonProperty(PropertyName = "total_results")] - public string TotalResults { get; set; } + [JsonPropertyName("total_results")] + public int? TotalResults { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Item.cs b/src/CompaniesHouse/Response/Search/AllSearch/Item.cs index 5c44968..d8343ae 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Item.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Item.cs @@ -12,4 +12,4 @@ public class Item public string snippet { get; set; } public string title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Links.cs b/src/CompaniesHouse/Response/Search/AllSearch/Links.cs index 81d24b0..bf8fc34 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Links.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Links.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.AllSearch { public class Links { - [JsonProperty(PropertyName = "self")] + [JsonPropertyName("self")] public string Self { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs b/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs index 02ef4b0..c90e7d4 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs @@ -1,14 +1,14 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.AllSearch { public class Matches { - [JsonProperty(PropertyName = "address_snippet")] + [JsonPropertyName("address_snippet")] public string[] AddressSnippet { get; set; } - [JsonProperty(PropertyName = "snippet")] + [JsonPropertyName("snippet")] public string[] Snippet { get; set; } - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public string[] Title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs index 661b873..c240104 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs @@ -1,46 +1,43 @@ using System; using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.CompanySearch { public class Company : SearchItem { - [JsonProperty(PropertyName = "address")] + [JsonPropertyName("address")] public Address Address { get; set; } - [JsonProperty(PropertyName = "company_number")] + [JsonPropertyName("company_number")] public string CompanyNumber { get; set; } - [JsonProperty(PropertyName = "company_status")] - [JsonConverter(typeof(OptionalStringEnumConverter), CompanyStatus.None)] + [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } - [JsonProperty(PropertyName = "company_type")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("company_type")] public CompanyType CompanyType { get; set; } - [JsonProperty(PropertyName = "date_of_cessation")] + [JsonPropertyName("date_of_cessation")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? DateOfCessation { get; set; } - [JsonProperty(PropertyName = "date_of_creation")] + [JsonPropertyName("date_of_creation")] public DateTime? DateOfCreation { get; set; } - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty(PropertyName = "description_identifier")] + [JsonPropertyName("description_identifier")] public object[] DescriptionIdentifier { get; set; } - [JsonProperty(PropertyName = "matches")] + [JsonPropertyName("matches")] public Matches Matches { get; set; } - [JsonProperty(PropertyName = "snippet")] + [JsonPropertyName("snippet")] public string Snippet { get; set; } - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public string Title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs b/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs index feaed22..86d20f8 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs @@ -1,28 +1,28 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.CompanySearch { public class CompanySearch { - [JsonProperty(PropertyName = "etag")] + [JsonPropertyName("etag")] public string ETag { get; set; } - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public Company[] Companies { get; set; } - [JsonProperty(PropertyName = "items_per_page")] + [JsonPropertyName("items_per_page")] public int? ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } - [JsonProperty(PropertyName = "page_number")] + [JsonPropertyName("page_number")] public int? PageNumber { get; set; } - [JsonProperty(PropertyName = "start_index")] + [JsonPropertyName("start_index")] public int? StartIndex { get; set; } - [JsonProperty(PropertyName = "total_results")] + [JsonPropertyName("total_results")] public int? TotalResults { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs index b8c0601..b1dc53e 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.CompanySearch { public class Matches { - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public int[] Title { get; set; } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs index 4e35fcc..5924b82 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs @@ -1,28 +1,28 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch { public class Address { - [JsonProperty(PropertyName = "address_line_1")] + [JsonPropertyName("address_line_1")] public string AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] + [JsonPropertyName("address_line_2")] public string AddressLine2 { get; set; } - [JsonProperty(PropertyName = "country")] + [JsonPropertyName("country")] public string Country { get; set; } - [JsonProperty(PropertyName = "locality")] + [JsonPropertyName("locality")] public string Locality { get; set; } - [JsonProperty(PropertyName = "postal_code")] + [JsonPropertyName("postal_code")] public string PostalCode { get; set; } - [JsonProperty(PropertyName = "premises")] + [JsonPropertyName("premises")] public string Premises { get; set; } - [JsonProperty(PropertyName = "region")] + [JsonPropertyName("region")] public string Region { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs index 859f2a3..88b9cb4 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs @@ -1,32 +1,32 @@ -using System; -using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch { public class DisqualifiedOfficer : SearchItem { - [JsonProperty(PropertyName = "address")] + [JsonPropertyName("address")] public Address Address { get; set; } - [JsonProperty(PropertyName = "address_snippet")] + [JsonPropertyName("address_snippet")] public string AddressSnippet { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] + [JsonPropertyName("date_of_birth")] public DateTime DateOfBirth { get; set; } - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty(PropertyName = "description_identifiers")] + [JsonPropertyName("description_identifiers")] public string[] DescriptionIdentifiers { get; set; } - [JsonProperty(PropertyName = "matches")] + [JsonPropertyName("matches")] public Match Matches { get; set; } - [JsonProperty(PropertyName = "snippet")] + [JsonPropertyName("snippet")] public string Snippet { get; set; } - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public string Title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs index bb2d6e8..aeb4605 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs @@ -1,22 +1,22 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch { public class DisqualifiedOfficerSearch { - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public DisqualifiedOfficer[] DisqualifiedOfficers { get; set; } - [JsonProperty(PropertyName = "items_per_page")] + [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } - [JsonProperty(PropertyName = "start_index")] + [JsonPropertyName("start_index")] public int StartIndex { get; set; } - [JsonProperty(PropertyName = "total_results")] + [JsonPropertyName("total_results")] public int TotalResults { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs index 99d4181..2259bf2 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch { public class Match { - [JsonProperty(PropertyName = "address_snippet")] + [JsonPropertyName("address_snippet")] public string[] AddressSnippet { get; set; } - [JsonProperty(PropertyName = "snippet")] + [JsonPropertyName("snippet")] public string[] Snippet { get; set; } - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public string[] Title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/Links.cs b/src/CompaniesHouse/Response/Search/Links.cs index b88f055..e51974e 100644 --- a/src/CompaniesHouse/Response/Search/Links.cs +++ b/src/CompaniesHouse/Response/Search/Links.cs @@ -1,10 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search { public class Links { - [JsonProperty(PropertyName = "self")] + [JsonPropertyName("self")] public string Self { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs index 1589e29..cd4fca6 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs @@ -1,34 +1,34 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.OfficerSearch { public class Address { - [JsonProperty(PropertyName = "address_line_1")] + [JsonPropertyName("address_line_1")] public string AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] + [JsonPropertyName("address_line_2")] public string AddressLine2 { get; set; } - [JsonProperty(PropertyName = "care_of")] + [JsonPropertyName("care_of")] public string CareOf { get; set; } - [JsonProperty(PropertyName = "country")] + [JsonPropertyName("country")] public string Country { get; set; } - [JsonProperty(PropertyName = "locality")] + [JsonPropertyName("locality")] public string Locality { get; set; } - [JsonProperty(PropertyName = "po_box")] + [JsonPropertyName("po_box")] public string PoBox { get; set; } - [JsonProperty(PropertyName = "postal_code")] + [JsonPropertyName("postal_code")] public string PostalCode { get; set; } - [JsonProperty(PropertyName = "premises")] + [JsonPropertyName("premises")] public string Premises { get; set; } - [JsonProperty(PropertyName = "region")] + [JsonPropertyName("region")] public string Region { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/DateOfBirth.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/DateOfBirth.cs index 5316cc2..a832b8f 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/DateOfBirth.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/DateOfBirth.cs @@ -1,13 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.OfficerSearch { public class DateOfBirth { - [JsonProperty(PropertyName = "month")] + [JsonPropertyName("month")] public int Month { get; set; } - [JsonProperty(PropertyName = "year")] + [JsonPropertyName("year")] public int Year { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs index 02156b6..141891f 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs @@ -1,16 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.OfficerSearch { public class Match { - [JsonProperty(PropertyName = "address_snippet")] + [JsonPropertyName("address_snippet")] public int[] AddressSnippet { get; set; } - [JsonProperty(PropertyName = "snippet")] + [JsonPropertyName("snippet")] public int[] Snippet { get; set; } - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public int[] Title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs index dc80fe5..9bb19aa 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs @@ -1,34 +1,34 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.OfficerSearch { public class Officer : SearchItem { - [JsonProperty(PropertyName = "address")] + [JsonPropertyName("address")] public Address Address { get; set; } - [JsonProperty(PropertyName = "address_snippet")] + [JsonPropertyName("address_snippet")] public string AddressSnippet { get; set; } - [JsonProperty(PropertyName = "appointment_count")] + [JsonPropertyName("appointment_count")] public int AppointmentCount { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] + [JsonPropertyName("date_of_birth")] public DateOfBirth DateOfBirth { get; set; } - [JsonProperty(PropertyName = "description")] + [JsonPropertyName("description")] public string Description { get; set; } - [JsonProperty(PropertyName = "description_identifiers")] + [JsonPropertyName("description_identifiers")] public string[] DescriptionIdentifiers { get; set; } - [JsonProperty(PropertyName = "matches")] + [JsonPropertyName("matches")] public Match Matches { get; set; } - [JsonProperty(PropertyName = "snippet")] + [JsonPropertyName("snippet")] public string Snippet { get; set; } - [JsonProperty(PropertyName = "title")] + [JsonPropertyName("title")] public string Title { get; set; } public string OfficerId @@ -36,4 +36,4 @@ public string OfficerId get { return Links.Self.Split('/')[2]; } } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs index 7b828e6..f2bcabf 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs @@ -1,23 +1,23 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.OfficerSearch { public class OfficerSearch { - [JsonProperty(PropertyName = "items")] + [JsonPropertyName("items")] public Officer[] Officers { get; set; } - [JsonProperty(PropertyName = "items_per_page")] + [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } - [JsonProperty(PropertyName = "start_index")] + [JsonPropertyName("start_index")] public int StartIndex { get; set; } - [JsonProperty(PropertyName = "total_results")] + [JsonPropertyName("total_results")] public int TotalResults { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/SearchItem.cs b/src/CompaniesHouse/Response/Search/SearchItem.cs index e12c788..3e29f4e 100644 --- a/src/CompaniesHouse/Response/Search/SearchItem.cs +++ b/src/CompaniesHouse/Response/Search/SearchItem.cs @@ -1,16 +1,16 @@ -using CompaniesHouse.JsonConverters; +using CompaniesHouse.JsonConverters; using CompaniesHouse.Response.Search.CompanySearch; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search { [JsonConverter(typeof(SearchItemConverter))] public abstract class SearchItem { - [JsonProperty(PropertyName = "kind")] + [JsonPropertyName("kind")] public string Kind { get; set; } - [JsonProperty(PropertyName = "links")] + [JsonPropertyName("links")] public Links Links { get; set; } } } diff --git a/src/CompaniesHouse/Response/SecuredDetailType.cs b/src/CompaniesHouse/Response/SecuredDetailType.cs index c1d138b..c0ba614 100644 --- a/src/CompaniesHouse/Response/SecuredDetailType.cs +++ b/src/CompaniesHouse/Response/SecuredDetailType.cs @@ -13,4 +13,4 @@ public enum SecuredDetailType [EnumMember(Value = "obligations-secured")] ObligationsSecured } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/app.config b/src/CompaniesHouse/app.config deleted file mode 100644 index 0a0c8e6..0000000 --- a/src/CompaniesHouse/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/CompaniesHouse/packages.config b/src/CompaniesHouse/packages.config deleted file mode 100644 index 9c8cae8..0000000 --- a/src/CompaniesHouse/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj b/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj index 75dbe52..e3de9d2 100644 --- a/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj +++ b/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj @@ -9,7 +9,6 @@ - diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs new file mode 100644 index 0000000..e5d86b3 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using CompaniesHouse.Response.Insolvency; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.CompanyInsolvencyInformationTests +{ + public abstract class CompanyInsolvencyInformationTestBase : IAsyncLifetime + { + protected CompaniesHouseClient Client = null!; + protected CompaniesHouseClientResponse Result = null!; + + public async Task InitializeAsync() + { + GivenACompaniesHouseClient(); + await When(); + } + + public Task DisposeAsync() => Task.CompletedTask; + + protected abstract Task When(); + + private void GivenACompaniesHouseClient() + { + Client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs deleted file mode 100644 index c794095..0000000 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Threading.Tasks; -using CompaniesHouse.Response.Insolvency; -using Shouldly; -using Xunit; - -namespace CompaniesHouse.IntegrationTests.Tests.CompanyInsolvencyInformationTests -{ - public class CompanyInsolvencyInformationTests - { - private readonly CompaniesHouseClient _client; - - public CompanyInsolvencyInformationTests() - { - _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); - } - - [Fact] - public async Task TheItemsAreReturned() - { - var result = await _client.GetCompanyInsolvencyInformationAsync("08749409"); - - result.Data.ShouldNotBeNull(); - } - } -} \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs new file mode 100644 index 0000000..6b611fe --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.CompanyInsolvencyInformationTests +{ + + public class CompanyInsolvencyInformationTestsInvalid : CompanyInsolvencyInformationTestBase + { + private const string InvalidCompanyNumber = "ABC00000"; + + protected override async Task When() => + Result = await Client.GetCompanyInsolvencyInformationAsync(InvalidCompanyNumber); + + [Fact] + public void ThenTheItemsAreNull() => Result.Data.ShouldBeNull(); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs new file mode 100644 index 0000000..96546e3 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.CompanyInsolvencyInformationTests +{ + + public class CompanyInsolvencyInformationTestsValid : CompanyInsolvencyInformationTestBase + { + private const string ValidCompanyNumber = "08749409"; + + protected override async Task When() => + Result = await Client.GetCompanyInsolvencyInformationAsync(ValidCompanyNumber); + + [Fact] + public void ThenTheItemsAreReturned() => Result.Data.ShouldNotBeNull(); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs new file mode 100644 index 0000000..1426a93 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using CompaniesHouse.Response.Officers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests +{ + + public class OfficerByAppointmentTestsInvalid : OfficersTestBase + { + private const string InvalidCompanyNumber = "ABC00000"; + private const string InvalidAppointmentId = "000000000000000000000000000"; + + protected override async Task When() => + Result = await Client.GetOfficerByAppointmentIdAsync(InvalidCompanyNumber, InvalidAppointmentId); + + [Fact] + public void ThenTheDataIsNull() => Result.Data.ShouldBeNull(); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs new file mode 100644 index 0000000..c742d66 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.RegisteredOfficeAddress +{ + + public class RegisteredOfficeAddressesTestsInValid : RegisteredOfficeAddressTestBase + { + private const string InvalidCompanyNumber = "ABC00000"; + + protected override async Task When() => Result = await Client.GetRegisteredOfficeAddress(InvalidCompanyNumber); + + [Fact] + public void ThenRegisteredOfficeAddressIsNull() => Result.Data.ShouldBeNull(); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/app.config b/tests/CompaniesHouse.IntegrationTests/app.config deleted file mode 100644 index 0a0c8e6..0000000 --- a/tests/CompaniesHouse.IntegrationTests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj b/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj index 2b685ac..411e2db 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj +++ b/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj @@ -12,7 +12,6 @@ - diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs index 999402b..028426d 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs @@ -12,7 +12,7 @@ namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests { public class CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests : IAsyncLifetime { - private Exception _caughtException; + private CompaniesHouseClientResponse? _response; public async Task InitializeAsync() { @@ -25,24 +25,18 @@ public async Task InitializeAsync() BaseAddress = new Uri("https://wibble.com/") }, new SearchUriBuilderFactory()); - try - { - await client.SearchAsync(new SearchCompanyRequest()); - } - catch (Exception ex) - { - _caughtException = ex; - } + _response = await client.SearchAsync(new SearchCompanyRequest()); } public Task DisposeAsync() => Task.CompletedTask; [Fact] - public void ThenExceptionIsThrown() + public void ThenUnsuccessfulResponseIsReturned() { - var exception = _caughtException.ShouldBeOfType(); - exception.Message.ShouldStartWith("Response status code does not indicate success: 429"); + _response.ShouldNotBeNull(); + _response.IsSuccess.ShouldBeFalse(); + _response.StatusCode.ShouldBe(429); + _response.Data.ShouldBeNull(); } - } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs b/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs index e95546e..6c3f618 100644 --- a/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs +++ b/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs @@ -1,5 +1,5 @@ using CompaniesHouse.Description; -using Newtonsoft.Json.Linq; +using System.Text.Json; using Shouldly; using Xunit; @@ -11,7 +11,7 @@ public class DescriptionProviderTests public void GivenFormatAndMatchingStringVariable() { var format = "some value: {variable}"; - var values = JObject.Parse(@"{ ""variable"": ""Value"" }"); + var values = JsonDocument.Parse(@"{ ""variable"": ""Value"" }").RootElement; var result = DescriptionProvider.GetDescription(format, values); result.ShouldBe(@"some value: Value"); @@ -20,7 +20,7 @@ public void GivenFormatAndMatchingStringVariable() public void GivenFormatAndMatchingStringVariables() { var format = "some value: {variable1}, other value: {variable2}"; - var values = JObject.Parse(@"{ ""variable1"": ""Value1"", ""variable2"": ""Value2"" }"); + var values = JsonDocument.Parse(@"{ ""variable1"": ""Value1"", ""variable2"": ""Value2"" }").RootElement; var result = DescriptionProvider.GetDescription(format, values); result.ShouldBe(@"some value: Value1, other value: Value2"); @@ -30,7 +30,7 @@ public void GivenFormatAndMatchingStringVariables() public void GivenFormatAndNotMatchingStringVariable() { var format = "some value: {variable}"; - var values = JObject.Parse(@"{ ""otherVariable"": ""Value"" }"); + var values = JsonDocument.Parse(@"{ ""otherVariable"": ""Value"" }").RootElement; var result = DescriptionProvider.GetDescription(format, values); result.ShouldBe(@"some value: {variable}"); @@ -40,7 +40,7 @@ public void GivenFormatAndNotMatchingStringVariable() public void GivenFormatAndMatchingCompoundVariable() { var format = "some value: {parent.variable}"; - var values = JObject.Parse(@"{ ""parent"": { ""variable"": ""Value"" }}"); + var values = JsonDocument.Parse(@"{ ""parent"": { ""variable"": ""Value"" }}").RootElement; var result = DescriptionProvider.GetDescription(format, values); result.ShouldBe(@"some value: Value"); diff --git a/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs index 43413e2..14bbd58 100644 --- a/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs +++ b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs @@ -1,9 +1,11 @@ namespace CompaniesHouse.Tests.Extensions { using System; + using System.Net.Http.Json; using System.Net; using System.Net.Http; using System.Net.Http.Headers; + using System.Threading.Tasks; using CompaniesHouse.Extensions; using Shouldly; using Xunit; @@ -11,22 +13,29 @@ public class HttpResponseMessageExtensionsTests { [Fact] - public void GivenAnHttpResponse_WhenTheStatusCodeIsSuccess_ThenEnsureSuccessStatusCode2ReturnsTheHttpResponse() + public async Task GivenAnHttpResponse_WhenTheStatusCodeIsSuccess_ThenToCompaniesHouseClientResponseAsyncReturnsTheWrappedResponse() { for (var statusCode = 200; statusCode < 299; statusCode++) { - var sut = new HttpResponseMessage((HttpStatusCode)200); - var responseMessage = sut.EnsureSuccessStatusCode2(); - responseMessage.ShouldBe(sut); + var sut = new HttpResponseMessage((HttpStatusCode)statusCode) + { + Content = JsonContent.Create(new TestPayload { Value = "ok" }) + }; + + var response = await sut.ToCompaniesHouseClientResponseAsync(); + + response.Data.ShouldNotBeNull(); + response.Data.Value.ShouldBe("ok"); + response.StatusCode.ShouldBe(statusCode); + response.IsSuccess.ShouldBeTrue(); + response.Headers.ShouldBe(sut.Headers); } } [Theory] [InlineData(410, "Gone", 0, null)] [InlineData(429, "Too Many Requests", 300, null)] - [InlineData(503, "Service Unavailable", 0, "2015-10-08T12:34:56.000+1")] - [InlineData(503, "Service Unavailable", -1, null)] - public void GivenAnHttpResponse_WhenTheStatusCodeIsNotSuccess_ThenEnsureSuccessStatusCode2ThrowsHttpRequestExceptionWithData( + public async Task GivenAnHttpResponse_WhenTheStatusCodeIsNotServerError_ThenToCompaniesHouseClientResponseAsyncReturnsMetadataWithoutData( int statusCode, string reasonPhrase, int retryAfterSeconds, @@ -46,21 +55,54 @@ public void GivenAnHttpResponse_WhenTheStatusCodeIsNotSuccess_ThenEnsureSuccessS : new RetryConditionHeaderValue(retryAfterDateTimeOffset); } - var exception = Should.Throw(() => sut.EnsureSuccessStatusCode2()); - exception.Data["StatusCode"].ShouldBe(statusCode); - exception.Data["ReasonPhrase"].ShouldBe(reasonPhrase); + var response = await sut.ToCompaniesHouseClientResponseAsync(); - if (retryAfterSeconds >= 0 || !string.IsNullOrWhiteSpace(retryAfterDate)) + response.Data.ShouldBeNull(); + response.StatusCode.ShouldBe(statusCode); + response.ReasonPhrase.ShouldBe(reasonPhrase); + response.IsSuccess.ShouldBeFalse(); + response.RetryAfter.ShouldBe( + string.IsNullOrWhiteSpace(retryAfterDate) + ? TimeSpan.FromSeconds(retryAfterSeconds) + : null); + } + + [Theory] + [InlineData(503, "Service Unavailable", 0, null)] + [InlineData(503, "Service Unavailable", -1, "2015-10-08T12:34:56.000+1")] + [InlineData(503, "Service Unavailable", -1, null)] + public void GivenAnHttpResponse_WhenTheStatusCodeIsServerError_ThenEnsureNotServerErrorAsyncThrowsCompaniesHouseApiException( + int statusCode, + string reasonPhrase, + int retryAfterSeconds, + string? retryAfterDate) + { + var sut = new HttpResponseMessage((HttpStatusCode)statusCode) { ReasonPhrase = reasonPhrase }; + var retryAfterDateTimeOffset = DateTimeOffset.MinValue; + if (!string.IsNullOrWhiteSpace(retryAfterDate)) { - exception.Data["RetryAfter"].ShouldBe( - string.IsNullOrWhiteSpace(retryAfterDate) - ? retryAfterSeconds.ToString() - : retryAfterDateTimeOffset.ToString("R")); + retryAfterDateTimeOffset = DateTimeOffset.Parse(retryAfterDate); } - else + + if (retryAfterSeconds >= 0 || !string.IsNullOrWhiteSpace(retryAfterDate)) { - exception.Data["RetryAfter"].ShouldBeNull(); + sut.Headers.RetryAfter = string.IsNullOrWhiteSpace(retryAfterDate) + ? new RetryConditionHeaderValue(TimeSpan.FromSeconds(retryAfterSeconds)) + : new RetryConditionHeaderValue(retryAfterDateTimeOffset); } + + var exception = Should.Throw(() => sut.EnsureNotServerErrorAsync().GetAwaiter().GetResult()); + exception.StatusCode.ShouldBe(statusCode); + exception.ReasonPhrase.ShouldBe(reasonPhrase); + exception.RetryAfter.ShouldBe( + string.IsNullOrWhiteSpace(retryAfterDate) + ? retryAfterSeconds >= 0 ? TimeSpan.FromSeconds(retryAfterSeconds) : null + : null); + } + + private sealed class TestPayload + { + public string? Value { get; set; } } } } diff --git a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs index df6afeb..8230b28 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs @@ -1,22 +1,17 @@ -using System.IO; -using CompaniesHouse.JsonConverters; using CompaniesHouse.Response; -using Newtonsoft.Json; +using System.Text.Json; namespace CompaniesHouse.Tests.JsonConverters.FilingSubcategoryConverterTests { public abstract class StringArrayOrFieldEnumConverterTestsBase { - private StringArrayOrFieldEnumConverter _convertor; - protected object Result; + protected FilingSubcategory[] Result; protected StringArrayOrFieldEnumConverterTestsBase() { - _convertor = new StringArrayOrFieldEnumConverter(); - var json = GetJson(); - var jsonTextReader = new JsonTextReader(new StringReader(json)); - jsonTextReader.Read(); - Result = _convertor.ReadJson(jsonTextReader, typeof(FilingSubcategory[]), null, null); + Result = JsonSerializer.Deserialize( + GetJson(), + CompaniesHouseJsonSerializerOptions.Default)!; } protected abstract string GetJson(); diff --git a/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs b/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs index 9e9644d..d3c72d1 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs @@ -1,6 +1,7 @@ using CompaniesHouse.JsonConverters; -using Moq; -using Newtonsoft.Json; +using System; +using System.Text; +using System.Text.Json; using Shouldly; using Xunit; @@ -8,15 +9,14 @@ namespace CompaniesHouse.Tests.JsonConverters.OptionalDateJsonConverterTests { public class OptionalDateJsonConverterTestsForUnknownValue { - private OptionalDateJsonConverter _convertor; - private object _result; + private readonly DateTime? _result; public OptionalDateJsonConverterTestsForUnknownValue() { - _convertor = new OptionalDateJsonConverter(); - var jsonReader = new Mock(); - jsonReader.Setup(x => x.Value).Returns("Unknown"); - _result = _convertor.ReadJson(jsonReader.Object, null, null, null); + var converter = new OptionalDateJsonConverter(); + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"""Unknown""")); + reader.Read(); + _result = converter.Read(ref reader, typeof(DateTime?), CompaniesHouseJsonSerializerOptions.Default); } [Fact] diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Charges.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Charges.cs index 02741ac..1f62cd0 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Charges.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Charges.cs @@ -1,25 +1,17 @@ -using Newtonsoft.Json; - namespace CompaniesHouse.Tests.ResourceBuilders { public class Charges { - [JsonProperty("Etag")] - public string Etag { get; set; } - - [JsonProperty("items")] - public Charge[] Items { get; set; } - - [JsonProperty("part_satisfied_count")] + public string? Etag { get; set; } + + public Charge[]? Items { get; set; } + public int? PartSatisfiedCount { get; set; } - - [JsonProperty("satisfied_count")] + public int? SatisfiedCount { get; set; } - - [JsonProperty("total_count")] + public int? TotalCount { get; set; } - - [JsonProperty("unfiletered_count")] + public int? UnfileteredCount { get; set; } } -} \ No newline at end of file +} diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCaseLinks.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCaseLinks.cs index 93a65d2..2fdba54 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCaseLinks.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCaseLinks.cs @@ -1,9 +1,7 @@ -using Newtonsoft.Json; - namespace CompaniesHouse.Tests.ResourceBuilders { public class InsolvencyCaseLinks { - public string Case { get; set; } + public string? Case { get; set; } } -} \ No newline at end of file +} diff --git a/tests/CompaniesHouse.Tests/app.config b/tests/CompaniesHouse.Tests/app.config deleted file mode 100644 index 7bd7144..0000000 --- a/tests/CompaniesHouse.Tests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - From 6d8c24b08e6d14f4cfec40141707a623c3dfbdaa Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 19:43:06 +0100 Subject: [PATCH 04/38] Modernise DI extensions to use IOptions<> with config binding and keyed 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 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. --- Directory.Packages.props | 6 +- ...sions.Microsoft.DependencyInjection.csproj | 5 + .../CompaniesHouseClientDocumentOptions.cs | 15 +- .../CompaniesHouseClientOptions.cs | 15 +- ...sHouseClientServiceCollectionExtensions.cs | 302 ++++++++++++++++-- ...cumentClientServiceCollectionExtensions.cs | 258 +++++++++++++-- ...Microsoft.DependencyInjection.Tests.csproj | 1 + .../ServiceCollectionExtensionsTests.cs | 87 ++++- 8 files changed, 622 insertions(+), 67 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7ad8762..db937f0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,13 +7,15 @@ + + + - + diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj index 167ad53..61929a7 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj @@ -22,8 +22,13 @@ + + + + + diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs index 3f6b6b7..a3e5643 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientDocumentOptions.cs @@ -1,10 +1,23 @@ using System; +using System.ComponentModel.DataAnnotations; namespace CompaniesHouse.Extensions.Microsoft.DependencyInjection { + /// + /// Options used to configure the Companies House document client. + /// public class CompaniesHouseClientDocumentOptions { + /// + /// The base of the Companies House document API. + /// + [Required] public Uri BaseUri { get; set; } = CompaniesHouseUris.DocumentApi; - public string ApiKey { get; set; } + + /// + /// The Companies House API key used to authenticate requests. + /// + [Required(AllowEmptyStrings = false)] + public string ApiKey { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs index 153f604..5b54cf1 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientOptions.cs @@ -1,10 +1,23 @@ using System; +using System.ComponentModel.DataAnnotations; namespace CompaniesHouse.Extensions.Microsoft.DependencyInjection { + /// + /// Options used to configure the Companies House client. + /// public class CompaniesHouseClientOptions { + /// + /// The base of the Companies House public data API. + /// + [Required] public Uri BaseUri { get; set; } = CompaniesHouseUris.Default; - public string ApiKey { get; set; } + + /// + /// The Companies House API key used to authenticate requests. + /// + [Required(AllowEmptyStrings = false)] + public string ApiKey { get; set; } = string.Empty; } } \ No newline at end of file diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs index f5d4472..e126004 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs @@ -2,7 +2,9 @@ using CompaniesHouse; using CompaniesHouse.DelegatingHandlers; using CompaniesHouse.Extensions.Microsoft.DependencyInjection; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { @@ -11,6 +13,12 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class CompaniesHouseClientServiceCollectionExtensions { + /// + /// The default configuration section name used when binding + /// from an . + /// + public const string DefaultSectionName = "CompaniesHouse"; + /// /// Registers the companies house client /// @@ -19,12 +27,9 @@ public static class CompaniesHouseClientServiceCollectionExtensions /// Service collection public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string apiKey) { - return services.AddCompaniesHouseClient(opt => - { - opt.ApiKey = apiKey; - }); + return services.AddCompaniesHouseClient(options => options.ApiKey = apiKey); } - + /// /// Registers the companies house client /// @@ -35,10 +40,10 @@ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, Uri baseUri, string apiKey) { - return services.AddCompaniesHouseClient(opt => + return services.AddCompaniesHouseClient(options => { - opt.BaseUri = baseUri; - opt.ApiKey = apiKey; + options.BaseUri = baseUri; + options.ApiKey = apiKey; }); } @@ -47,11 +52,18 @@ public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection /// /// Service collection /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers /// Service collection - private static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, - Action configure) + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, + Action configure, + Action? configureHttpClientBuilder = null) { - return services.AddCompaniesHouseClient((provider, options) => configure(options)); + services.AddOptions() + .Configure(configure) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseClientCore(configureHttpClientBuilder); } /// @@ -59,58 +71,280 @@ private static IServiceCollection AddCompaniesHouseClient(this IServiceCollectio /// /// Service collection /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers /// Service collection public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, - Action configure) + Action configure, + Action? configureHttpClientBuilder = null) + { + services.AddOptions() + .Configure((options, provider) => configure(provider, options)) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseClientCore(configureHttpClientBuilder); + } + + /// + /// Registers the companies house client, binding from configuration. + /// + /// Service collection + /// The configuration to bind options from + /// The configuration section name (defaults to ) + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, + IConfiguration configuration, string sectionName = DefaultSectionName, + Action? configureHttpClientBuilder = null) + { + return services.AddCompaniesHouseClient(configuration.GetSection(sectionName), configureHttpClientBuilder); + } + + /// + /// Registers the companies house client, binding from a configuration section. + /// + /// Service collection + /// The configuration section to bind options from + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, + IConfigurationSection section, Action? configureHttpClientBuilder = null) + { + services.AddOptions() + .Bind(section) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseClientCore(configureHttpClientBuilder); + } + + private static IServiceCollection AddCompaniesHouseClientCore(this IServiceCollection services, + Action? configureHttpClientBuilder) { - services.TryAddSingleton(provider => - { - var options = new CompaniesHouseClientOptions(); - configure.Invoke(provider, options); - return options; - }); - services.TryAddTransient(provider => { - var options = provider.GetRequiredService(); + var options = provider.GetRequiredService>().Value; return new StaticApiKeyProvider(options.ApiKey); }); services.TryAddTransient(); - services.AddHttpClient((provider, client) => + var httpClientBuilder = services.AddHttpClient((provider, client) => { - var options = provider.GetRequiredService(); + var options = provider.GetRequiredService>().Value; client.BaseAddress = options.BaseUri; }) .AddHttpMessageHandler(); + configureHttpClientBuilder?.Invoke(httpClientBuilder); + + services.TryAddCompaniesHouseSubClients(); + + return services; + } + + private static IServiceCollection TryAddCompaniesHouseSubClients(this IServiceCollection services) + { services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient( - provider => provider.GetService()); + provider => provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); + + return services; + } + + // --------------------------------------------------------------- + // Named / keyed registrations — allow several distinct, separately + // configured Companies House clients to coexist in the same + // service collection, resolved via `[FromKeyedServices(name)]` or + // `IServiceProvider.GetRequiredKeyedService(name)`. + // --------------------------------------------------------------- + + /// + /// Registers a named companies house client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The Api Key + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name, + string apiKey) + { + return services.AddCompaniesHouseClient(name, options => options.ApiKey = apiKey); + } + + /// + /// Registers a named companies house client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The Base Uri of the API + /// The Api Key + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name, + Uri baseUri, string apiKey) + { + return services.AddCompaniesHouseClient(name, options => + { + options.BaseUri = baseUri; + options.ApiKey = apiKey; + }); + } + + /// + /// Registers a named companies house client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name, + Action configure, + Action? configureHttpClientBuilder = null) + { + services.AddOptions(name) + .Configure(configure) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseClientCore(name, configureHttpClientBuilder); + } + + /// + /// Registers a named companies house client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name, + Action configure, + Action? configureHttpClientBuilder = null) + { + services.AddOptions(name) + .Configure((options, provider) => configure(provider, options)) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseClientCore(name, configureHttpClientBuilder); + } + + /// + /// Registers a named companies house client, binding from configuration. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The configuration to bind options from + /// The configuration section name (defaults to ) + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name, + IConfiguration configuration, string sectionName = DefaultSectionName, + Action? configureHttpClientBuilder = null) + { + return services.AddCompaniesHouseClient(name, configuration.GetSection(sectionName), + configureHttpClientBuilder); + } + + /// + /// Registers a named companies house client, binding from a configuration section. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The configuration section to bind options from + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseClient(this IServiceCollection services, string name, + IConfigurationSection section, Action? configureHttpClientBuilder = null) + { + services.AddOptions(name) + .Bind(section) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseClientCore(name, configureHttpClientBuilder); + } + + private static IServiceCollection AddCompaniesHouseClientCore(this IServiceCollection services, string name, + Action? configureHttpClientBuilder) + { + services.TryAddKeyedTransient(name, (provider, key) => + { + var options = provider.GetRequiredService>().Get((string)key!); + + return new StaticApiKeyProvider(options.ApiKey); + }); + + var httpClientBuilder = services.AddHttpClient(name) + .ConfigureHttpClient((provider, client) => + { + var options = provider.GetRequiredService>().Get(name); + + client.BaseAddress = options.BaseUri; + }) + .AddHttpMessageHandler(provider => + new CompaniesHouseAuthorizationHandler( + provider.GetRequiredKeyedService(name))); + + configureHttpClientBuilder?.Invoke(httpClientBuilder); + + services.TryAddKeyedTransient(name, (provider, key) => + new CompaniesHouseClient(provider.GetRequiredService().CreateClient((string)key!))); + + services.TryAddKeyedCompaniesHouseSubClients(name); + + return services; + } + + private static IServiceCollection TryAddKeyedCompaniesHouseSubClients(this IServiceCollection services, + string name) + { + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); return services; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs index 0873fb2..c14101b 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseDocumentClientServiceCollectionExtensions.cs @@ -2,7 +2,9 @@ using CompaniesHouse; using CompaniesHouse.DelegatingHandlers; using CompaniesHouse.Extensions.Microsoft.DependencyInjection; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { @@ -11,6 +13,12 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class CompaniesHouseDocumentClientServiceCollectionExtensions { + /// + /// The default configuration section name used when binding + /// from an . + /// + public const string DefaultSectionName = "CompaniesHouseDocument"; + /// /// Registers the companies house document client /// @@ -19,12 +27,9 @@ public static class CompaniesHouseDocumentClientServiceCollectionExtensions /// Service collection public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, string apiKey) { - return services.AddCompaniesHouseDocumentClient(opt => - { - opt.ApiKey = apiKey; - }); + return services.AddCompaniesHouseDocumentClient(options => options.ApiKey = apiKey); } - + /// /// Registers the companies house document client /// @@ -35,10 +40,10 @@ public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCo public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, Uri baseUri, string apiKey) { - return services.AddCompaniesHouseDocumentClient(opt => + return services.AddCompaniesHouseDocumentClient(options => { - opt.BaseUri = baseUri; - opt.ApiKey = apiKey; + options.BaseUri = baseUri; + options.ApiKey = apiKey; }); } @@ -47,11 +52,18 @@ public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCo /// /// Service collection /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers /// Service collection - private static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, - Action configure) + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + Action configure, + Action? configureHttpClientBuilder = null) { - return services.AddCompaniesHouseDocumentClient((provider, options) => configure(options)); + services.AddOptions() + .Configure(configure) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseDocumentClientCore(configureHttpClientBuilder); } /// @@ -59,40 +71,234 @@ private static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceC /// /// Service collection /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers /// Service collection public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, - Action configure) + Action configure, + Action? configureHttpClientBuilder = null) + { + services.AddOptions() + .Configure((options, provider) => configure(provider, options)) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseDocumentClientCore(configureHttpClientBuilder); + } + + /// + /// Registers the companies house document client, binding from configuration. + /// + /// Service collection + /// The configuration to bind options from + /// The configuration section name (defaults to ) + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + IConfiguration configuration, string sectionName = DefaultSectionName, + Action? configureHttpClientBuilder = null) + { + return services.AddCompaniesHouseDocumentClient(configuration.GetSection(sectionName), + configureHttpClientBuilder); + } + + /// + /// Registers the companies house document client, binding from a configuration section. + /// + /// Service collection + /// The configuration section to bind options from + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + IConfigurationSection section, Action? configureHttpClientBuilder = null) + { + services.AddOptions() + .Bind(section) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseDocumentClientCore(configureHttpClientBuilder); + } + + private static IServiceCollection AddCompaniesHouseDocumentClientCore(this IServiceCollection services, + Action? configureHttpClientBuilder) { - services.TryAddSingleton(provider => - { - var options = new CompaniesHouseClientDocumentOptions(); - configure.Invoke(provider, options); - return options; - }); - services.TryAddTransient(provider => { - var options = provider.GetRequiredService(); + var options = provider.GetRequiredService>().Value; return new StaticApiKeyProvider(options.ApiKey); }); - + services.TryAddTransient(); - services.AddHttpClient((provider, client) => + var httpClientBuilder = services + .AddHttpClient((provider, client) => { - var options = provider.GetRequiredService(); + var options = provider.GetRequiredService>().Value; client.BaseAddress = options.BaseUri; }) .AddHttpMessageHandler(); + configureHttpClientBuilder?.Invoke(httpClientBuilder); + services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); + + return services; + } + + // --------------------------------------------------------------- + // Named / keyed registrations — allow several distinct, separately + // configured Companies House document clients to coexist in the + // same service collection, resolved via `[FromKeyedServices(name)]` + // or `IServiceProvider.GetRequiredKeyedService(name)`. + // --------------------------------------------------------------- + + /// + /// Registers a named companies house document client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The Api Key + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + string name, string apiKey) + { + return services.AddCompaniesHouseDocumentClient(name, options => options.ApiKey = apiKey); + } + + /// + /// Registers a named companies house document client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The Base Uri of the API + /// The Api Key + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + string name, Uri baseUri, string apiKey) + { + return services.AddCompaniesHouseDocumentClient(name, options => + { + options.BaseUri = baseUri; + options.ApiKey = apiKey; + }); + } + + /// + /// Registers a named companies house document client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + string name, Action configure, + Action? configureHttpClientBuilder = null) + { + services.AddOptions(name) + .Configure(configure) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseDocumentClientCore(name, configureHttpClientBuilder); + } + + /// + /// Registers a named companies house document client, resolvable as a keyed service. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// Companies house client options configuration + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + string name, Action configure, + Action? configureHttpClientBuilder = null) + { + services.AddOptions(name) + .Configure((options, provider) => configure(provider, options)) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseDocumentClientCore(name, configureHttpClientBuilder); + } + + /// + /// Registers a named companies house document client, binding from configuration. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The configuration to bind options from + /// The configuration section name (defaults to ) + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + string name, IConfiguration configuration, string sectionName = DefaultSectionName, + Action? configureHttpClientBuilder = null) + { + return services.AddCompaniesHouseDocumentClient(name, configuration.GetSection(sectionName), + configureHttpClientBuilder); + } + + /// + /// Registers a named companies house document client, binding from a configuration section. + /// + /// Service collection + /// The name/key used to register and resolve this client + /// The configuration section to bind options from + /// Optional hook to customise the underlying , e.g. to add resilience handlers + /// Service collection + public static IServiceCollection AddCompaniesHouseDocumentClient(this IServiceCollection services, + string name, IConfigurationSection section, Action? configureHttpClientBuilder = null) + { + services.AddOptions(name) + .Bind(section) + .ValidateDataAnnotations() + .ValidateOnStart(); + + return services.AddCompaniesHouseDocumentClientCore(name, configureHttpClientBuilder); + } + + private static IServiceCollection AddCompaniesHouseDocumentClientCore(this IServiceCollection services, + string name, Action? configureHttpClientBuilder) + { + services.TryAddKeyedTransient(name, (provider, key) => + { + var options = provider.GetRequiredService>() + .Get((string)key!); + + return new StaticApiKeyProvider(options.ApiKey); + }); + + var httpClientBuilder = services.AddHttpClient(name) + .ConfigureHttpClient((provider, client) => + { + var options = provider.GetRequiredService>() + .Get(name); + + client.BaseAddress = options.BaseUri; + }) + .AddHttpMessageHandler(provider => + new CompaniesHouseAuthorizationHandler( + provider.GetRequiredKeyedService(name))); + + configureHttpClientBuilder?.Invoke(httpClientBuilder); + + services.TryAddKeyedTransient(name, (provider, key) => + new CompaniesHouseDocumentClient( + provider.GetRequiredService().CreateClient((string)key!))); + + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); return services; } } -} \ No newline at end of file +} diff --git a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj index 7372dfb..5f23868 100644 --- a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj +++ b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj @@ -5,6 +5,7 @@ + diff --git a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs index c9b95fb..d30396c 100644 --- a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs @@ -1,4 +1,8 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Shouldly; using Xunit; @@ -11,7 +15,7 @@ public void CanResolveCompaniesHouseClients() { var serviceCollection = new ServiceCollection(); serviceCollection.AddCompaniesHouseClient("ApiKey"); - + var serviceProvider = serviceCollection.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); @@ -27,15 +31,71 @@ public void CanResolveCompaniesHouseClients() scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + } + + [Fact] + public void AddCompaniesHouseClient_FromConfiguration_BindsOptions() + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["CompaniesHouse:ApiKey"] = "ConfiguredApiKey", + ["CompaniesHouse:BaseUri"] = "https://example.test/", + }) + .Build(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCompaniesHouseClient(configuration); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var options = serviceProvider.GetRequiredService>().Value; + + options.ApiKey.ShouldBe("ConfiguredApiKey"); + options.BaseUri.ShouldBe(new Uri("https://example.test/")); + } + + [Fact] + public void AddCompaniesHouseClient_MissingApiKey_FailsValidationOnStart() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCompaniesHouseClient(options => options.ApiKey = string.Empty); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + Should.Throw(() => + serviceProvider.GetRequiredService>().Value); + } + + [Fact] + public void AddCompaniesHouseClient_Named_ResolvesKeyedServices() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCompaniesHouseClient("first", "FirstApiKey"); + serviceCollection.AddCompaniesHouseClient("second", "SecondApiKey"); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + using var scope = serviceProvider.CreateScope(); + + var first = scope.ServiceProvider.GetRequiredKeyedService("first"); + var second = scope.ServiceProvider.GetRequiredKeyedService("second"); + + first.ShouldNotBeNull(); + second.ShouldNotBeNull(); + first.ShouldNotBeSameAs(second); + + scope.ServiceProvider.GetRequiredKeyedService("first").ShouldNotBeNull(); + scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); } [Fact] public void CanResolveCompaniesHouseDocumentClients() { - + var serviceCollection = new ServiceCollection(); serviceCollection.AddCompaniesHouseDocumentClient("ApiKey"); - + var serviceProvider = serviceCollection.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); @@ -43,5 +103,26 @@ public void CanResolveCompaniesHouseDocumentClients() scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); } + + [Fact] + public void AddCompaniesHouseDocumentClient_Named_ResolvesKeyedServices() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddCompaniesHouseDocumentClient("first", "FirstApiKey"); + serviceCollection.AddCompaniesHouseDocumentClient("second", "SecondApiKey"); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + using var scope = serviceProvider.CreateScope(); + + var first = scope.ServiceProvider.GetRequiredKeyedService("first"); + var second = scope.ServiceProvider.GetRequiredKeyedService("second"); + + first.ShouldNotBeNull(); + second.ShouldNotBeNull(); + first.ShouldNotBeSameAs(second); + + scope.ServiceProvider.GetRequiredKeyedService("first").ShouldNotBeNull(); + scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); + } } } \ No newline at end of file From 76f3a749226531e6ca04c8d80f4ca7dd24d072a8 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 21:14:16 +0100 Subject: [PATCH 05/38] Replace CompanyStatus enum with a string-backed value type (plan 03) - 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. --- .../CompanyStatusJsonConverter.cs | 35 +++++ src/CompaniesHouse/Response/CompanyStatus.cs | 122 +++++++++++++----- ...sHouseSearchClientTestsForCompanySearch.cs | 2 +- .../ResponseValueTypes/CompanyStatusTests.cs | 108 ++++++++++++++++ 4 files changed, 232 insertions(+), 35 deletions(-) create mode 100644 src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusTests.cs diff --git a/src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs b/src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs new file mode 100644 index 0000000..34a2d88 --- /dev/null +++ b/src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs @@ -0,0 +1,35 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using CompaniesHouse.Response; + +namespace CompaniesHouse.JsonConverters +{ + /// + /// Reads/writes a as its raw wire string. Performs no + /// validation or lookup — unrecognised values are preserved rather than rejected. + /// + public sealed class CompanyStatusJsonConverter : JsonConverter + { + public override CompanyStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + return new CompanyStatus(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, CompanyStatus value, JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStringValue(value.Value); + } + } +} diff --git a/src/CompaniesHouse/Response/CompanyStatus.cs b/src/CompaniesHouse/Response/CompanyStatus.cs index dfd2c75..22a7cfa 100644 --- a/src/CompaniesHouse/Response/CompanyStatus.cs +++ b/src/CompaniesHouse/Response/CompanyStatus.cs @@ -1,49 +1,103 @@ -using System.Runtime.Serialization; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using CompaniesHouse.JsonConverters; namespace CompaniesHouse.Response { - public enum CompanyStatus + /// + /// The status of a company, as returned by the Companies House API. + /// + /// + /// This is a string-backed value type rather than a plain C# . + /// Companies House do not reliably version their API, so new status values can + /// appear in responses at any time. A string-backed type preserves the raw wire + /// value and never throws on an unrecognised value — see + /// . + /// + [JsonConverter(typeof(CompanyStatusJsonConverter))] + public readonly record struct CompanyStatus { - [EnumMember(Value = "")] - None = 0, + private readonly string? _value; - [EnumMember(Value = "active")] - Active, + public CompanyStatus(string? value) + { + _value = value; + } - [EnumMember(Value = "dissolved")] - Dissolved, + /// + /// The raw wire value. Never — an absent/unknown + /// value is represented as ; see . + /// + public string Value => _value ?? string.Empty; - [EnumMember(Value = "liquidation")] - Liquidation, + /// + /// if a value was present on the wire (including any + /// unrecognised value); for the /absent case. + /// + public bool HasValue => !string.IsNullOrEmpty(_value); - [EnumMember(Value = "receivership")] - Receivership, + /// + /// if is one of the values known to this + /// version of the library at the time of writing. + /// + public bool IsKnown => KnownValues.Contains(Value); - [EnumMember(Value = "administration")] - Administration, + /// + /// The human-readable description of , sourced from the + /// Companies House api-enumerations reference data, or + /// if no description is known for this value. + /// + public string? Description => Descriptions.TryGetValue(Value, out var description) ? description : null; - [EnumMember(Value = "voluntary-arrangement")] - VoluntaryArrangement, + public static CompanyStatus Active => new("active"); + public static CompanyStatus Dissolved => new("dissolved"); + public static CompanyStatus Liquidation => new("liquidation"); + public static CompanyStatus Receivership => new("receivership"); + public static CompanyStatus Administration => new("administration"); + public static CompanyStatus VoluntaryArrangement => new("voluntary-arrangement"); + public static CompanyStatus ConvertedClosed => new("converted-closed"); + public static CompanyStatus InsolvencyProceedings => new("insolvency-proceedings"); + public static CompanyStatus Open => new("open"); + public static CompanyStatus Closed => new("closed"); + public static CompanyStatus ClosedOn => new("closed-on"); + public static CompanyStatus Registered => new("registered"); + public static CompanyStatus Removed => new("removed"); - [EnumMember(Value = "converted-closed")] - ConvertedClosed, + public override string ToString() => Value; - [EnumMember(Value = "insolvency-proceedings")] - InsolvencyProceedings, + private static readonly HashSet KnownValues = new(StringComparer.Ordinal) + { + "active", + "dissolved", + "liquidation", + "receivership", + "administration", + "voluntary-arrangement", + "converted-closed", + "insolvency-proceedings", + "open", + "closed", + "closed-on", + "registered", + "removed", + }; - [EnumMember(Value = "open")] - Open, - - [EnumMember(Value = "closed")] - Closed, - - [EnumMember(Value = "closed-on")] - ClosedOn, - - [EnumMember(Value = "registered")] - Registered, - - [EnumMember(Value = "removed")] - Removed, + // Sourced from https://github.com/companieshouse/api-enumerations constants.yml (company_status). + private static readonly IReadOnlyDictionary Descriptions = new Dictionary(StringComparer.Ordinal) + { + ["active"] = "Active", + ["dissolved"] = "Dissolved", + ["liquidation"] = "Liquidation", + ["receivership"] = "Receiver Action", + ["converted-closed"] = "Converted / Closed", + ["voluntary-arrangement"] = "Voluntary Arrangement", + ["insolvency-proceedings"] = "Insolvency Proceedings", + ["administration"] = "In Administration", + ["open"] = "Open", + ["closed"] = "Closed", + ["registered"] = "Registered", + ["removed"] = "Removed", + }; } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs index 2d85b34..4e7e8ca 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs @@ -147,7 +147,7 @@ public void ThenTheCompaniesAreCorrect() private static readonly IReadOnlyDictionary ExpectedCompanyStatus = new Dictionary () { - { "", CompanyStatus.None }, + { "", default }, { "active", CompanyStatus.Active }, { "dissolved", CompanyStatus.Dissolved }, { "liquidation", CompanyStatus.Liquidation }, diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusTests.cs new file mode 100644 index 0000000..4f58751 --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusTests.cs @@ -0,0 +1,108 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class CompanyStatusTests + { + [Theory] + [InlineData("active")] + [InlineData("dissolved")] + [InlineData("liquidation")] + [InlineData("receivership")] + [InlineData("administration")] + [InlineData("voluntary-arrangement")] + [InlineData("converted-closed")] + [InlineData("insolvency-proceedings")] + [InlineData("open")] + [InlineData("closed")] + [InlineData("closed-on")] + [InlineData("registered")] + [InlineData("removed")] + public void Deserializing_KnownValue_RoundTripsAndIsKnown(string value) + { + var json = $"\"{value}\""; + + var status = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + status.Value.ShouldBe(value); + status.IsKnown.ShouldBeTrue(); + status.HasValue.ShouldBeTrue(); + + var reserialized = JsonSerializer.Serialize(status, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe(json); + } + + [Fact] + public void Deserializing_UnknownValue_DoesNotThrowAndPreservesRawValue() + { + const string json = "\"some-brand-new-status-companies-house-invented-tomorrow\""; + + var status = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + status.Value.ShouldBe("some-brand-new-status-companies-house-invented-tomorrow"); + status.IsKnown.ShouldBeFalse(); + status.HasValue.ShouldBeTrue(); + status.Description.ShouldBeNull(); + + var reserialized = JsonSerializer.Serialize(status, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe(json); + } + + [Fact] + public void Deserializing_Null_ReturnsDefaultWithNoValue() + { + var status = JsonSerializer.Deserialize("null", CompaniesHouseJsonSerializerOptions.Default); + + status.ShouldBe(default); + status.HasValue.ShouldBeFalse(); + status.Value.ShouldBe(string.Empty); + status.IsKnown.ShouldBeFalse(); + + var reserialized = JsonSerializer.Serialize(status, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe("null"); + } + + [Fact] + public void KnownValues_CompareEqualToStaticMembers() + { + new CompanyStatus("active").ShouldBe(CompanyStatus.Active); + new CompanyStatus("dissolved").ShouldBe(CompanyStatus.Dissolved); + new CompanyStatus("removed").ShouldBe(CompanyStatus.Removed); + + (CompanyStatus.Active == new CompanyStatus("active")).ShouldBeTrue(); + (CompanyStatus.Active == CompanyStatus.Dissolved).ShouldBeFalse(); + } + + [Fact] + public void SwitchExpression_MatchesOnKnownValues() + { + var status = new CompanyStatus("dissolved"); + + var description = status switch + { + _ when status == CompanyStatus.Active => "is active", + _ when status == CompanyStatus.Dissolved => "is dissolved", + _ => "something else", + }; + + description.ShouldBe("is dissolved"); + } + + [Fact] + public void Description_ReturnsFriendlyTextForKnownValues() + { + CompanyStatus.Active.Description.ShouldBe("Active"); + CompanyStatus.InsolvencyProceedings.Description.ShouldBe("Insolvency Proceedings"); + } + + [Fact] + public void ToString_ReturnsRawValue() + { + CompanyStatus.Active.ToString().ShouldBe("active"); + default(CompanyStatus).ToString().ShouldBe(string.Empty); + } + } +} From aa479c3cbfca5a48c9b1fd68ed641c0f03544f81 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 22:22:57 +0100 Subject: [PATCH 06/38] Add api-enumerations submodule, local extras overlay and monthly bump 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. --- .github/workflows/bump-api-enumerations.yml | 48 ++++++++++++++++++ .gitmodules | 3 ++ Dockerfile | 2 + enumerations/extra/README.md | 55 +++++++++++++++++++++ enumerations/extra/company_status.yml | 2 + external/api-enumerations | 1 + 6 files changed, 111 insertions(+) create mode 100644 .github/workflows/bump-api-enumerations.yml create mode 100644 .gitmodules create mode 100644 enumerations/extra/README.md create mode 100644 enumerations/extra/company_status.yml create mode 160000 external/api-enumerations diff --git a/.github/workflows/bump-api-enumerations.yml b/.github/workflows/bump-api-enumerations.yml new file mode 100644 index 0000000..b0fa42d --- /dev/null +++ b/.github/workflows/bump-api-enumerations.yml @@ -0,0 +1,48 @@ +name: Bump api-enumerations submodule + +on: + schedule: + # 03:00 UTC on the 1st of every month. + - cron: "0 3 1 * *" + workflow_dispatch: {} + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Update api-enumerations submodule + id: update + run: | + cd external/api-enumerations + git fetch origin + before=$(git rev-parse HEAD) + git checkout origin/master + after=$(git rev-parse HEAD) + cd ../.. + if [ "$before" = "$after" ]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create pull request + if: steps.update.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "Bump api-enumerations submodule" + title: "Bump api-enumerations submodule" + body: | + Automated monthly update of the `external/api-enumerations` submodule + to the latest upstream `master` commit. + + Review the upstream diff before merging — new/changed enum + descriptions will flow into the generated value types (plan `04`) + the next time the package is built and released. + branch: chore/bump-api-enumerations + delete-branch: true diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4d8e671 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/api-enumerations"] + path = external/api-enumerations + url = https://github.com/companieshouse/api-enumerations diff --git a/Dockerfile b/Dockerfile index f7e0a27..65f0f0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,8 @@ ARG NUGET_PACKAGE_VERSION COPY ./src/ ./src/ COPY ./tests/ ./tests/ COPY ./samples/ ./samples/ +COPY ./external/ ./external/ +COPY ./enumerations/ ./enumerations/ RUN dotnet build --configuration $CONFIGURATION --no-restore FROM build as test diff --git a/enumerations/extra/README.md b/enumerations/extra/README.md new file mode 100644 index 0000000..2dcd757 --- /dev/null +++ b/enumerations/extra/README.md @@ -0,0 +1,55 @@ +# `enumerations/extra/` + +This folder is a repo-local overlay on top of the +[`companieshouse/api-enumerations`](https://github.com/companieshouse/api-enumerations) +git submodule at `external/api-enumerations/`. It lets us: + +1. **Add values Companies House haven't published yet** (or have used in the + live API before updating their own reference data). +2. **Define library-only enum groups** that don't exist upstream at all. + +The source generator (plan `04`) reads YAML from **both** locations and merges +them per top-level group key: + +- The submodule (`external/api-enumerations/*.yml`) is read first. +- Files in this folder (`enumerations/extra/*.yml`) are read second and merged + **on top**: for a given group (e.g. `company_status`), any wire-value key + present in an extras file **overrides** the submodule's entry for that key; + keys not present in the submodule are **appended**. +- You may add entirely new group keys here that don't exist upstream at all — + they behave like any other generated group. + +## File format + +Same shape as the upstream files — a YAML mapping of group name to a mapping +of wire value to human-readable description: + +```yaml +group_name: + 'wire-value' : "Friendly description" +``` + +## Example + +`company_status.yml` in this folder adds the description for `closed-on`, +which the upstream `constants.yml` `company_status` group is missing (all +other `company_status` entries continue to come from the submodule): + +```yaml +company_status: + 'closed-on' : "Closed On" +``` + +## Refreshing the upstream submodule + +```powershell +git submodule update --remote external/api-enumerations +git add external/api-enumerations +git commit -m "Bump api-enumerations submodule" +``` + +A scheduled workflow (`.github/workflows/bump-api-enumerations.yml`) does this +automatically once a month and opens a pull request for review — new upstream +values still only take effect once that PR is merged and the package is +rebuilt (see plan `04`: generation happens at build time in this repo, not in +consumers). diff --git a/enumerations/extra/company_status.yml b/enumerations/extra/company_status.yml new file mode 100644 index 0000000..4884e2f --- /dev/null +++ b/enumerations/extra/company_status.yml @@ -0,0 +1,2 @@ +company_status: + 'closed-on' : "Closed On" diff --git a/external/api-enumerations b/external/api-enumerations new file mode 160000 index 0000000..9d9af10 --- /dev/null +++ b/external/api-enumerations @@ -0,0 +1 @@ +Subproject commit 9d9af10e2504bfdd08bd69228e25f18c76dc760f From 1917cbcff1a4870a72d3ba705a4c7dbe186ae9e2 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 22:43:12 +0100 Subject: [PATCH 07/38] Add Roslyn source generator for string-backed enum value types (plan 04) - 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. --- CompaniesHouse.slnx | 2 + Directory.Packages.props | 5 + .../AssemblyInfo.cs | 3 + .../CompaniesHouse.SourceGenerator.csproj | 27 +++ .../EnumDataMerger.cs | 75 +++++++ .../EnumMapEntry.cs | 30 +++ .../EnumMapParser.cs | 46 ++++ .../EnumValueTypeGenerator.cs | 102 +++++++++ .../MemberNameGenerator.cs | 60 ++++++ .../MinimalYamlParser.cs | 197 ++++++++++++++++++ .../ValueTypeEmitter.cs | 166 +++++++++++++++ src/CompaniesHouse/CompaniesHouse.csproj | 18 ++ .../CompanyStatusJsonConverter.cs | 35 ---- src/CompaniesHouse/Response/CompanyStatus.cs | 103 --------- src/CompaniesHouse/enum-map.txt | 10 + ...ompaniesHouse.SourceGenerator.Tests.csproj | 26 +++ .../EnumDataMergerTests.cs | 83 ++++++++ .../EnumMapParserTests.cs | 52 +++++ .../EnumValueTypeGeneratorTests.cs | 154 ++++++++++++++ .../InMemoryAdditionalText.cs | 21 ++ .../MemberNameGeneratorTests.cs | 38 ++++ .../MinimalYamlParserTests.cs | 118 +++++++++++ 22 files changed, 1233 insertions(+), 138 deletions(-) create mode 100644 src/CompaniesHouse.SourceGenerator/AssemblyInfo.cs create mode 100644 src/CompaniesHouse.SourceGenerator/CompaniesHouse.SourceGenerator.csproj create mode 100644 src/CompaniesHouse.SourceGenerator/EnumDataMerger.cs create mode 100644 src/CompaniesHouse.SourceGenerator/EnumMapEntry.cs create mode 100644 src/CompaniesHouse.SourceGenerator/EnumMapParser.cs create mode 100644 src/CompaniesHouse.SourceGenerator/EnumValueTypeGenerator.cs create mode 100644 src/CompaniesHouse.SourceGenerator/MemberNameGenerator.cs create mode 100644 src/CompaniesHouse.SourceGenerator/MinimalYamlParser.cs create mode 100644 src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs delete mode 100644 src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs delete mode 100644 src/CompaniesHouse/Response/CompanyStatus.cs create mode 100644 src/CompaniesHouse/enum-map.txt create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/CompaniesHouse.SourceGenerator.Tests.csproj create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/EnumDataMergerTests.cs create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/EnumMapParserTests.cs create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/InMemoryAdditionalText.cs create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/MemberNameGeneratorTests.cs create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/MinimalYamlParserTests.cs diff --git a/CompaniesHouse.slnx b/CompaniesHouse.slnx index da86daa..8084acf 100644 --- a/CompaniesHouse.slnx +++ b/CompaniesHouse.slnx @@ -1,9 +1,11 @@ + + diff --git a/Directory.Packages.props b/Directory.Packages.props index db937f0..a8734d2 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,6 +18,11 @@ + + + + + diff --git a/src/CompaniesHouse.SourceGenerator/AssemblyInfo.cs b/src/CompaniesHouse.SourceGenerator/AssemblyInfo.cs new file mode 100644 index 0000000..cf0d9ce --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("CompaniesHouse.SourceGenerator.Tests")] diff --git a/src/CompaniesHouse.SourceGenerator/CompaniesHouse.SourceGenerator.csproj b/src/CompaniesHouse.SourceGenerator/CompaniesHouse.SourceGenerator.csproj new file mode 100644 index 0000000..9eece56 --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/CompaniesHouse.SourceGenerator.csproj @@ -0,0 +1,27 @@ + + + + netstandard2.0 + true + true + false + CompaniesHouse.SourceGenerator + + $(NoWarn);RS2008 + + + + + + + + + diff --git a/src/CompaniesHouse.SourceGenerator/EnumDataMerger.cs b/src/CompaniesHouse.SourceGenerator/EnumDataMerger.cs new file mode 100644 index 0000000..656e217 --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/EnumDataMerger.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CompaniesHouse.SourceGenerator +{ + /// + /// Merges the parsed data from multiple YAML sources into a single + /// per-group, ordered set of wire-value/description entries. + /// + /// + /// Merge order matters: sources are merged in the order they're passed in, and a later + /// source's entry for the same group + wire value overrides an earlier one's description + /// (and appends brand new wire values/groups). Callers should pass the submodule's parsed + /// files first, and the local enumerations/extra/*.yml overlay files last, per + /// enumerations/extra/README.md. + /// + internal static class EnumDataMerger + { + public static IReadOnlyDictionary Merge(IEnumerable> sourcesInOrder) + { + var groups = new Dictionary(StringComparer.Ordinal); + + foreach (var source in sourcesInOrder) + { + foreach (var group in source) + { + if (!groups.TryGetValue(group.Name, out var merged)) + { + merged = new MergedGroup(group.Name); + groups[group.Name] = merged; + } + + foreach (var entry in group.Entries) + { + merged.Set(entry.Key, entry.Value); + } + } + } + + return groups; + } + } + + /// The merged, de-duplicated entries for a single enum group, in first-seen order. + internal sealed class MergedGroup + { + private readonly List _order = new List(); + private readonly Dictionary _descriptions = new Dictionary(StringComparer.Ordinal); + + public MergedGroup(string name) + { + Name = name; + } + + public string Name { get; } + + public void Set(string wireValue, string description) + { + if (!_descriptions.ContainsKey(wireValue)) + { + _order.Add(wireValue); + } + + _descriptions[wireValue] = description; + } + + /// Wire values in first-seen order (submodule order, then any extras-only additions). + public IReadOnlyList WireValues => _order; + + public string GetDescription(string wireValue) => _descriptions[wireValue]; + + public IEnumerable> Entries => _order.Select(v => new KeyValuePair(v, _descriptions[v])); + } +} diff --git a/src/CompaniesHouse.SourceGenerator/EnumMapEntry.cs b/src/CompaniesHouse.SourceGenerator/EnumMapEntry.cs new file mode 100644 index 0000000..791a13f --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/EnumMapEntry.cs @@ -0,0 +1,30 @@ +namespace CompaniesHouse.SourceGenerator +{ + /// + /// A single configured mapping of a YAML group (parsed via , + /// merged across the submodule and local extras data - see ) to a + /// generated C# type. + /// + internal sealed class EnumMapEntry + { + public EnumMapEntry(string group, string @namespace, string typeName, bool includeDescriptions) + { + Group = group; + Namespace = @namespace; + TypeName = typeName; + IncludeDescriptions = includeDescriptions; + } + + /// The top-level YAML key, e.g. company_status. + public string Group { get; } + + /// The namespace the generated type is emitted into, e.g. CompaniesHouse.Response. + public string Namespace { get; } + + /// The generated type name, e.g. CompanyStatus. + public string TypeName { get; } + + /// Whether to emit a Description property/lookup for this type. + public bool IncludeDescriptions { get; } + } +} diff --git a/src/CompaniesHouse.SourceGenerator/EnumMapParser.cs b/src/CompaniesHouse.SourceGenerator/EnumMapParser.cs new file mode 100644 index 0000000..3d0366c --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/EnumMapParser.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; + +namespace CompaniesHouse.SourceGenerator +{ + /// + /// Parses the enum-map.txt configuration file that declares which YAML groups + /// (see ) should be generated, and into which type. + /// + /// + /// Format: one entry per non-blank, non-comment (#) line, pipe-delimited: + /// group|namespace|TypeName|includeDescriptions + /// e.g. company_status|CompaniesHouse.Response|CompanyStatus|true. + /// + internal static class EnumMapParser + { + public static IReadOnlyList Parse(string text) + { + var entries = new List(); + + foreach (var rawLine in text.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n')) + { + var line = rawLine.Trim(); + if (line.Length == 0 || line.StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + var parts = line.Split('|'); + if (parts.Length != 4) + { + continue; + } + + var group = parts[0].Trim(); + var @namespace = parts[1].Trim(); + var typeName = parts[2].Trim(); + var includeDescriptions = bool.TryParse(parts[3].Trim(), out var parsed) && parsed; + + entries.Add(new EnumMapEntry(group, @namespace, typeName, includeDescriptions)); + } + + return entries; + } + } +} diff --git a/src/CompaniesHouse.SourceGenerator/EnumValueTypeGenerator.cs b/src/CompaniesHouse.SourceGenerator/EnumValueTypeGenerator.cs new file mode 100644 index 0000000..9c5335c --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/EnumValueTypeGenerator.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace CompaniesHouse.SourceGenerator +{ + /// + /// Incremental generator that reads the Companies House api-enumerations YAML + /// (submodule, plan 05) and our local enumerations/extra/*.yml overlay, merges them + /// (submodule first, extras override/append), and emits the string-backed value types + /// (plan 03) configured in enum-map.txt. + /// + /// + /// This generator is referenced from CompaniesHouse.csproj as a build-time-only + /// analyzer (OutputItemType="Analyzer", ReferenceOutputAssembly="false") - it + /// is never shipped to consumers. The shipped package contains only the concrete generated + /// types, compiled straight into CompaniesHouse.dll. + /// + [Generator(LanguageNames.CSharp)] + public sealed class EnumValueTypeGenerator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var yamlFiles = context.AdditionalTextsProvider + .Where(static file => file.Path.EndsWith(".yml", StringComparison.OrdinalIgnoreCase)) + .Where(static file => !file.Path.Replace('\\', '/').Contains("/enum-map")) + .Select(static (file, ct) => (file.Path, Text: file.GetText(ct)?.ToString() ?? string.Empty)); + + var enumMapFiles = context.AdditionalTextsProvider + .Where(static file => System.IO.Path.GetFileName(file.Path).Equals("enum-map.txt", StringComparison.OrdinalIgnoreCase)) + .Select(static (file, ct) => file.GetText(ct)?.ToString() ?? string.Empty); + + var combined = yamlFiles.Collect().Combine(enumMapFiles.Collect()); + + context.RegisterSourceOutput(combined, static (spc, data) => + { + var (yamlSources, enumMapTexts) = data; + Execute(spc, yamlSources, enumMapTexts); + }); + } + + private static void Execute( + SourceProductionContext context, + ImmutableArray<(string Path, string Text)> yamlSources, + ImmutableArray enumMapTexts) + { + if (enumMapTexts.Length == 0) + { + return; + } + + var mapEntries = enumMapTexts.SelectMany(EnumMapParser.Parse).ToList(); + if (mapEntries.Count == 0) + { + return; + } + + // Submodule files first, then our local extras overlay - so extras override/append. + // See enumerations/extra/README.md for the documented merge rule. + var orderedYaml = yamlSources + .OrderBy(static f => IsExtraFile(f.Path) ? 1 : 0) + .ThenBy(static f => f.Path, StringComparer.Ordinal) + .Select(static f => MinimalYamlParser.Parse(f.Text)) + .ToList(); + + var mergedGroups = EnumDataMerger.Merge(orderedYaml); + + foreach (var mapEntry in mapEntries) + { + if (!mergedGroups.TryGetValue(mapEntry.Group, out var group)) + { + context.ReportDiagnostic(Diagnostic.Create( + MissingGroupDescriptor, + Location.None, + mapEntry.Group, + mapEntry.TypeName)); + continue; + } + + var valueTypeSource = ValueTypeEmitter.EmitValueType(mapEntry, group); + context.AddSource($"{mapEntry.TypeName}.g.cs", SourceText.From(valueTypeSource, System.Text.Encoding.UTF8)); + + var converterSource = ValueTypeEmitter.EmitJsonConverter(mapEntry); + context.AddSource($"{mapEntry.TypeName}JsonConverter.g.cs", SourceText.From(converterSource, System.Text.Encoding.UTF8)); + } + } + + private static bool IsExtraFile(string path) => + path.Replace('\\', '/').Contains("/enumerations/extra/"); + + private static readonly DiagnosticDescriptor MissingGroupDescriptor = new DiagnosticDescriptor( + id: "CHENUM001", + title: "Enum group not found in api-enumerations data", + messageFormat: "enum-map.txt configures group '{0}' -> '{1}' but no such group was found in the submodule or extras YAML", + category: "CompaniesHouse.SourceGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + } +} diff --git a/src/CompaniesHouse.SourceGenerator/MemberNameGenerator.cs b/src/CompaniesHouse.SourceGenerator/MemberNameGenerator.cs new file mode 100644 index 0000000..b5fdd6a --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/MemberNameGenerator.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Text; + +namespace CompaniesHouse.SourceGenerator +{ + /// + /// Converts a Companies House wire value (e.g. voluntary-arrangement) into a + /// PascalCase C# identifier suitable for a static member name (e.g. VoluntaryArrangement). + /// + internal static class MemberNameGenerator + { + /// + /// A small set of hand-picked overrides for wire values that the naive + /// PascalCase conversion would turn into an awkward or invalid identifier. + /// Keyed by wire value, ordinal. + /// + private static readonly Dictionary Overrides = + new Dictionary(System.StringComparer.Ordinal) + { + [""] = "Empty", + }; + + public static string ToMemberName(string wireValue) + { + if (Overrides.TryGetValue(wireValue, out var overridden)) + { + return overridden; + } + + var sb = new StringBuilder(); + var capitaliseNext = true; + + foreach (var c in wireValue) + { + if (!char.IsLetterOrDigit(c)) + { + capitaliseNext = true; + continue; + } + + sb.Append(capitaliseNext ? char.ToUpperInvariant(c) : c); + capitaliseNext = false; + } + + var result = sb.ToString(); + + if (result.Length == 0) + { + return "Empty"; + } + + if (char.IsDigit(result[0])) + { + result = "_" + result; + } + + return result; + } + } +} diff --git a/src/CompaniesHouse.SourceGenerator/MinimalYamlParser.cs b/src/CompaniesHouse.SourceGenerator/MinimalYamlParser.cs new file mode 100644 index 0000000..710bd44 --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/MinimalYamlParser.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; + +namespace CompaniesHouse.SourceGenerator +{ + /// + /// A minimal, hand-rolled parser for the specific YAML subset used by the + /// Companies House api-enumerations reference data and our own + /// enumerations/extra/*.yml overlay files. + /// + /// + /// + /// Only the shape actually used by these files is supported: + /// + /// + /// group_one: + /// 'wire-value' : "Friendly description" + /// 'other-value': "Another description" + /// group_two: + /// 'value': '' + /// + /// + /// Top-level (column 0) keys are group names. Indented lines under a group + /// are 'key' : "value" pairs, where the key and value may each be + /// single- or double-quoted (or left bare). Blank lines, lines consisting + /// solely of ---, and lines whose first non-whitespace character is + /// # are ignored. This is intentionally not a general-purpose YAML + /// parser - it will not handle nested mappings, sequences, block scalars, + /// flow style, anchors, or multi-line values. + /// + /// + internal static class MinimalYamlParser + { + /// + /// Parses into an ordered list of top-level groups, + /// each with its ordered list of wire-value/description entries. + /// + public static IReadOnlyList Parse(string yaml) + { + var groups = new List(); + YamlGroup? current = null; + + foreach (var rawLine in SplitLines(yaml)) + { + var line = rawLine; + + // Strip trailing comments/whitespace, but only when not inside quotes - + // none of our real files put '#' inside a quoted value, so a simple + // "not currently in a quote" scan is sufficient here. + line = StripComment(line); + + if (line.Trim().Length == 0) + { + continue; + } + + if (line.Trim() == "---") + { + continue; + } + + if (!char.IsWhiteSpace(line[0])) + { + // Top-level line -> new group. Format: "group_name:" + var name = line.TrimEnd().TrimEnd(':').Trim(); + if (name.Length == 0) + { + continue; + } + + current = new YamlGroup(name); + groups.Add(current); + continue; + } + + if (current is null) + { + // Indented content before any group header - ignore. + continue; + } + + var trimmed = line.Trim(); + var colonIndex = FindUnquotedColon(trimmed); + if (colonIndex < 0) + { + continue; + } + + var rawKey = trimmed.Substring(0, colonIndex).Trim(); + var rawValue = trimmed.Substring(colonIndex + 1).Trim(); + + var key = Unquote(rawKey); + var value = Unquote(rawValue); + + current.Entries.Add(new YamlEntry(key, value)); + } + + return groups; + } + + private static IEnumerable SplitLines(string text) + { + return text.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n'); + } + + private static string StripComment(string line) + { + var inSingleQuote = false; + var inDoubleQuote = false; + + for (var i = 0; i < line.Length; i++) + { + var c = line[i]; + if (c == '\'' && !inDoubleQuote) + { + inSingleQuote = !inSingleQuote; + } + else if (c == '"' && !inSingleQuote) + { + inDoubleQuote = !inDoubleQuote; + } + else if (c == '#' && !inSingleQuote && !inDoubleQuote) + { + return line.Substring(0, i); + } + } + + return line; + } + + private static int FindUnquotedColon(string text) + { + var inSingleQuote = false; + var inDoubleQuote = false; + + for (var i = 0; i < text.Length; i++) + { + var c = text[i]; + if (c == '\'' && !inDoubleQuote) + { + inSingleQuote = !inSingleQuote; + } + else if (c == '"' && !inSingleQuote) + { + inDoubleQuote = !inDoubleQuote; + } + else if (c == ':' && !inSingleQuote && !inDoubleQuote) + { + return i; + } + } + + return -1; + } + + private static string Unquote(string text) + { + if (text.Length >= 2) + { + if ((text[0] == '\'' && text[text.Length - 1] == '\'') || + (text[0] == '"' && text[text.Length - 1] == '"')) + { + return text.Substring(1, text.Length - 2); + } + } + + return text; + } + } + + /// A top-level YAML group (e.g. company_status) and its ordered entries. + internal sealed class YamlGroup + { + public YamlGroup(string name) + { + Name = name; + Entries = new List(); + } + + public string Name { get; } + + public List Entries { get; } + } + + /// A single 'wire-value': "description" entry within a . + internal readonly struct YamlEntry + { + public YamlEntry(string key, string value) + { + Key = key; + Value = value; + } + + public string Key { get; } + + public string Value { get; } + } +} diff --git a/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs b/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs new file mode 100644 index 0000000..2959b43 --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CompaniesHouse.SourceGenerator +{ + /// + /// Emits the C# source for a single enum group, matching the frozen string-backed value + /// type shape established in src/CompaniesHouse/Response/CompanyStatus.cs (plan 03). + /// + internal static class ValueTypeEmitter + { + /// Emits the readonly record struct value type. + public static string EmitValueType(EnumMapEntry entry, MergedGroup group) + { + var members = BuildMembers(group); + + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("// Generated by CompaniesHouse.SourceGenerator from the api-enumerations"); + sb.AppendLine("// submodule and enumerations/extra overlay. Do not hand-edit - see"); + sb.AppendLine("// .plans/completed/04-enum-source-generator.md."); + sb.AppendLine("#nullable enable"); + sb.AppendLine(); + sb.AppendLine("using System;"); + sb.AppendLine("using System.Collections.Generic;"); + sb.AppendLine("using System.Text.Json.Serialization;"); + sb.AppendLine("using CompaniesHouse.JsonConverters;"); + sb.AppendLine(); + sb.AppendLine($"namespace {entry.Namespace}"); + sb.AppendLine("{"); + sb.AppendLine($" [JsonConverter(typeof({entry.TypeName}JsonConverter))]"); + sb.AppendLine($" public readonly record struct {entry.TypeName}"); + sb.AppendLine(" {"); + sb.AppendLine(" private readonly string? _value;"); + sb.AppendLine(); + sb.AppendLine($" public {entry.TypeName}(string? value)"); + sb.AppendLine(" {"); + sb.AppendLine(" _value = value;"); + sb.AppendLine(" }"); + sb.AppendLine(); + sb.AppendLine(" public string Value => _value ?? string.Empty;"); + sb.AppendLine(); + sb.AppendLine(" public bool HasValue => !string.IsNullOrEmpty(_value);"); + sb.AppendLine(); + sb.AppendLine(" public bool IsKnown => KnownValues.Contains(Value);"); + sb.AppendLine(); + + if (entry.IncludeDescriptions) + { + sb.AppendLine(" public string? Description => Descriptions.TryGetValue(Value, out var description) ? description : null;"); + sb.AppendLine(); + } + + foreach (var (memberName, wireValue) in members) + { + sb.AppendLine($" public static {entry.TypeName} {memberName} => new(\"{Escape(wireValue)}\");"); + } + + sb.AppendLine(); + sb.AppendLine(" public override string ToString() => Value;"); + sb.AppendLine(); + sb.AppendLine(" private static readonly HashSet KnownValues = new(StringComparer.Ordinal)"); + sb.AppendLine(" {"); + foreach (var (_, wireValue) in members) + { + sb.AppendLine($" \"{Escape(wireValue)}\","); + } + sb.AppendLine(" };"); + + if (entry.IncludeDescriptions) + { + sb.AppendLine(); + sb.AppendLine(" private static readonly IReadOnlyDictionary Descriptions = new Dictionary(StringComparer.Ordinal)"); + sb.AppendLine(" {"); + foreach (var wireValue in group.WireValues) + { + sb.AppendLine($" [\"{Escape(wireValue)}\"] = \"{Escape(group.GetDescription(wireValue))}\","); + } + sb.AppendLine(" };"); + } + + sb.AppendLine(" }"); + sb.AppendLine("}"); + + return sb.ToString(); + } + + /// Emits the companion . + public static string EmitJsonConverter(EnumMapEntry entry) + { + var sb = new StringBuilder(); + sb.AppendLine("// "); + sb.AppendLine("// Generated by CompaniesHouse.SourceGenerator - do not hand-edit."); + sb.AppendLine("#nullable enable"); + sb.AppendLine(); + sb.AppendLine("using System;"); + sb.AppendLine("using System.Text.Json;"); + sb.AppendLine("using System.Text.Json.Serialization;"); + sb.AppendLine($"using {entry.Namespace};"); + sb.AppendLine(); + sb.AppendLine("namespace CompaniesHouse.JsonConverters"); + sb.AppendLine("{"); + sb.AppendLine($" public sealed class {entry.TypeName}JsonConverter : JsonConverter<{entry.TypeName}>"); + sb.AppendLine(" {"); + sb.AppendLine($" public override {entry.TypeName} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)"); + sb.AppendLine(" {"); + sb.AppendLine(" if (reader.TokenType == JsonTokenType.Null)"); + sb.AppendLine(" {"); + sb.AppendLine(" return default;"); + sb.AppendLine(" }"); + sb.AppendLine(); + sb.AppendLine($" return new {entry.TypeName}(reader.GetString());"); + sb.AppendLine(" }"); + sb.AppendLine(); + sb.AppendLine($" public override void Write(Utf8JsonWriter writer, {entry.TypeName} value, JsonSerializerOptions options)"); + sb.AppendLine(" {"); + sb.AppendLine(" if (!value.HasValue)"); + sb.AppendLine(" {"); + sb.AppendLine(" writer.WriteNullValue();"); + sb.AppendLine(" return;"); + sb.AppendLine(" }"); + sb.AppendLine(); + sb.AppendLine(" writer.WriteStringValue(value.Value);"); + sb.AppendLine(" }"); + sb.AppendLine(" }"); + sb.AppendLine("}"); + + return sb.ToString(); + } + + /// + /// Builds the ordered, de-duplicated (member name, wire value) pairs for a group, skipping + /// an empty-string wire value (which is represented by the type's absent/default state - see + /// plan 03 - rather than a static member). + /// + private static List<(string MemberName, string WireValue)> BuildMembers(MergedGroup group) + { + var members = new List<(string MemberName, string WireValue)>(); + var usedNames = new HashSet(StringComparer.Ordinal); + + foreach (var wireValue in group.WireValues) + { + if (wireValue.Length == 0) + { + continue; + } + + var memberName = MemberNameGenerator.ToMemberName(wireValue); + if (!usedNames.Add(memberName)) + { + throw new InvalidOperationException( + $"CompaniesHouse.SourceGenerator: generated member name '{memberName}' for group " + + $"'{group.Name}' collides with another wire value. Add an override in " + + $"{nameof(MemberNameGenerator)}."); + } + + members.Add((memberName, wireValue)); + } + + return members; + } + + private static string Escape(string value) => value.Replace("\\", "\\\\").Replace("\"", "\\\""); + } +} diff --git a/src/CompaniesHouse/CompaniesHouse.csproj b/src/CompaniesHouse/CompaniesHouse.csproj index 3bba0be..e187d60 100644 --- a/src/CompaniesHouse/CompaniesHouse.csproj +++ b/src/CompaniesHouse/CompaniesHouse.csproj @@ -27,4 +27,22 @@ + + + + + + + + + + + diff --git a/src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs b/src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs deleted file mode 100644 index 34a2d88..0000000 --- a/src/CompaniesHouse/JsonConverters/CompanyStatusJsonConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using CompaniesHouse.Response; - -namespace CompaniesHouse.JsonConverters -{ - /// - /// Reads/writes a as its raw wire string. Performs no - /// validation or lookup — unrecognised values are preserved rather than rejected. - /// - public sealed class CompanyStatusJsonConverter : JsonConverter - { - public override CompanyStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) - { - return default; - } - - return new CompanyStatus(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, CompanyStatus value, JsonSerializerOptions options) - { - if (!value.HasValue) - { - writer.WriteNullValue(); - return; - } - - writer.WriteStringValue(value.Value); - } - } -} diff --git a/src/CompaniesHouse/Response/CompanyStatus.cs b/src/CompaniesHouse/Response/CompanyStatus.cs deleted file mode 100644 index 22a7cfa..0000000 --- a/src/CompaniesHouse/Response/CompanyStatus.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; -using CompaniesHouse.JsonConverters; - -namespace CompaniesHouse.Response -{ - /// - /// The status of a company, as returned by the Companies House API. - /// - /// - /// This is a string-backed value type rather than a plain C# . - /// Companies House do not reliably version their API, so new status values can - /// appear in responses at any time. A string-backed type preserves the raw wire - /// value and never throws on an unrecognised value — see - /// . - /// - [JsonConverter(typeof(CompanyStatusJsonConverter))] - public readonly record struct CompanyStatus - { - private readonly string? _value; - - public CompanyStatus(string? value) - { - _value = value; - } - - /// - /// The raw wire value. Never — an absent/unknown - /// value is represented as ; see . - /// - public string Value => _value ?? string.Empty; - - /// - /// if a value was present on the wire (including any - /// unrecognised value); for the /absent case. - /// - public bool HasValue => !string.IsNullOrEmpty(_value); - - /// - /// if is one of the values known to this - /// version of the library at the time of writing. - /// - public bool IsKnown => KnownValues.Contains(Value); - - /// - /// The human-readable description of , sourced from the - /// Companies House api-enumerations reference data, or - /// if no description is known for this value. - /// - public string? Description => Descriptions.TryGetValue(Value, out var description) ? description : null; - - public static CompanyStatus Active => new("active"); - public static CompanyStatus Dissolved => new("dissolved"); - public static CompanyStatus Liquidation => new("liquidation"); - public static CompanyStatus Receivership => new("receivership"); - public static CompanyStatus Administration => new("administration"); - public static CompanyStatus VoluntaryArrangement => new("voluntary-arrangement"); - public static CompanyStatus ConvertedClosed => new("converted-closed"); - public static CompanyStatus InsolvencyProceedings => new("insolvency-proceedings"); - public static CompanyStatus Open => new("open"); - public static CompanyStatus Closed => new("closed"); - public static CompanyStatus ClosedOn => new("closed-on"); - public static CompanyStatus Registered => new("registered"); - public static CompanyStatus Removed => new("removed"); - - public override string ToString() => Value; - - private static readonly HashSet KnownValues = new(StringComparer.Ordinal) - { - "active", - "dissolved", - "liquidation", - "receivership", - "administration", - "voluntary-arrangement", - "converted-closed", - "insolvency-proceedings", - "open", - "closed", - "closed-on", - "registered", - "removed", - }; - - // Sourced from https://github.com/companieshouse/api-enumerations constants.yml (company_status). - private static readonly IReadOnlyDictionary Descriptions = new Dictionary(StringComparer.Ordinal) - { - ["active"] = "Active", - ["dissolved"] = "Dissolved", - ["liquidation"] = "Liquidation", - ["receivership"] = "Receiver Action", - ["converted-closed"] = "Converted / Closed", - ["voluntary-arrangement"] = "Voluntary Arrangement", - ["insolvency-proceedings"] = "Insolvency Proceedings", - ["administration"] = "In Administration", - ["open"] = "Open", - ["closed"] = "Closed", - ["registered"] = "Registered", - ["removed"] = "Removed", - }; - } -} diff --git a/src/CompaniesHouse/enum-map.txt b/src/CompaniesHouse/enum-map.txt new file mode 100644 index 0000000..d10a10d --- /dev/null +++ b/src/CompaniesHouse/enum-map.txt @@ -0,0 +1,10 @@ +# Configures which api-enumerations YAML groups (submodule + enumerations/extra +# overlay - see enumerations/extra/README.md) are generated into string-backed +# value types (plan 03) by CompaniesHouse.SourceGenerator (plan 04). +# +# Format: group|namespace|TypeName|includeDescriptions +# +# Adding a new value to the relevant YAML file and rebuilding is enough to pick +# up a new static member - no hand-editing of generated types required. + +company_status|CompaniesHouse.Response|CompanyStatus|true diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/CompaniesHouse.SourceGenerator.Tests.csproj b/tests/CompaniesHouse.SourceGenerator.Tests/CompaniesHouse.SourceGenerator.Tests.csproj new file mode 100644 index 0000000..df9e06a --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/CompaniesHouse.SourceGenerator.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + false + + + + + + + + + + + + + + + + diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/EnumDataMergerTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/EnumDataMergerTests.cs new file mode 100644 index 0000000..b6b556b --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/EnumDataMergerTests.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using CompaniesHouse.SourceGenerator; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.SourceGenerator.Tests +{ + public class EnumDataMergerTests + { + [Fact] + public void LaterSourcesOverrideDescriptionsForTheSameWireValue() + { + var submodule = MinimalYamlParser.Parse(""" + company_status: + 'active' : "Active" + 'closed' : "Closed" + """); + + var extras = MinimalYamlParser.Parse(""" + company_status: + 'closed' : "Overridden closed description" + """); + + var merged = EnumDataMerger.Merge(new[] { submodule, extras }); + + merged["company_status"].GetDescription("closed").ShouldBe("Overridden closed description"); + merged["company_status"].GetDescription("active").ShouldBe("Active"); + } + + [Fact] + public void LaterSourcesAppendNewWireValuesToAnExistingGroup() + { + var submodule = MinimalYamlParser.Parse(""" + company_status: + 'active' : "Active" + """); + + var extras = MinimalYamlParser.Parse(""" + company_status: + 'closed-on' : "Closed On" + """); + + var merged = EnumDataMerger.Merge(new[] { submodule, extras }); + + merged["company_status"].WireValues.ShouldBe(new[] { "active", "closed-on" }); + } + + [Fact] + public void LaterSourcesCanIntroduceABrandNewGroup() + { + var submodule = MinimalYamlParser.Parse(""" + company_status: + 'active' : "Active" + """); + + var extras = MinimalYamlParser.Parse(""" + library_only_group: + 'foo' : "Bar" + """); + + var merged = EnumDataMerger.Merge(new[] { submodule, extras }); + + merged.Keys.ShouldBe(new[] { "company_status", "library_only_group" }, ignoreOrder: true); + merged["library_only_group"].WireValues.Single().ShouldBe("foo"); + } + + [Fact] + public void PreservesFirstSeenOrderOfWireValues() + { + var submodule = MinimalYamlParser.Parse(""" + company_status: + 'active' : "Active" + 'dissolved' : "Dissolved" + 'liquidation' : "Liquidation" + """); + + var merged = EnumDataMerger.Merge(new[] { submodule }); + + merged["company_status"].WireValues.ShouldBe(new[] { "active", "dissolved", "liquidation" }); + } + } +} diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/EnumMapParserTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/EnumMapParserTests.cs new file mode 100644 index 0000000..0962281 --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/EnumMapParserTests.cs @@ -0,0 +1,52 @@ +using System.Linq; +using CompaniesHouse.SourceGenerator; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.SourceGenerator.Tests +{ + public class EnumMapParserTests + { + [Fact] + public void ParsesAWellFormedEntry() + { + var entries = EnumMapParser.Parse("company_status|CompaniesHouse.Response|CompanyStatus|true"); + + entries.Count.ShouldBe(1); + entries[0].Group.ShouldBe("company_status"); + entries[0].Namespace.ShouldBe("CompaniesHouse.Response"); + entries[0].TypeName.ShouldBe("CompanyStatus"); + entries[0].IncludeDescriptions.ShouldBeTrue(); + } + + [Fact] + public void SkipsBlankLinesAndCommentLines() + { + const string text = """ + # a comment + + company_status|CompaniesHouse.Response|CompanyStatus|true + """; + + var entries = EnumMapParser.Parse(text); + + entries.Count.ShouldBe(1); + } + + [Fact] + public void SkipsMalformedLinesWithTheWrongNumberOfFields() + { + var entries = EnumMapParser.Parse("company_status|CompaniesHouse.Response|CompanyStatus"); + + entries.ShouldBeEmpty(); + } + + [Fact] + public void TreatsAMissingOrUnparsableIncludeDescriptionsFlagAsFalse() + { + var entries = EnumMapParser.Parse("company_status|CompaniesHouse.Response|CompanyStatus|notabool"); + + entries.Single().IncludeDescriptions.ShouldBeFalse(); + } + } +} diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs new file mode 100644 index 0000000..298e5d0 --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using CompaniesHouse.SourceGenerator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.SourceGenerator.Tests +{ + /// + /// End-to-end tests that run through a real + /// with in-memory s standing + /// in for the api-enumerations submodule + extras + enum-map.txt config. + /// + public class EnumValueTypeGeneratorTests + { + [Fact] + public void GeneratesAValueTypeAndConverterForAConfiguredGroup() + { + var additionalFiles = new[] + { + new InMemoryAdditionalText(@"external\api-enumerations\constants.yml", """ + company_status: + 'active' : "Active" + 'dissolved' : "Dissolved" + """), + new InMemoryAdditionalText(@"enum-map.txt", "company_status|CompaniesHouse.Response|CompanyStatus|true"), + }; + + var generated = RunGenerator(additionalFiles); + + generated.ShouldContainKey("CompanyStatus.g.cs"); + generated["CompanyStatus.g.cs"].ShouldContain("public readonly record struct CompanyStatus"); + generated["CompanyStatus.g.cs"].ShouldContain("public static CompanyStatus Active => new(\"active\");"); + generated["CompanyStatus.g.cs"].ShouldContain("public static CompanyStatus Dissolved => new(\"dissolved\");"); + generated["CompanyStatus.g.cs"].ShouldContain("[\"active\"] = \"Active\""); + + generated.ShouldContainKey("CompanyStatusJsonConverter.g.cs"); + generated["CompanyStatusJsonConverter.g.cs"].ShouldContain("public sealed class CompanyStatusJsonConverter : JsonConverter"); + } + + [Fact] + public void ExtrasOverlayOverridesAndAppendsToSubmoduleData() + { + var additionalFiles = new[] + { + new InMemoryAdditionalText(@"external\api-enumerations\constants.yml", """ + company_status: + 'active' : "Active" + """), + new InMemoryAdditionalText(@"C:\repo\enumerations\extra\company_status.yml", """ + company_status: + 'active' : "Overridden" + 'closed-on' : "Closed On" + """), + new InMemoryAdditionalText(@"enum-map.txt", "company_status|CompaniesHouse.Response|CompanyStatus|true"), + }; + + var generated = RunGenerator(additionalFiles); + + generated["CompanyStatus.g.cs"].ShouldContain("[\"active\"] = \"Overridden\""); + generated["CompanyStatus.g.cs"].ShouldContain("public static CompanyStatus ClosedOn => new(\"closed-on\");"); + } + + [Fact] + public void DoesNotGenerateAMemberForAnEmptyStringWireValue() + { + var additionalFiles = new[] + { + new InMemoryAdditionalText(@"external\api-enumerations\constants.yml", """ + company_status_detail: + 'active' : "" + 'dissolved' : "" + """), + new InMemoryAdditionalText(@"enum-map.txt", "company_status_detail|CompaniesHouse.Response|CompanyStatusDetail|false"), + }; + + var generated = RunGenerator(additionalFiles); + + generated["CompanyStatusDetail.g.cs"].ShouldNotContain("public static CompanyStatusDetail Empty"); + generated["CompanyStatusDetail.g.cs"].ShouldNotContain("Description =>"); + } + + [Fact] + public void ReportsADiagnosticWhenAConfiguredGroupIsNotFoundInAnyYaml() + { + var additionalFiles = new[] + { + new InMemoryAdditionalText(@"external\api-enumerations\constants.yml", """ + company_status: + 'active' : "Active" + """), + new InMemoryAdditionalText(@"enum-map.txt", "no_such_group|CompaniesHouse.Response|NoSuchGroup|false"), + }; + + var diagnostics = RunGeneratorAndGetDiagnostics(additionalFiles); + + diagnostics.ShouldContain(d => d.Id == "CHENUM001"); + } + + private static Dictionary RunGenerator(IEnumerable additionalFiles) + { + var compilation = CreateCompilation(); + var generator = new EnumValueTypeGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(new IIncrementalGenerator[] { generator }); + driver = driver.AddAdditionalTexts(additionalFiles.Cast().ToImmutableArray()); + + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out var runDiagnostics); + var runResult = driver.GetRunResult(); + + var faulted = runResult.Results.FirstOrDefault(r => r.Exception is not null); + if (faulted.Exception is not null) + { + throw faulted.Exception; + } + + if (runDiagnostics.Any()) + { + throw new System.Exception("Generator diagnostics: " + string.Join("\n", runDiagnostics)); + } + + return runResult.Results + .Where(r => !r.GeneratedSources.IsDefault) + .SelectMany(r => r.GeneratedSources) + .ToDictionary(s => s.HintName, s => s.SourceText.ToString()); + } + + private static List RunGeneratorAndGetDiagnostics(IEnumerable additionalFiles) + { + var compilation = CreateCompilation(); + var generator = new EnumValueTypeGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(new IIncrementalGenerator[] { generator }); + driver = driver.AddAdditionalTexts(additionalFiles.Cast().ToImmutableArray()); + + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out var diagnostics); + + return diagnostics.ToList(); + } + + private static CSharpCompilation CreateCompilation() + { + return CSharpCompilation.Create( + "CompaniesHouse.SourceGenerator.Tests.GeneratedAssembly", + references: new[] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + }, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + } + } +} diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/InMemoryAdditionalText.cs b/tests/CompaniesHouse.SourceGenerator.Tests/InMemoryAdditionalText.cs new file mode 100644 index 0000000..1d39907 --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/InMemoryAdditionalText.cs @@ -0,0 +1,21 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace CompaniesHouse.SourceGenerator.Tests +{ + /// A minimal in-memory for driving the generator in tests. + internal sealed class InMemoryAdditionalText : AdditionalText + { + private readonly SourceText _text; + + public InMemoryAdditionalText(string path, string content) + { + Path = path; + _text = SourceText.From(content, System.Text.Encoding.UTF8); + } + + public override string Path { get; } + + public override SourceText GetText(System.Threading.CancellationToken cancellationToken = default) => _text; + } +} diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/MemberNameGeneratorTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/MemberNameGeneratorTests.cs new file mode 100644 index 0000000..35e5ad6 --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/MemberNameGeneratorTests.cs @@ -0,0 +1,38 @@ +using CompaniesHouse.SourceGenerator; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.SourceGenerator.Tests +{ + public class MemberNameGeneratorTests + { + [Theory] + [InlineData("active", "Active")] + [InlineData("voluntary-arrangement", "VoluntaryArrangement")] + [InlineData("closed-on", "ClosedOn")] + [InlineData("converted-to-ukeig", "ConvertedToUkeig")] + [InlineData("accounting-requirements-of-originating-country-do-not-apply", "AccountingRequirementsOfOriginatingCountryDoNotApply")] + public void ConvertsWireValuesToPascalCase(string wireValue, string expected) + { + MemberNameGenerator.ToMemberName(wireValue).ShouldBe(expected); + } + + [Fact] + public void MapsEmptyStringToEmptyMemberName() + { + MemberNameGenerator.ToMemberName(string.Empty).ShouldBe("Empty"); + } + + [Fact] + public void PrefixesAnUnderscoreWhenTheResultWouldStartWithADigit() + { + MemberNameGenerator.ToMemberName("2024").ShouldBe("_2024"); + } + + [Fact] + public void CollapsesRunsOfNonAlphanumericCharacters() + { + MemberNameGenerator.ToMemberName("foo--bar__baz").ShouldBe("FooBarBaz"); + } + } +} diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/MinimalYamlParserTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/MinimalYamlParserTests.cs new file mode 100644 index 0000000..c2f3899 --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/MinimalYamlParserTests.cs @@ -0,0 +1,118 @@ +using System.Linq; +using CompaniesHouse.SourceGenerator; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.SourceGenerator.Tests +{ + public class MinimalYamlParserTests + { + [Fact] + public void ParsesASingleGroupWithQuotedKeysAndValues() + { + const string yaml = """ + company_status: + 'active' : "Active" + 'dissolved' : "Dissolved" + """; + + var groups = MinimalYamlParser.Parse(yaml); + + groups.Count.ShouldBe(1); + groups[0].Name.ShouldBe("company_status"); + groups[0].Entries.Select(e => (e.Key, e.Value)).ShouldBe(new[] + { + ("active", "Active"), + ("dissolved", "Dissolved"), + }); + } + + [Fact] + public void ParsesMultipleGroupsInFirstSeenOrder() + { + const string yaml = """ + company_status: + 'active' : "Active" + company_type: + 'ltd' : "Private limited company" + """; + + var groups = MinimalYamlParser.Parse(yaml); + + groups.Select(g => g.Name).ShouldBe(new[] { "company_status", "company_type" }); + groups[1].Entries.Single().Key.ShouldBe("ltd"); + } + + [Fact] + public void SkipsBlankLinesCommentsAndDocumentMarkers() + { + const string yaml = """ + --- + # a top level comment + company_status: + # an indented comment + 'active' : "Active" # trailing comment + + 'dissolved' : "Dissolved" + """; + + var groups = MinimalYamlParser.Parse(yaml); + + groups.Count.ShouldBe(1); + groups[0].Entries.Count.ShouldBe(2); + groups[0].Entries[0].Value.ShouldBe("Active"); + } + + [Fact] + public void HandlesUnquotedAndEmptyValues() + { + const string yaml = """ + company_status_detail: + 'active' : "" + 'dissolved': '' + """; + + var groups = MinimalYamlParser.Parse(yaml); + + groups[0].Entries.Select(e => (e.Key, e.Value)).ShouldBe(new[] + { + ("active", ""), + ("dissolved", ""), + }); + } + + [Fact] + public void DoesNotTreatAHashInsideAQuotedValueAsAComment() + { + const string yaml = """ + company_type: + 'ltd' : "Private # limited company" + """; + + var groups = MinimalYamlParser.Parse(yaml); + + groups[0].Entries[0].Value.ShouldBe("Private # limited company"); + } + + [Fact] + public void ReturnsNoGroupsForEmptyInput() + { + MinimalYamlParser.Parse(string.Empty).ShouldBeEmpty(); + } + + [Fact] + public void IgnoresIndentedLinesBeforeAnyGroupHeader() + { + const string yaml = """ + 'orphan' : "Should be ignored" + company_status: + 'active' : "Active" + """; + + var groups = MinimalYamlParser.Parse(yaml); + + groups.Count.ShouldBe(1); + groups[0].Entries.Single().Key.ShouldBe("active"); + } + } +} From 7105567296e010b6164155c865130da604ff405b Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 22:56:50 +0100 Subject: [PATCH 08/38] Implement the remaining search endpoints 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. --- ...sHouseClientServiceCollectionExtensions.cs | 12 ++ src/CompaniesHouse/CompaniesHouseClient.cs | 18 +++ .../CompaniesHouseSearchClient.cs | 1 - ...mpaniesHouseAdvancedCompanySearchClient.cs | 12 ++ src/CompaniesHouse/ICompaniesHouseClient.cs | 3 + .../ICompaniesHouseSearchClient.cs | 3 +- ...ouseSearchCompaniesAlphabeticallyClient.cs | 12 ++ ...niesHouseSearchDissolvedCompaniesClient.cs | 12 ++ .../ISearchUriBuilderFactory.cs | 2 +- .../Request/AdvancedCompanySearchRequest.cs | 35 ++++++ .../SearchCompaniesAlphabeticallyRequest.cs | 14 +++ .../Request/SearchCompanyRequest.cs | 2 +- .../SearchDissolvedCompaniesRequest.cs | 18 +++ src/CompaniesHouse/Response/CompanyType.cs | 115 ------------------ .../AdvancedCompanySearch.cs | 22 ++++ .../Search/AdvancedCompanySearch/Company.cs | 44 +++++++ .../CompaniesAlphabeticallySearch.cs | 16 +++ .../CompaniesAlphabeticallySearch/Company.cs | 29 +++++ .../Response/Search/CompanyProfileLinks.cs | 10 ++ .../DissolvedCompaniesSearch/Company.cs | 41 +++++++ .../DissolvedCompaniesSearch.cs | 22 ++++ .../PreviousCompanyName.cs | 20 +++ src/CompaniesHouse/SearchUriBuilderFactory.cs | 26 +++- .../AdvancedCompanySearchUriBuilder.cs | 84 +++++++++++++ .../UriBuilders/ISearchUriBuilder.cs | 2 +- ...SearchCompaniesAlphabeticallyUriBuilder.cs | 36 ++++++ .../UriBuilders/SearchCompanyUriBuilder.cs | 4 +- .../SearchDissolvedCompaniesUriBuilder.cs | 41 +++++++ src/CompaniesHouse/enum-map.txt | 2 + .../ServiceCollectionExtensionsTests.cs | 5 + .../AdvancedCompanySearchTests.cs | 32 +++++ .../CompaniesAlphabeticalSearchTests.cs | 32 +++++ .../DissolvedCompaniesSearchTests.cs | 33 +++++ .../EnumValueTypeGeneratorTests.cs | 24 ++++ ...archClientTestsForAdvancedCompanySearch.cs | 87 +++++++++++++ ...ntTestsForCompaniesAlphabeticallySearch.cs | 63 ++++++++++ ...hClientTestsForDissolvedCompaniesSearch.cs | 106 ++++++++++++++++ .../EnumerationMappings.cs | 11 +- .../EquivalencyAssertionExtensions.cs | 48 +++++++- .../ResponseValueTypes/CompanySubtypeTests.cs | 45 +++++++ .../ResponseValueTypes/CompanyTypeTests.cs | 56 +++++++++ .../AdvancedCompanySearchUriBuilderTests.cs | 47 +++++++ ...hCompaniesAlphabeticallyUriBuilderTests.cs | 40 ++++++ .../SearchCompanyUriBuilderTestsBase+Thens.cs | 43 +++++++ .../SearchCompanyUriBuilderTestsBase.cs | 28 +++++ ...UriBuilderTestsForRestrictionsWhenEmpty.cs | 12 ++ ...yUriBuilderTestsForRestrictionsWhenNull.cs | 10 ++ ...BuilderTestsForRestrictionsWhenProvided.cs | 12 ++ ...ilderTestsForRestrictionsWhenWhitespace.cs | 12 ++ ...SearchDissolvedCompaniesUriBuilderTests.cs | 43 +++++++ 50 files changed, 1314 insertions(+), 133 deletions(-) create mode 100644 src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs create mode 100644 src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs create mode 100644 src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs create mode 100644 src/CompaniesHouse/Request/AdvancedCompanySearchRequest.cs create mode 100644 src/CompaniesHouse/Request/SearchCompaniesAlphabeticallyRequest.cs create mode 100644 src/CompaniesHouse/Request/SearchDissolvedCompaniesRequest.cs delete mode 100644 src/CompaniesHouse/Response/CompanyType.cs create mode 100644 src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs create mode 100644 src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs create mode 100644 src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs create mode 100644 src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs create mode 100644 src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs create mode 100644 src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs create mode 100644 src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs create mode 100644 src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs create mode 100644 src/CompaniesHouse/UriBuilders/AdvancedCompanySearchUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/SearchCompaniesAlphabeticallyUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/SearchDissolvedCompaniesUriBuilder.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/CompanySubtypeTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyTypeTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/AdvancedCompanySearchUriBuilderTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompaniesAlphabeticallyUriBuilderTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase+Thens.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenEmpty.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenNull.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenProvided.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenWhitespace.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchDissolvedCompaniesUriBuilderTests.cs diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs index e126004..ea7e3b7 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs @@ -155,6 +155,12 @@ private static IServiceCollection TryAddCompaniesHouseSubClients(this IServiceCo provider.GetRequiredService()); services.TryAddTransient(provider => provider.GetRequiredService()); + services.TryAddTransient(provider => + provider.GetRequiredService()); + services.TryAddTransient(provider => + provider.GetRequiredService()); + services.TryAddTransient(provider => + provider.GetRequiredService()); services.TryAddTransient(provider => provider.GetRequiredService()); services.TryAddTransient(provider => @@ -329,6 +335,12 @@ private static IServiceCollection TryAddKeyedCompaniesHouseSubClients(this IServ provider.GetRequiredKeyedService(key)); services.TryAddKeyedTransient(name, (provider, key) => provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); services.TryAddKeyedTransient(name, (provider, key) => provider.GetRequiredKeyedService(key)); services.TryAddKeyedTransient(name, (provider, key) => diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index ecee2ca..f459117 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -12,8 +12,11 @@ using CompaniesHouse.Response.PersonsWithSignificantControl; using CompaniesHouse.Response.RegisteredOfficeAddress; using CompaniesHouse.Response.Search.AllSearch; +using CompaniesHouse.Response.Search.AdvancedCompanySearch; +using CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch; using CompaniesHouse.Response.Search.CompanySearch; using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch; +using CompaniesHouse.Response.Search.DissolvedCompaniesSearch; using CompaniesHouse.Response.Search.OfficerSearch; using CompaniesHouse.UriBuilders; using Officer = CompaniesHouse.Response.Officers.Officer; @@ -74,6 +77,21 @@ public CompaniesHouseClient(ICompaniesHouseSettings settings) return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } + public Task> SearchCompaniesAlphabeticallyAsync(SearchCompaniesAlphabeticallyRequest request, CancellationToken cancellationToken = default(CancellationToken)) + { + return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); + } + + public Task> SearchDissolvedCompaniesAsync(SearchDissolvedCompaniesRequest request, CancellationToken cancellationToken = default(CancellationToken)) + { + return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); + } + + public Task> AdvancedCompanySearchAsync(AdvancedCompanySearchRequest request, CancellationToken cancellationToken = default(CancellationToken)) + { + return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); + } + public Task> GetCompanyProfileAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseCompanyProfileClient.GetCompanyProfileAsync(companyNumber, cancellationToken); diff --git a/src/CompaniesHouse/CompaniesHouseSearchClient.cs b/src/CompaniesHouse/CompaniesHouseSearchClient.cs index d5fc5e0..241b591 100644 --- a/src/CompaniesHouse/CompaniesHouseSearchClient.cs +++ b/src/CompaniesHouse/CompaniesHouseSearchClient.cs @@ -20,7 +20,6 @@ public CompaniesHouseSearchClient(HttpClient httpClient, ISearchUriBuilderFactor public async Task> SearchAsync(TSearchRequest request, CancellationToken cancellationToken = default(CancellationToken)) - where TSearchRequest : SearchRequest { var searchUriBuilder = _searchUriBuilderFactory.Create(); var requestUri = searchUriBuilder.Build(request); diff --git a/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs b/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs new file mode 100644 index 0000000..c2f8a9d --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response.Search.AdvancedCompanySearch; + +namespace CompaniesHouse +{ + public interface ICompaniesHouseAdvancedCompanySearchClient + { + Task> AdvancedCompanySearchAsync(AdvancedCompanySearchRequest request, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/CompaniesHouse/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index 909d32d..209a2a9 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -5,6 +5,9 @@ public interface ICompaniesHouseClient : ICompaniesHouseSearchOfficerClient, ICompaniesHouseSearchDisqualifiedOfficerClient, ICompaniesHouseSearchAllClient, + ICompaniesHouseSearchCompaniesAlphabeticallyClient, + ICompaniesHouseSearchDissolvedCompaniesClient, + ICompaniesHouseAdvancedCompanySearchClient, ICompaniesHouseCompanyProfileClient, ICompaniesHouseCompanyFilingHistoryClient, ICompaniesHouseOfficersClient, diff --git a/src/CompaniesHouse/ICompaniesHouseSearchClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchClient.cs index d1ca17f..4f508a2 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchClient.cs @@ -7,7 +7,6 @@ namespace CompaniesHouse public interface ICompaniesHouseSearchClient { Task> SearchAsync(TSearchRequest request, - CancellationToken cancellationToken = default(CancellationToken)) - where TSearchRequest : SearchRequest; + CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs new file mode 100644 index 0000000..11f38cd --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch; + +namespace CompaniesHouse +{ + public interface ICompaniesHouseSearchCompaniesAlphabeticallyClient + { + Task> SearchCompaniesAlphabeticallyAsync(SearchCompaniesAlphabeticallyRequest request, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs new file mode 100644 index 0000000..15470e0 --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response.Search.DissolvedCompaniesSearch; + +namespace CompaniesHouse +{ + public interface ICompaniesHouseSearchDissolvedCompaniesClient + { + Task> SearchDissolvedCompaniesAsync(SearchDissolvedCompaniesRequest request, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/src/CompaniesHouse/ISearchUriBuilderFactory.cs b/src/CompaniesHouse/ISearchUriBuilderFactory.cs index f2880d7..3f1df77 100644 --- a/src/CompaniesHouse/ISearchUriBuilderFactory.cs +++ b/src/CompaniesHouse/ISearchUriBuilderFactory.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse { public interface ISearchUriBuilderFactory { - ISearchUriBuilder Create() where TSearch : SearchRequest; + ISearchUriBuilder Create(); } } \ No newline at end of file diff --git a/src/CompaniesHouse/Request/AdvancedCompanySearchRequest.cs b/src/CompaniesHouse/Request/AdvancedCompanySearchRequest.cs new file mode 100644 index 0000000..8156edf --- /dev/null +++ b/src/CompaniesHouse/Request/AdvancedCompanySearchRequest.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Search.AdvancedCompanySearch; + +namespace CompaniesHouse.Request; + +public class AdvancedCompanySearchRequest +{ + public string? CompanyNameIncludes { get; set; } + + public string? CompanyNameExcludes { get; set; } + + public IReadOnlyCollection? CompanyStatuses { get; set; } + + public IReadOnlyCollection? CompanySubtypes { get; set; } + + public IReadOnlyCollection? CompanyTypes { get; set; } + + public DateTime? DissolvedFrom { get; set; } + + public DateTime? DissolvedTo { get; set; } + + public DateTime? IncorporatedFrom { get; set; } + + public DateTime? IncorporatedTo { get; set; } + + public string? Location { get; set; } + + public IReadOnlyCollection? SicCodes { get; set; } + + public int? Size { get; set; } + + public int? StartIndex { get; set; } +} diff --git a/src/CompaniesHouse/Request/SearchCompaniesAlphabeticallyRequest.cs b/src/CompaniesHouse/Request/SearchCompaniesAlphabeticallyRequest.cs new file mode 100644 index 0000000..0cf7aae --- /dev/null +++ b/src/CompaniesHouse/Request/SearchCompaniesAlphabeticallyRequest.cs @@ -0,0 +1,14 @@ +using CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch; + +namespace CompaniesHouse.Request; + +public class SearchCompaniesAlphabeticallyRequest +{ + public string Query { get; set; } = ""; + + public string? SearchAbove { get; set; } + + public string? SearchBelow { get; set; } + + public int? Size { get; set; } +} diff --git a/src/CompaniesHouse/Request/SearchCompanyRequest.cs b/src/CompaniesHouse/Request/SearchCompanyRequest.cs index de47963..e7eb397 100644 --- a/src/CompaniesHouse/Request/SearchCompanyRequest.cs +++ b/src/CompaniesHouse/Request/SearchCompanyRequest.cs @@ -4,5 +4,5 @@ namespace CompaniesHouse.Request; public class SearchCompanyRequest : SearchRequest { - public string Restrictions { get; set; } + public string? Restrictions { get; set; } } \ No newline at end of file diff --git a/src/CompaniesHouse/Request/SearchDissolvedCompaniesRequest.cs b/src/CompaniesHouse/Request/SearchDissolvedCompaniesRequest.cs new file mode 100644 index 0000000..3e08a79 --- /dev/null +++ b/src/CompaniesHouse/Request/SearchDissolvedCompaniesRequest.cs @@ -0,0 +1,18 @@ +using CompaniesHouse.Response.Search.DissolvedCompaniesSearch; + +namespace CompaniesHouse.Request; + +public class SearchDissolvedCompaniesRequest +{ + public string Query { get; set; } = ""; + + public string SearchType { get; set; } = ""; + + public string? SearchAbove { get; set; } + + public string? SearchBelow { get; set; } + + public int? Size { get; set; } + + public int? StartIndex { get; set; } +} diff --git a/src/CompaniesHouse/Response/CompanyType.cs b/src/CompaniesHouse/Response/CompanyType.cs deleted file mode 100644 index 096338f..0000000 --- a/src/CompaniesHouse/Response/CompanyType.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum CompanyType - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "private-unlimited")] - PrivateUnlimited, - - [EnumMember(Value = "ltd")] - Ltd, - - [EnumMember(Value = "plc")] - Plc, - - [EnumMember(Value = "old-public-company")] - OldPublicCompany, - - [EnumMember(Value = "private-limited-guarant-nsc-limited-exemption")] - PrivateLimitedGuarantNscLimitedExemption, - - [EnumMember(Value = "limited-partnership")] - LimitedPartnership, - - [EnumMember(Value = "private-limited-guarant-nsc")] - PrivateLimitedGuarantNsc, - - [EnumMember(Value = "converted-or-closed")] - ConvertedOrClosed, - - [EnumMember(Value = "private-unlimited-nsc")] - PrivateUnlimitedNsc, - - [EnumMember(Value = "private-limited-shares-section-30-exemption")] - PrivateLimitedSharesSection30Exemption, - - [EnumMember(Value = "assurance-company")] - AssuranceCompany, - - [EnumMember(Value = "oversea-company")] - OverseaCompany, - - [EnumMember(Value = "eeig")] - Eeig, - - [EnumMember(Value = "icvc-securities")] - IcvcSecurities, - - [EnumMember(Value = "icvc-warrant")] - IcvcWarrant, - - [EnumMember(Value = "icvc-umbrella")] - IcvcUmbrella, - - [EnumMember(Value = "industrial-and-provident-society")] - IndustrialAndProvidentSociety, - - [EnumMember(Value = "northern-ireland")] - NorthernIreland, - - [EnumMember(Value = "northern-ireland-other")] - NorthernIrelandOther, - - [EnumMember(Value = "royal-charter")] - RoyalCharter, - - [EnumMember(Value = "investment-company-with-variable-capital")] - InvestmentCompanyWithVariableCapital, - - [EnumMember(Value = "unregistered-company")] - UnregisteredCompany, - - [EnumMember(Value = "llp")] - Llp, - - [EnumMember(Value = "other")] - Other, - - [EnumMember(Value = "european-public-limited-liability-company-se")] - EuropeanPublicLimitedLiabilityCompanySe, - - [EnumMember(Value = "registered-overseas-entity")] - RegisteredOverseasEntity, - - [EnumMember(Value = "uk-establishment")] - UkEstablishment, - - [EnumMember(Value = "registered-society-non-jurisdictional")] - RegisteredSociety, - - [EnumMember(Value = "protected-cell-company")] - ProtectedCellCompany, - - [EnumMember(Value = "scottish-partnership")] - ScottishPartnership, - - [EnumMember(Value = "charitable-incorporated-organisation")] - CharitableIncorporatedOrganisation, - - [EnumMember(Value = "scottish-charitable-incorporated-organisation")] - ScottishCharitableIncorporatedOrganisation, - - [EnumMember(Value = "further-education-or-sixth-form-college-corporation")] - FurtherEducationOrSixthFormCollegeCorporation, - - [EnumMember(Value = "ukeig")] - UnitedKingdomEconomicInterestGroupings, - - [EnumMember(Value = "united-kingdom-societas")] - UnitedKingdomSocietas, - } -} diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs new file mode 100644 index 0000000..b9a36d6 --- /dev/null +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Search.AdvancedCompanySearch +{ + public class AdvancedCompanySearch + { + [JsonPropertyName("etag")] + public string ETag { get; set; } + + [JsonPropertyName("hits")] + public int? Hits { get; set; } + + [JsonPropertyName("items")] + public Company[] Items { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } + + [JsonPropertyName("top_hit")] + public Company TopHit { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs new file mode 100644 index 0000000..9b5d0b1 --- /dev/null +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs @@ -0,0 +1,44 @@ +using System; +using System.Text.Json.Serialization; +using CompaniesHouse.JsonConverters; +using CompaniesHouse.Response; + +namespace CompaniesHouse.Response.Search.AdvancedCompanySearch +{ + public class Company + { + [JsonPropertyName("company_name")] + public string CompanyName { get; set; } + + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } + + [JsonPropertyName("company_status")] + public CompanyStatus CompanyStatus { get; set; } + + [JsonPropertyName("company_subtype")] + public CompanySubtype CompanySubtype { get; set; } + + [JsonPropertyName("company_type")] + public CompanyType CompanyType { get; set; } + + [JsonPropertyName("date_of_cessation")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? DateOfCessation { get; set; } + + [JsonPropertyName("date_of_creation")] + public DateTime? DateOfCreation { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } + + [JsonPropertyName("links")] + public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } + + [JsonPropertyName("registered_office_address")] + public Address RegisteredOfficeAddress { get; set; } + + [JsonPropertyName("sic_codes")] + public string[] SicCodes { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs new file mode 100644 index 0000000..c19a2ef --- /dev/null +++ b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch +{ + public class CompaniesAlphabeticallySearch + { + [JsonPropertyName("items")] + public Company[] Items { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } + + [JsonPropertyName("top_hit")] + public Company TopHit { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs new file mode 100644 index 0000000..7867600 --- /dev/null +++ b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; +using CompaniesHouse.Response; + +namespace CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch +{ + public class Company + { + [JsonPropertyName("company_name")] + public string CompanyName { get; set; } + + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } + + [JsonPropertyName("company_status")] + public CompanyStatus CompanyStatus { get; set; } + + [JsonPropertyName("company_type")] + public CompanyType CompanyType { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } + + [JsonPropertyName("links")] + public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } + + [JsonPropertyName("ordered_alpha_key_with_id")] + public string OrderedAlphaKeyWithId { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs b/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs new file mode 100644 index 0000000..4999911 --- /dev/null +++ b/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Search +{ + public class CompanyProfileLinks + { + [JsonPropertyName("company_profile")] + public string CompanyProfile { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs new file mode 100644 index 0000000..be53e7c --- /dev/null +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.Json.Serialization; +using CompaniesHouse.JsonConverters; +using CompaniesHouse.Response; + +namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch +{ + public class Company + { + [JsonPropertyName("company_name")] + public string CompanyName { get; set; } + + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } + + [JsonPropertyName("company_status")] + public CompanyStatus CompanyStatus { get; set; } + + [JsonPropertyName("date_of_cessation")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? DateOfCessation { get; set; } + + [JsonPropertyName("date_of_creation")] + public DateTime? DateOfCreation { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } + + [JsonPropertyName("matched_previous_company_name")] + public PreviousCompanyName MatchedPreviousCompanyName { get; set; } + + [JsonPropertyName("ordered_alpha_key_with_id")] + public string OrderedAlphaKeyWithId { get; set; } + + [JsonPropertyName("previous_company_names")] + public PreviousCompanyName[] PreviousCompanyNames { get; set; } + + [JsonPropertyName("registered_office_address")] + public Address RegisteredOfficeAddress { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs new file mode 100644 index 0000000..f9fa539 --- /dev/null +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch +{ + public class DissolvedCompaniesSearch + { + [JsonPropertyName("etag")] + public string ETag { get; set; } + + [JsonPropertyName("hits")] + public int? Hits { get; set; } + + [JsonPropertyName("items")] + public Company[] Items { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } + + [JsonPropertyName("top_hit")] + public Company TopHit { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs new file mode 100644 index 0000000..7c9a2bf --- /dev/null +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs @@ -0,0 +1,20 @@ +using System; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch +{ + public class PreviousCompanyName + { + [JsonPropertyName("ceased_on")] + public DateTime? CeasedOn { get; set; } + + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } + + [JsonPropertyName("effective_from")] + public DateTime? EffectiveFrom { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + } +} diff --git a/src/CompaniesHouse/SearchUriBuilderFactory.cs b/src/CompaniesHouse/SearchUriBuilderFactory.cs index 7a5a46c..3f57864 100644 --- a/src/CompaniesHouse/SearchUriBuilderFactory.cs +++ b/src/CompaniesHouse/SearchUriBuilderFactory.cs @@ -5,7 +5,7 @@ namespace CompaniesHouse { public class SearchUriBuilderFactory : ISearchUriBuilderFactory { - public ISearchUriBuilder Create() where TSearch : SearchRequest + public ISearchUriBuilder Create() { var type = typeof(TSearch); @@ -15,13 +15,27 @@ public ISearchUriBuilder Create() where TSearch : Sea } else if (type == typeof(SearchOfficerRequest)) { - return new SearchUriBuilder("search/officers"); - }else if (type == typeof(SearchDisqualifiedOfficerRequest)) + return (ISearchUriBuilder)new SearchUriBuilder("search/officers"); + } + else if (type == typeof(SearchDisqualifiedOfficerRequest)) + { + return (ISearchUriBuilder)new SearchUriBuilder("search/disqualified-officers"); + } + else if (type == typeof(SearchAllRequest)) + { + return (ISearchUriBuilder)new SearchUriBuilder("search"); + } + else if (type == typeof(SearchCompaniesAlphabeticallyRequest)) + { + return (ISearchUriBuilder)new SearchCompaniesAlphabeticallyUriBuilder("alphabetical-search/companies"); + } + else if (type == typeof(SearchDissolvedCompaniesRequest)) { - return new SearchUriBuilder("search/disqualified-officers"); - } else if (type == typeof(SearchAllRequest)) + return (ISearchUriBuilder)new SearchDissolvedCompaniesUriBuilder("dissolved-search/companies"); + } + else if (type == typeof(AdvancedCompanySearchRequest)) { - return new SearchUriBuilder("search"); + return (ISearchUriBuilder)new AdvancedCompanySearchUriBuilder("advanced-search/companies"); } throw new InvalidOperationException(); diff --git a/src/CompaniesHouse/UriBuilders/AdvancedCompanySearchUriBuilder.cs b/src/CompaniesHouse/UriBuilders/AdvancedCompanySearchUriBuilder.cs new file mode 100644 index 0000000..d4812ae --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/AdvancedCompanySearchUriBuilder.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using CompaniesHouse.Request; + +namespace CompaniesHouse.UriBuilders; + +public class AdvancedCompanySearchUriBuilder : ISearchUriBuilder +{ + private readonly string _path; + + public AdvancedCompanySearchUriBuilder(string path) + { + _path = path; + } + + public Uri Build(AdvancedCompanySearchRequest request) + { + var queryParts = new List(); + + AddString("company_name_includes", request.CompanyNameIncludes); + AddString("company_name_excludes", request.CompanyNameExcludes); + AddDelimited("company_status", request.CompanyStatuses?.Select(x => x.Value)); + AddDelimited("company_subtype", request.CompanySubtypes?.Select(x => x.Value)); + AddDelimited("company_type", request.CompanyTypes?.Select(x => x.Value)); + AddDate("dissolved_from", request.DissolvedFrom); + AddDate("dissolved_to", request.DissolvedTo); + AddDate("incorporated_from", request.IncorporatedFrom); + AddDate("incorporated_to", request.IncorporatedTo); + AddString("location", request.Location); + AddDelimited("sic_codes", request.SicCodes); + + if (request.Size.HasValue) + { + queryParts.Add("size=" + request.Size.Value); + } + + if (request.StartIndex.HasValue) + { + queryParts.Add("start_index=" + request.StartIndex.Value); + } + + var query = queryParts.Count == 0 ? string.Empty : "?" + string.Join("&", queryParts); + + return new Uri(_path + query, UriKind.Relative); + + void AddString(string key, string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return; + } + + queryParts.Add(key + "=" + Uri.EscapeDataString(value)); + } + + void AddDelimited(string key, IEnumerable? values) + { + if (values is null) + { + return; + } + + var nonEmptyValues = values.Where(x => !string.IsNullOrWhiteSpace(x)).ToArray(); + if (nonEmptyValues.Length == 0) + { + return; + } + + queryParts.Add(key + "=" + Uri.EscapeDataString(string.Join(",", nonEmptyValues))); + } + + void AddDate(string key, DateTime? value) + { + if (!value.HasValue) + { + return; + } + + queryParts.Add(key + "=" + value.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); + } + } +} diff --git a/src/CompaniesHouse/UriBuilders/ISearchUriBuilder.cs b/src/CompaniesHouse/UriBuilders/ISearchUriBuilder.cs index 3c0f98c..812dd41 100644 --- a/src/CompaniesHouse/UriBuilders/ISearchUriBuilder.cs +++ b/src/CompaniesHouse/UriBuilders/ISearchUriBuilder.cs @@ -3,7 +3,7 @@ namespace CompaniesHouse.UriBuilders { - public interface ISearchUriBuilder where TSearch : ISearchRequest + public interface ISearchUriBuilder { Uri Build(TSearch request); } diff --git a/src/CompaniesHouse/UriBuilders/SearchCompaniesAlphabeticallyUriBuilder.cs b/src/CompaniesHouse/UriBuilders/SearchCompaniesAlphabeticallyUriBuilder.cs new file mode 100644 index 0000000..f4a86a6 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/SearchCompaniesAlphabeticallyUriBuilder.cs @@ -0,0 +1,36 @@ +using System; +using CompaniesHouse.Request; + +namespace CompaniesHouse.UriBuilders; + +public class SearchCompaniesAlphabeticallyUriBuilder : ISearchUriBuilder +{ + private readonly string _path; + + public SearchCompaniesAlphabeticallyUriBuilder(string path) + { + _path = path; + } + + public Uri Build(SearchCompaniesAlphabeticallyRequest request) + { + var query = $"?q={Uri.EscapeDataString(request.Query)}"; + + if (!string.IsNullOrWhiteSpace(request.SearchAbove)) + { + query += "&search_above=" + Uri.EscapeDataString(request.SearchAbove); + } + + if (!string.IsNullOrWhiteSpace(request.SearchBelow)) + { + query += "&search_below=" + Uri.EscapeDataString(request.SearchBelow); + } + + if (request.Size.HasValue) + { + query += "&size=" + request.Size.Value; + } + + return new Uri(_path + query, UriKind.Relative); + } +} diff --git a/src/CompaniesHouse/UriBuilders/SearchCompanyUriBuilder.cs b/src/CompaniesHouse/UriBuilders/SearchCompanyUriBuilder.cs index 8ff5253..32dcaf5 100644 --- a/src/CompaniesHouse/UriBuilders/SearchCompanyUriBuilder.cs +++ b/src/CompaniesHouse/UriBuilders/SearchCompanyUriBuilder.cs @@ -12,9 +12,9 @@ protected override string BuildQuery(SearchCompanyRequest request) { var query = base.BuildQuery(request); - if (string.IsNullOrWhiteSpace(request.Restrictions)) + if (!string.IsNullOrWhiteSpace(request.Restrictions)) { - query += "&restrictions=" + request.Restrictions; + query += "&restrictions=" + Uri.EscapeDataString(request.Restrictions); } return query; diff --git a/src/CompaniesHouse/UriBuilders/SearchDissolvedCompaniesUriBuilder.cs b/src/CompaniesHouse/UriBuilders/SearchDissolvedCompaniesUriBuilder.cs new file mode 100644 index 0000000..3b868d5 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/SearchDissolvedCompaniesUriBuilder.cs @@ -0,0 +1,41 @@ +using System; +using CompaniesHouse.Request; + +namespace CompaniesHouse.UriBuilders; + +public class SearchDissolvedCompaniesUriBuilder : ISearchUriBuilder +{ + private readonly string _path; + + public SearchDissolvedCompaniesUriBuilder(string path) + { + _path = path; + } + + public Uri Build(SearchDissolvedCompaniesRequest request) + { + var query = $"?q={Uri.EscapeDataString(request.Query)}&search_type={Uri.EscapeDataString(request.SearchType)}"; + + if (!string.IsNullOrWhiteSpace(request.SearchAbove)) + { + query += "&search_above=" + Uri.EscapeDataString(request.SearchAbove); + } + + if (!string.IsNullOrWhiteSpace(request.SearchBelow)) + { + query += "&search_below=" + Uri.EscapeDataString(request.SearchBelow); + } + + if (request.Size.HasValue) + { + query += "&size=" + request.Size.Value; + } + + if (request.StartIndex.HasValue) + { + query += "&start_index=" + request.StartIndex.Value; + } + + return new Uri(_path + query, UriKind.Relative); + } +} diff --git a/src/CompaniesHouse/enum-map.txt b/src/CompaniesHouse/enum-map.txt index d10a10d..a963b10 100644 --- a/src/CompaniesHouse/enum-map.txt +++ b/src/CompaniesHouse/enum-map.txt @@ -8,3 +8,5 @@ # up a new static member - no hand-editing of generated types required. company_status|CompaniesHouse.Response|CompanyStatus|true +company_type|CompaniesHouse.Response|CompanyType|true +company_subtype|CompaniesHouse.Response|CompanySubtype|true diff --git a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs index d30396c..f900403 100644 --- a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs @@ -24,6 +24,9 @@ public void CanResolveCompaniesHouseClients() scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); @@ -86,6 +89,8 @@ public void AddCompaniesHouseClient_Named_ResolvesKeyedServices() first.ShouldNotBeSameAs(second); scope.ServiceProvider.GetRequiredKeyedService("first").ShouldNotBeNull(); + scope.ServiceProvider.GetRequiredKeyedService("first").ShouldNotBeNull(); + scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs new file mode 100644 index 0000000..ee86017 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests +{ + public class AdvancedCompanySearchTests + { + private readonly CompaniesHouseClient _client; + + public AdvancedCompanySearchTests() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + + [Fact] + public async Task ThenCompaniesAreReturned() + { + var result = await _client.AdvancedCompanySearchAsync(new AdvancedCompanySearchRequest + { + CompanyNameIncludes = "TESCO", + CompanyStatuses = new[] { CompanyStatus.Active }, + Size = 25, + }); + + result.Data.ShouldNotBeNull(); + result.Data.Items.ShouldNotBeEmpty(); + } + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs new file mode 100644 index 0000000..df96cd4 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using CompaniesHouse.Request; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests +{ + public class CompaniesAlphabeticalSearchTests + { + private readonly CompaniesHouseClient _client; + + public CompaniesAlphabeticalSearchTests() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + + [Theory] + [InlineData("TESCO")] + [InlineData("TESCO PERSONAL FINANCE")] + public async Task ThenCompaniesAreReturned(string query) + { + var result = await _client.SearchCompaniesAlphabeticallyAsync(new SearchCompaniesAlphabeticallyRequest + { + Query = query, + Size = 25, + }); + + result.Data.ShouldNotBeNull(); + result.Data.Items.ShouldNotBeEmpty(); + } + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs new file mode 100644 index 0000000..7e22046 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using CompaniesHouse.Request; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests +{ + public class DissolvedCompaniesSearchTests + { + private readonly CompaniesHouseClient _client; + + public DissolvedCompaniesSearchTests() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + + [Theory] + [InlineData("CARILLION")] + [InlineData("BLOCKBUSTER")] + public async Task ThenCompaniesAreReturned(string query) + { + var result = await _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest + { + Query = query, + SearchType = "best-match", + Size = 25, + }); + + result.Data.ShouldNotBeNull(); + result.Data.Items.ShouldNotBeEmpty(); + } + } +} diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs index 298e5d0..24a0471 100644 --- a/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs +++ b/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs @@ -101,6 +101,30 @@ public void ReportsADiagnosticWhenAConfiguredGroupIsNotFoundInAnyYaml() diagnostics.ShouldContain(d => d.Id == "CHENUM001"); } + [Fact] + public void GeneratesMultipleConfiguredGroupsFromASingleEnumMapFile() + { + var additionalFiles = new[] + { + new InMemoryAdditionalText(@"external\api-enumerations\constants.yml", """ + company_status: + 'active' : "Active" + company_type: + 'ltd' : "Private limited company" + """), + new InMemoryAdditionalText(@"enum-map.txt", """ + company_status|CompaniesHouse.Response|CompanyStatus|true + company_type|CompaniesHouse.Response|CompanyType|true + """), + }; + + var generated = RunGenerator(additionalFiles); + + generated.ShouldContainKey("CompanyStatus.g.cs"); + generated.ShouldContainKey("CompanyType.g.cs"); + generated["CompanyType.g.cs"].ShouldContain("public static CompanyType Ltd => new(\"ltd\");"); + } + private static Dictionary RunGenerator(IEnumerable additionalFiles) { var compilation = CreateCompilation(); diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs new file mode 100644 index 0000000..2c7ebae --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs @@ -0,0 +1,87 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Search.AdvancedCompanySearch; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests +{ + public class CompaniesHouseSearchClientTestsForAdvancedCompanySearch + { + [Fact] + public async Task GivenAResponse_WhenPerformingAnAdvancedCompanySearch_ThenTheTypedPayloadIsReturned() + { + const string resource = """ + { + "etag": "etag-advanced", + "hits": "1", + "items": [ + { + "company_name": "ABC CIC LIMITED", + "company_number": "01234567", + "company_status": "active", + "company_subtype": "community-interest-company", + "company_type": "ltd", + "date_of_creation": "2010-02-03", + "kind": "search-results#company", + "links": { "company_profile": "/company/01234567" }, + "registered_office_address": { + "address_line_1": "1 Example Street", + "address_line_2": "Suite 2", + "country": "England", + "locality": "London", + "postal_code": "SW1A 1AA", + "region": "Greater London" + }, + "sic_codes": [ "62012", "62020" ] + } + ], + "kind": "search#advanced-search", + "top_hit": { + "company_name": "ABC CIC LIMITED", + "company_number": "01234567", + "company_status": "active", + "company_subtype": "community-interest-company", + "company_type": "ltd", + "date_of_creation": "2010-02-03", + "kind": "search-results#company", + "links": { "company_profile": "/company/01234567" }, + "registered_office_address": { + "address_line_1": "1 Example Street", + "address_line_2": "Suite 2", + "country": "England", + "locality": "London", + "postal_code": "SW1A 1AA", + "region": "Greater London" + }, + "sic_codes": [ "62012", "62020" ] + } + } + """; + + var uri = new Uri("https://wibble.com/advanced-search/companies"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, resource); + var client = new CompaniesHouseSearchClient( + new HttpClient(handler) { BaseAddress = new Uri("https://wibble.com/") }, + new SearchUriBuilderFactory()); + + var result = await client.SearchAsync( + new AdvancedCompanySearchRequest { CompanyNameIncludes = "abc" }); + + result.Data.ETag.ShouldBe("etag-advanced"); + result.Data.Hits.ShouldBe(1); + result.Data.Kind.ShouldBe("search#advanced-search"); + var company = result.Data.Items[0]; + company.CompanyStatus.ShouldBe(CompanyStatus.Active); + company.CompanySubtype.ShouldBe(CompanySubtype.CommunityInterestCompany); + company.CompanyType.ShouldBe(CompanyType.Ltd); + company.Links.CompanyProfile.ShouldBe("/company/01234567"); + company.RegisteredOfficeAddress.Country.ShouldBe("England"); + company.SicCodes.ShouldBe(new[] { "62012", "62020" }); + result.Data.TopHit.CompanyNumber.ShouldBe("01234567"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs new file mode 100644 index 0000000..227eb11 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs @@ -0,0 +1,63 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests +{ + public class CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch + { + [Fact] + public async Task GivenAResponse_WhenSearchingCompaniesAlphabetically_ThenTheTypedPayloadIsReturned() + { + const string resource = """ + { + "items": [ + { + "company_name": "ABC LIMITED", + "company_number": "01234567", + "company_status": "active", + "company_type": "ltd", + "kind": "search-results#alphabetical-search", + "links": { "company_profile": "/company/01234567" }, + "ordered_alpha_key_with_id": "ABC LIMITED:01234567" + } + ], + "kind": "search#alphabetical-search", + "top_hit": { + "company_name": "ABC LIMITED", + "company_number": "01234567", + "company_status": "active", + "company_type": "ltd", + "kind": "search-results#alphabetical-search", + "links": { "company_profile": "/company/01234567" }, + "ordered_alpha_key_with_id": "ABC LIMITED:01234567" + } + } + """; + + var uri = new Uri("https://wibble.com/alphabetical-search/companies"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, resource); + var client = new CompaniesHouseSearchClient( + new HttpClient(handler) { BaseAddress = new Uri("https://wibble.com/") }, + new SearchUriBuilderFactory()); + + var result = await client.SearchAsync( + new SearchCompaniesAlphabeticallyRequest { Query = "abc" }); + + result.Data.Kind.ShouldBe("search#alphabetical-search"); + result.Data.Items.Length.ShouldBe(1); + result.Data.TopHit.CompanyNumber.ShouldBe("01234567"); + var company = result.Data.Items[0]; + company.CompanyName.ShouldBe("ABC LIMITED"); + company.CompanyStatus.ShouldBe(CompanyStatus.Active); + company.CompanyType.ShouldBe(CompanyType.Ltd); + company.Links.CompanyProfile.ShouldBe("/company/01234567"); + company.OrderedAlphaKeyWithId.ShouldBe("ABC LIMITED:01234567"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs new file mode 100644 index 0000000..3f2e291 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs @@ -0,0 +1,106 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Search.DissolvedCompaniesSearch; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests +{ + public class CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch + { + [Fact] + public async Task GivenAResponse_WhenSearchingDissolvedCompanies_ThenTheTypedPayloadIsReturned() + { + const string resource = """ + { + "etag": "etag-1", + "hits": "2", + "items": [ + { + "company_name": "ABC DISSOLVED LIMITED", + "company_number": "01234567", + "company_status": "dissolved", + "date_of_cessation": "2023-01-20", + "date_of_creation": "2001-02-03", + "kind": "search-results#dissolved-company", + "matched_previous_company_name": { + "ceased_on": "2010-01-01", + "company_number": "01234567", + "effective_from": "2009-01-01", + "name": "OLD ABC LIMITED" + }, + "ordered_alpha_key_with_id": "ABC DISSOLVED LIMITED:01234567", + "previous_company_names": [ + { + "ceased_on": "2010-01-01", + "company_number": "01234567", + "effective_from": "2009-01-01", + "name": "OLD ABC LIMITED" + } + ], + "registered_office_address": { + "address_line_1": "1 Example Street", + "address_line_2": "Example House", + "locality": "Cardiff", + "postal_code": "CF14 3UZ" + } + } + ], + "kind": "search#dissolved", + "top_hit": { + "company_name": "ABC DISSOLVED LIMITED", + "company_number": "01234567", + "company_status": "dissolved", + "date_of_cessation": "2023-01-20", + "date_of_creation": "2001-02-03", + "kind": "search-results#dissolved-company", + "matched_previous_company_name": { + "ceased_on": "2010-01-01", + "company_number": "01234567", + "effective_from": "2009-01-01", + "name": "OLD ABC LIMITED" + }, + "ordered_alpha_key_with_id": "ABC DISSOLVED LIMITED:01234567", + "previous_company_names": [ + { + "ceased_on": "2010-01-01", + "company_number": "01234567", + "effective_from": "2009-01-01", + "name": "OLD ABC LIMITED" + } + ], + "registered_office_address": { + "address_line_1": "1 Example Street", + "address_line_2": "Example House", + "locality": "Cardiff", + "postal_code": "CF14 3UZ" + } + } + } + """; + + var uri = new Uri("https://wibble.com/dissolved-search/companies"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, resource); + var client = new CompaniesHouseSearchClient( + new HttpClient(handler) { BaseAddress = new Uri("https://wibble.com/") }, + new SearchUriBuilderFactory()); + + var result = await client.SearchAsync( + new SearchDissolvedCompaniesRequest { Query = "abc", SearchType = "best-match" }); + + result.Data.ETag.ShouldBe("etag-1"); + result.Data.Hits.ShouldBe(2); + result.Data.Kind.ShouldBe("search#dissolved"); + var company = result.Data.Items[0]; + company.CompanyStatus.ShouldBe(CompanyStatus.Dissolved); + company.DateOfCessation.ShouldBe(new DateTime(2023, 01, 20)); + company.MatchedPreviousCompanyName.Name.ShouldBe("OLD ABC LIMITED"); + company.PreviousCompanyNames[0].CompanyNumber.ShouldBe("01234567"); + company.RegisteredOfficeAddress.Locality.ShouldBe("Cardiff"); + result.Data.TopHit.OrderedAlphaKeyWithId.ShouldBe("ABC DISSOLVED LIMITED:01234567"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/EnumerationMappings.cs b/tests/CompaniesHouse.Tests/EnumerationMappings.cs index f24d728..e870472 100644 --- a/tests/CompaniesHouse.Tests/EnumerationMappings.cs +++ b/tests/CompaniesHouse.Tests/EnumerationMappings.cs @@ -117,8 +117,10 @@ public static class EnumerationMappings {"converted-or-closed", CompanyType.ConvertedOrClosed}, {"private-unlimited-nsc", CompanyType.PrivateUnlimitedNsc}, {"private-limited-shares-section-30-exemption", CompanyType.PrivateLimitedSharesSection30Exemption}, + {"protected-cell-company", CompanyType.ProtectedCellCompany}, {"assurance-company", CompanyType.AssuranceCompany}, {"oversea-company", CompanyType.OverseaCompany}, + {"eeig-establishment", CompanyType.EeigEstablishment}, {"eeig", CompanyType.Eeig}, {"icvc-securities", CompanyType.IcvcSecurities}, {"icvc-warrant", CompanyType.IcvcWarrant}, @@ -132,9 +134,14 @@ public static class EnumerationMappings {"unregistered-company", CompanyType.UnregisteredCompany}, {"other", CompanyType.Other}, {"european-public-limited-liability-company-se", CompanyType.EuropeanPublicLimitedLiabilityCompanySe}, - {"registered-society-non-jurisdictional", CompanyType.RegisteredSociety}, - {"ukeig", CompanyType.UnitedKingdomEconomicInterestGroupings}, + {"registered-society-non-jurisdictional", CompanyType.RegisteredSocietyNonJurisdictional}, + {"ukeig", CompanyType.Ukeig}, {"united-kingdom-societas", CompanyType.UnitedKingdomSocietas}, + {"uk-establishment", CompanyType.UkEstablishment}, + {"scottish-partnership", CompanyType.ScottishPartnership}, + {"charitable-incorporated-organisation", CompanyType.CharitableIncorporatedOrganisation}, + {"scottish-charitable-incorporated-organisation", CompanyType.ScottishCharitableIncorporatedOrganisation}, + {"further-education-or-sixth-form-college-corporation", CompanyType.FurtherEducationOrSixthFormCollegeCorporation}, {"registered-overseas-entity", CompanyType.RegisteredOverseasEntity}, }; diff --git a/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs index 653be8e..1750362 100644 --- a/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs +++ b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs @@ -11,7 +11,7 @@ namespace CompaniesHouse.Tests /// /// A small, dependency-free replacement for FluentAssertions' BeEquivalentTo. /// Recursively compares public properties of two object graphs, with built-in bridging - /// between production enum values and the raw wire strings used by the test + /// between production enum/value-type values and the raw wire strings used by the test /// ResourceBuilders fixtures (matched via each enum member's ). /// This removes the need for the old FluentAssertions IEquivalencyStep/MapProviders machinery. /// @@ -42,7 +42,7 @@ private static void Compare(object actual, object expected, string path, string[ return; } - // Enum <-> raw wire string bridging (either direction). + // Enum/string-backed-value-type <-> raw wire string bridging (either direction). if (actual is Enum actualEnum && expected is string expectedString) { var actualWireValue = GetEnumMemberValue(actualEnum); @@ -65,6 +65,26 @@ private static void Compare(object actual, object expected, string path, string[ return; } + if (TryGetStringBackedValue(actual, out var actualRawValueFromValueType) && expected is string expectedRawValue) + { + if (actualRawValueFromValueType != expectedRawValue) + { + differences.Add($"{path}: expected <{expectedRawValue}> but was <{actualRawValueFromValueType}>"); + } + + return; + } + + if (TryGetStringBackedValue(expected, out var expectedRawValueFromValueType) && actual is string actualRawValue) + { + if (expectedRawValueFromValueType != actualRawValue) + { + differences.Add($"{path}: expected <{expectedRawValueFromValueType}> but was <{actualRawValue}>"); + } + + return; + } + // Array/collection of enums compared against a single raw wire string: // replicates the old "does this collection contain the mapped value" behaviour. if (actual is IEnumerable actualContainer && actual is not string && expected is string containsExpected) @@ -138,6 +158,11 @@ private static bool ValuesEqual(object actual, object expected) return GetEnumMemberValue(actualEnum) == expectedString; } + if (TryGetStringBackedValue(actual, out var actualRawValueFromValueType) && expected is string expectedRawValue) + { + return actualRawValueFromValueType == expectedRawValue; + } + return Equals(actual, expected); } @@ -151,5 +176,24 @@ private static string GetEnumMemberValue(Enum enumValue) return enumMember is { Length: > 0 } ? enumMember[0].Value : enumValue.ToString(); } + + private static bool TryGetStringBackedValue(object value, out string rawValue) + { + rawValue = string.Empty; + + if (value is null || value is string || value is Enum) + { + return false; + } + + var valueProperty = value.GetType().GetProperty("Value"); + if (valueProperty?.PropertyType != typeof(string)) + { + return false; + } + + rawValue = (string?)valueProperty.GetValue(value) ?? string.Empty; + return true; + } } } diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanySubtypeTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanySubtypeTests.cs new file mode 100644 index 0000000..121f23e --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanySubtypeTests.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class CompanySubtypeTests + { + [Theory] + [InlineData("community-interest-company")] + [InlineData("private-fund-limited-partnership")] + [InlineData("slp")] + public void Deserializing_KnownValue_RoundTripsAndIsKnown(string value) + { + var json = $"\"{value}\""; + + var companySubtype = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + companySubtype.Value.ShouldBe(value); + companySubtype.IsKnown.ShouldBeTrue(); + companySubtype.HasValue.ShouldBeTrue(); + JsonSerializer.Serialize(companySubtype, CompaniesHouseJsonSerializerOptions.Default).ShouldBe(json); + } + + [Fact] + public void Deserializing_UnknownValue_DoesNotThrowAndPreservesRawValue() + { + const string json = "\"future-company-subtype\""; + + var companySubtype = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + companySubtype.Value.ShouldBe("future-company-subtype"); + companySubtype.IsKnown.ShouldBeFalse(); + companySubtype.Description.ShouldBeNull(); + } + + [Fact] + public void Description_ReturnsFriendlyTextForKnownValues() + { + CompanySubtype.CommunityInterestCompany.Description.ShouldBe("Community Interest Company (CIC)"); + CompanySubtype.PrivateFundLimitedPartnership.Description.ShouldBe("Private Fund Limited Partnership (PFLP)"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyTypeTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyTypeTests.cs new file mode 100644 index 0000000..9b9f0b3 --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyTypeTests.cs @@ -0,0 +1,56 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class CompanyTypeTests + { + [Theory] + [InlineData("private-unlimited")] + [InlineData("protected-cell-company")] + [InlineData("eeig-establishment")] + [InlineData("registered-overseas-entity")] + public void Deserializing_KnownValue_RoundTripsAndIsKnown(string value) + { + var json = $"\"{value}\""; + + var companyType = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + companyType.Value.ShouldBe(value); + companyType.IsKnown.ShouldBeTrue(); + companyType.HasValue.ShouldBeTrue(); + JsonSerializer.Serialize(companyType, CompaniesHouseJsonSerializerOptions.Default).ShouldBe(json); + } + + [Fact] + public void Deserializing_UnknownValue_DoesNotThrowAndPreservesRawValue() + { + const string json = "\"future-company-type\""; + + var companyType = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + companyType.Value.ShouldBe("future-company-type"); + companyType.IsKnown.ShouldBeFalse(); + companyType.Description.ShouldBeNull(); + } + + [Fact] + public void Deserializing_Null_ReturnsDefaultWithNoValue() + { + var companyType = JsonSerializer.Deserialize("null", CompaniesHouseJsonSerializerOptions.Default); + + companyType.ShouldBe(default); + companyType.HasValue.ShouldBeFalse(); + companyType.Value.ShouldBe(string.Empty); + } + + [Fact] + public void Description_ReturnsFriendlyTextForKnownValues() + { + CompanyType.ProtectedCellCompany.Description.ShouldBe("Protected cell company"); + CompanyType.EeigEstablishment.Description.ShouldBe("European Economic Interest Grouping Establishment (EEIG)"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/AdvancedCompanySearchUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/AdvancedCompanySearchUriBuilderTests.cs new file mode 100644 index 0000000..0f5607d --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/AdvancedCompanySearchUriBuilderTests.cs @@ -0,0 +1,47 @@ +using System; +using CompaniesHouse.Request; +using CompaniesHouse.Response; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public class AdvancedCompanySearchUriBuilderTests + { + [Fact] + public void Build_IncludesConfiguredParameters() + { + var sut = new AdvancedCompanySearchUriBuilder("advanced-search/companies"); + + var uri = sut.Build(new AdvancedCompanySearchRequest + { + CompanyNameIncludes = "abc & co", + CompanyNameExcludes = "beta ltd", + CompanyStatuses = new[] { CompanyStatus.Active, CompanyStatus.Dissolved }, + CompanySubtypes = new[] { CompanySubtype.CommunityInterestCompany, CompanySubtype.PrivateFundLimitedPartnership }, + CompanyTypes = new[] { CompanyType.Ltd, CompanyType.PrivateLimitedGuarantNsc }, + DissolvedFrom = new DateTime(2020, 01, 02), + DissolvedTo = new DateTime(2020, 03, 04), + IncorporatedFrom = new DateTime(2010, 05, 06), + IncorporatedTo = new DateTime(2011, 07, 08), + Location = "London & Surrey", + SicCodes = new[] { "62012", "62020" }, + Size = 100, + StartIndex = 30, + }); + + uri.ToString().ShouldBe("advanced-search/companies?company_name_includes=abc%20%26%20co&company_name_excludes=beta%20ltd&company_status=active%2Cdissolved&company_subtype=community-interest-company%2Cprivate-fund-limited-partnership&company_type=ltd%2Cprivate-limited-guarant-nsc&dissolved_from=2020-01-02&dissolved_to=2020-03-04&incorporated_from=2010-05-06&incorporated_to=2011-07-08&location=London%20%26%20Surrey&sic_codes=62012%2C62020&size=100&start_index=30"); + } + + [Fact] + public void Build_OmitsParametersThatAreNotSupplied() + { + var sut = new AdvancedCompanySearchUriBuilder("advanced-search/companies"); + + var uri = sut.Build(new AdvancedCompanySearchRequest()); + + uri.ShouldBe(new Uri("advanced-search/companies", UriKind.Relative)); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompaniesAlphabeticallyUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompaniesAlphabeticallyUriBuilderTests.cs new file mode 100644 index 0000000..c9b353f --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompaniesAlphabeticallyUriBuilderTests.cs @@ -0,0 +1,40 @@ +using System; +using CompaniesHouse.Request; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public class SearchCompaniesAlphabeticallyUriBuilderTests + { + [Fact] + public void Build_IncludesConfiguredParameters() + { + var sut = new SearchCompaniesAlphabeticallyUriBuilder("alphabetical-search/companies"); + + var uri = sut.Build(new SearchCompaniesAlphabeticallyRequest + { + Query = "abc & co", + SearchAbove = "A/1", + SearchBelow = "B/2", + Size = 25, + }); + + uri.ToString().ShouldBe("alphabetical-search/companies?q=abc%20%26%20co&search_above=A%2F1&search_below=B%2F2&size=25"); + } + + [Fact] + public void Build_OmitsOptionalParametersWhenTheyAreNotSupplied() + { + var sut = new SearchCompaniesAlphabeticallyUriBuilder("alphabetical-search/companies"); + + var uri = sut.Build(new SearchCompaniesAlphabeticallyRequest + { + Query = "abc", + }); + + uri.ShouldBe(new Uri("alphabetical-search/companies?q=abc", UriKind.Relative)); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase+Thens.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase+Thens.cs new file mode 100644 index 0000000..9c317dd --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase+Thens.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Shouldly; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public abstract partial class SearchCompanyUriBuilderTestsBase + { + public class Thens + { + private readonly SearchCompanyUriBuilderTestsBase _tests; + + public Thens(SearchCompanyUriBuilderTestsBase tests) + { + _tests = tests; + } + + public void TheUriQueryStringDoesNotContainRestrictions() + { + GetQuery().ContainsKey("restrictions").ShouldBeFalse(); + } + + public void TheUriQueryStringContainsRestrictions() + { + GetQuery()["restrictions"].ShouldBe(_tests.Restrictions); + } + + private Dictionary GetQuery() + { + return new Uri(_tests._baseUri, _tests.ActualUri) + .ToString() + .Split('?') + .Last() + .Split('&') + .ToDictionary(GetKey, GetValue); + + string GetKey(string keyValue) => keyValue.Split('=')[0]; + string GetValue(string keyValue) => keyValue.Contains('=') ? Uri.UnescapeDataString(keyValue.Split('=')[1]) : string.Empty; + } + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase.cs new file mode 100644 index 0000000..4a02ba7 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsBase.cs @@ -0,0 +1,28 @@ +using System; +using CompaniesHouse.Request; +using CompaniesHouse.UriBuilders; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public abstract partial class SearchCompanyUriBuilderTestsBase + { + private readonly SearchCompanyUriBuilder _uriBuilder; + private readonly Uri _baseUri = new Uri("https://example.test/"); + private readonly string _path = "search/companies"; + + protected SearchCompanyUriBuilderTestsBase() + { + _uriBuilder = new SearchCompanyUriBuilder(_path); + } + + protected virtual string? Restrictions => null; + + private Uri ActualUri => _uriBuilder.Build(new SearchCompanyRequest + { + Query = "company name", + Restrictions = Restrictions, + }); + + public Thens Then => new Thens(this); + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenEmpty.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenEmpty.cs new file mode 100644 index 0000000..bb9f01f --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenEmpty.cs @@ -0,0 +1,12 @@ +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public class SearchCompanyUriBuilderTestsForRestrictionsWhenEmpty : SearchCompanyUriBuilderTestsBase + { + protected override string? Restrictions => string.Empty; + + [Fact] + public void ThenTheUriQueryStringDoesNotContainRestrictions() => Then.TheUriQueryStringDoesNotContainRestrictions(); + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenNull.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenNull.cs new file mode 100644 index 0000000..b9cd44c --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenNull.cs @@ -0,0 +1,10 @@ +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public class SearchCompanyUriBuilderTestsForRestrictionsWhenNull : SearchCompanyUriBuilderTestsBase + { + [Fact] + public void ThenTheUriQueryStringDoesNotContainRestrictions() => Then.TheUriQueryStringDoesNotContainRestrictions(); + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenProvided.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenProvided.cs new file mode 100644 index 0000000..07de788 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenProvided.cs @@ -0,0 +1,12 @@ +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public class SearchCompanyUriBuilderTestsForRestrictionsWhenProvided : SearchCompanyUriBuilderTestsBase + { + protected override string? Restrictions => "active companies & subsidiaries"; + + [Fact] + public void ThenTheUriQueryStringContainsRestrictions() => Then.TheUriQueryStringContainsRestrictions(); + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenWhitespace.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenWhitespace.cs new file mode 100644 index 0000000..5ade2ea --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchCompanyUriBuilderTestsForRestrictionsWhenWhitespace.cs @@ -0,0 +1,12 @@ +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public class SearchCompanyUriBuilderTestsForRestrictionsWhenWhitespace : SearchCompanyUriBuilderTestsBase + { + protected override string? Restrictions => " "; + + [Fact] + public void ThenTheUriQueryStringDoesNotContainRestrictions() => Then.TheUriQueryStringDoesNotContainRestrictions(); + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchDissolvedCompaniesUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchDissolvedCompaniesUriBuilderTests.cs new file mode 100644 index 0000000..fc8f60a --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchDissolvedCompaniesUriBuilderTests.cs @@ -0,0 +1,43 @@ +using System; +using CompaniesHouse.Request; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.SearchUriBuilderTests +{ + public class SearchDissolvedCompaniesUriBuilderTests + { + [Fact] + public void Build_IncludesConfiguredParameters() + { + var sut = new SearchDissolvedCompaniesUriBuilder("dissolved-search/companies"); + + var uri = sut.Build(new SearchDissolvedCompaniesRequest + { + Query = "abc & co", + SearchType = "previous-name-dissolved", + SearchAbove = "A/1", + SearchBelow = "B/2", + Size = 25, + StartIndex = 30, + }); + + uri.ToString().ShouldBe("dissolved-search/companies?q=abc%20%26%20co&search_type=previous-name-dissolved&search_above=A%2F1&search_below=B%2F2&size=25&start_index=30"); + } + + [Fact] + public void Build_OmitsOptionalParametersWhenTheyAreNotSupplied() + { + var sut = new SearchDissolvedCompaniesUriBuilder("dissolved-search/companies"); + + var uri = sut.Build(new SearchDissolvedCompaniesRequest + { + Query = "abc", + SearchType = "best-match", + }); + + uri.ShouldBe(new Uri("dissolved-search/companies?q=abc&search_type=best-match", UriKind.Relative)); + } + } +} From f75f7ab620ecfb283a11d82cbad5b04793054181 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 23:20:11 +0100 Subject: [PATCH 09/38] Complete company profile schema coverage 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. --- .../Response/CompanyProfile/CompanyProfile.cs | 34 +++-- .../CompanyProfile/CompanyProfileLinks.cs | 34 ++--- .../ForeignCompanyAccountingRequirement.cs | 13 ++ .../CompanyProfile/ForeignCompanyAccounts.cs | 16 +++ .../CompanyProfile/ForeignCompanyDetails.cs | 31 +++++ .../ForeignCompanyOriginatingRegistry.cs | 13 ++ .../Response/CompanyProfile/Jurisdiction.cs | 33 ----- .../Response/CompanyProfile/MustFileWithin.cs | 10 ++ .../Response/CompanyStatusDetail.cs | 30 ----- src/CompaniesHouse/enum-map.txt | 4 + .../CompanyProfileTestsInvalid.cs | 5 +- .../CompanyProfileTestsValid.cs | 47 ++++++- ...panyProfileDeserializationScenarioTests.cs | 119 ++++++++++++++++++ ...CompaniesHouseCompanyProfileClientTests.cs | 78 +++++++++++- .../EnumerationMappings.cs | 10 +- .../CompanyStatusDetailTests.cs | 59 +++++++++ .../ResponseValueTypes/JurisdictionTests.cs | 60 +++++++++ 17 files changed, 496 insertions(+), 100 deletions(-) create mode 100644 src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccountingRequirement.cs create mode 100644 src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccounts.cs create mode 100644 src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyDetails.cs create mode 100644 src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyOriginatingRegistry.cs delete mode 100644 src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs create mode 100644 src/CompaniesHouse/Response/CompanyProfile/MustFileWithin.cs delete mode 100644 src/CompaniesHouse/Response/CompanyStatusDetail.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusDetailTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/JurisdictionTests.cs diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs index f52213c..7139a67 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs @@ -10,25 +10,25 @@ public class CompanyProfile public CompanyType Type { get; set; } [JsonPropertyName("etag")] - public string ETag { get; set; } + public required string ETag { get; set; } [JsonPropertyName("accounts")] - public Accounts Accounts { get; set; } + public required Accounts Accounts { get; set; } [JsonPropertyName("annual_return")] - public AnnualReturn AnnualReturn { get; set; } + public required AnnualReturn AnnualReturn { get; set; } [JsonPropertyName("confirmation_statement")] - public ConfirmationStatement ConfirmationStatement { get; set; } + public required ConfirmationStatement ConfirmationStatement { get; set; } [JsonPropertyName("can_file")] public bool? CanFile { get; set; } [JsonPropertyName("company_name")] - public string CompanyName { get; set; } + public required string CompanyName { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public required string CompanyNumber { get; set; } [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -36,6 +36,9 @@ public class CompanyProfile [JsonPropertyName("company_status_detail")] public CompanyStatusDetail CompanyStatusDetail { get; set; } + [JsonPropertyName("subtype")] + public CompanySubtype Subtype { get; set; } + [JsonPropertyName("date_of_creation")] public DateTime? DateOfCreation { get; set; } @@ -52,9 +55,18 @@ public class CompanyProfile [JsonPropertyName("has_insolvency_history")] public bool? HasInsolvencyHistory { get; set; } + [JsonPropertyName("has_super_secure_pscs")] + public bool? HasSuperSecurePscs { get; set; } + [JsonPropertyName("is_community_interest_company")] public bool? IsCommunityInterestCompany { get; set; } + [JsonPropertyName("external_registration_number")] + public string? ExternalRegistrationNumber { get; set; } + + [JsonPropertyName("foreign_company_details")] + public ForeignCompanyDetails? ForeignCompanyDetails { get; set; } + [JsonPropertyName("jurisdiction")] public Jurisdiction Jurisdiction { get; set; } @@ -63,24 +75,24 @@ public class CompanyProfile public DateTime? LastFullMembersListDate { get; set; } [JsonPropertyName("links")] - public CompanyProfileLinks Links { get; set; } + public required CompanyProfileLinks Links { get; set; } [JsonPropertyName("previous_company_names")] - public PreviousCompanyName[] PreviousCompanyNames { get; set; } + public required PreviousCompanyName[] PreviousCompanyNames { get; set; } [JsonPropertyName("registered_office_address")] - public Address RegisteredOfficeAddress { get; set; } + public required Address RegisteredOfficeAddress { get; set; } [JsonPropertyName("registered_office_is_in_dispute")] public bool? RegisteredOfficeIsInDispute { get; set; } [JsonPropertyName("sic_codes")] - public string[] SicCodes { get; set; } + public required string[] SicCodes { get; set; } [JsonPropertyName("undeliverable_registered_office_address")] public bool? UndeliverableRegisteredOfficeAddress { get; set; } [JsonPropertyName("branch_company_details")] - public BranchCompanyDetails BranchCompanyDetails { get; set; } + public required BranchCompanyDetails BranchCompanyDetails { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs index a031cc7..b94820f 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs @@ -5,27 +5,33 @@ namespace CompaniesHouse.Response.CompanyProfile public class CompanyProfileLinks { [JsonPropertyName("charges")] - public string Charges { get; set; } + public required string Charges { get; set; } + + [JsonPropertyName("exemptions")] + public string? Exemptions { get; set; } [JsonPropertyName("filing_history")] - public string FilingHistory { get; set; } - + public required string FilingHistory { get; set; } + [JsonPropertyName("insolvency")] - public string Insolvency { get; set; } - + public required string Insolvency { get; set; } + [JsonPropertyName("officers")] - public string Officers { get; set; } - + public required string Officers { get; set; } + [JsonPropertyName("persons_with_significant_control")] - public string PersonsWithSignificantControl { get; set; } - + public required string PersonsWithSignificantControl { get; set; } + [JsonPropertyName("persons_with_significant_control_statements")] - public string PersonsWithSignificantControlStatements { get; set; } - + public required string PersonsWithSignificantControlStatements { get; set; } + [JsonPropertyName("registers")] - public string Registers { get; set; } - + public required string Registers { get; set; } + [JsonPropertyName("self")] - public string Self { get; set; } + public required string Self { get; set; } + + [JsonPropertyName("uk_establishments")] + public string? UkEstablishments { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccountingRequirement.cs b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccountingRequirement.cs new file mode 100644 index 0000000..2458576 --- /dev/null +++ b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccountingRequirement.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.CompanyProfile +{ + public class ForeignCompanyAccountingRequirement + { + [JsonPropertyName("foreign_account_type")] + public ForeignAccountType ForeignAccountType { get; set; } + + [JsonPropertyName("terms_of_account_publication")] + public TermsOfAccountPublication TermsOfAccountPublication { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccounts.cs b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccounts.cs new file mode 100644 index 0000000..e8d7fe9 --- /dev/null +++ b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccounts.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.CompanyProfile +{ + public class ForeignCompanyAccounts + { + [JsonPropertyName("account_period_from")] + public AccountingReferenceDate? AccountPeriodFrom { get; set; } + + [JsonPropertyName("account_period_to")] + public AccountingReferenceDate? AccountPeriodTo { get; set; } + + [JsonPropertyName("must_file_within")] + public MustFileWithin? MustFileWithin { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyDetails.cs b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyDetails.cs new file mode 100644 index 0000000..e211215 --- /dev/null +++ b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyDetails.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.CompanyProfile +{ + public class ForeignCompanyDetails + { + [JsonPropertyName("accounting_requirement")] + public ForeignCompanyAccountingRequirement? AccountingRequirement { get; set; } + + [JsonPropertyName("accounts")] + public ForeignCompanyAccounts? Accounts { get; set; } + + [JsonPropertyName("business_activity")] + public string? BusinessActivity { get; set; } + + [JsonPropertyName("governed_by")] + public string? GovernedBy { get; set; } + + [JsonPropertyName("is_a_credit_financial_institution")] + public bool? IsACreditFinancialInstitution { get; set; } + + [JsonPropertyName("originating_registry")] + public ForeignCompanyOriginatingRegistry? OriginatingRegistry { get; set; } + + [JsonPropertyName("registration_number")] + public string? RegistrationNumber { get; set; } + + [JsonPropertyName("legal_form")] + public string? LegalForm { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyOriginatingRegistry.cs b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyOriginatingRegistry.cs new file mode 100644 index 0000000..5d9de54 --- /dev/null +++ b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyOriginatingRegistry.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.CompanyProfile +{ + public class ForeignCompanyOriginatingRegistry + { + [JsonPropertyName("country")] + public string? Country { get; set; } + + [JsonPropertyName("name")] + public string? Name { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs b/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs deleted file mode 100644 index ca623a0..0000000 --- a/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.CompanyProfile -{ - public enum Jurisdiction - { - None = 0, - - [EnumMember(Value = "england-wales")] - EnglandAndWales, - - [EnumMember(Value = "wales")] - Wales, - - [EnumMember(Value = "scotland")] - Scotland, - - [EnumMember(Value = "northern-ireland")] - NorthernIreland, - - [EnumMember(Value = "european-union")] - EuropeanUnion, - - [EnumMember(Value = "united-kingdom")] - UnitedKingdom, - - [EnumMember(Value = "england")] - England, - - [EnumMember(Value = "noneu")] - NonEu - } -} diff --git a/src/CompaniesHouse/Response/CompanyProfile/MustFileWithin.cs b/src/CompaniesHouse/Response/CompanyProfile/MustFileWithin.cs new file mode 100644 index 0000000..c8b28c6 --- /dev/null +++ b/src/CompaniesHouse/Response/CompanyProfile/MustFileWithin.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.CompanyProfile +{ + public class MustFileWithin + { + [JsonPropertyName("months")] + public string? Months { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/CompanyStatusDetail.cs b/src/CompaniesHouse/Response/CompanyStatusDetail.cs deleted file mode 100644 index 8a67f2c..0000000 --- a/src/CompaniesHouse/Response/CompanyStatusDetail.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum CompanyStatusDetail - { - None = 0, - - [EnumMember(Value = "transferred-from-uk")] - TransferredFromUk, - - [EnumMember(Value = "active-proposal-to-strike-off")] - ActiveProposalToStrikeOff, - - [EnumMember(Value = "petition-to-restore-dissolved")] - PetitionToRestoreDissolved, - - [EnumMember(Value = "transformed-to-se")] - TransformedToSe, - - [EnumMember(Value = "converted-to-plc")] - ConvertedToPlc, - - [EnumMember(Value = "converted-to-ukeig")] - ConvertedToUnitedKingdomEconomicInterestGroupings, - - [EnumMember(Value = "converted-to-uk-societas")] - ConvertedToUnitedKingdomSocietas - } -} diff --git a/src/CompaniesHouse/enum-map.txt b/src/CompaniesHouse/enum-map.txt index a963b10..53a5b81 100644 --- a/src/CompaniesHouse/enum-map.txt +++ b/src/CompaniesHouse/enum-map.txt @@ -10,3 +10,7 @@ company_status|CompaniesHouse.Response|CompanyStatus|true company_type|CompaniesHouse.Response|CompanyType|true company_subtype|CompaniesHouse.Response|CompanySubtype|true +company_status_detail|CompaniesHouse.Response|CompanyStatusDetail|true +jurisdiction|CompaniesHouse.Response.CompanyProfile|Jurisdiction|true +foreign_account_type|CompaniesHouse.Response.CompanyProfile|ForeignAccountType|true +terms_of_account_publication|CompaniesHouse.Response.CompanyProfile|TermsOfAccountPublication|true diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs index c2f1802..20af11e 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs @@ -4,12 +4,12 @@ namespace CompaniesHouse.IntegrationTests.Tests.CompanyProfileTests { - + public class CompanyProfileTestsInvalid : CompanyProfileTestsBase { private const string InvalidCompanyNumber = "ABC00000"; - + protected override async Task When() { await WhenRetrievingAnInvalidCompanyProfile() @@ -20,6 +20,7 @@ await WhenRetrievingAnInvalidCompanyProfile() public void ThenTheProfileIsNotReturned() { _result.Data.ShouldBeNull(); + _result.StatusCode.ShouldBe(404); } private async Task WhenRetrievingAnInvalidCompanyProfile() diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs index 2b31037..9c7641d 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; +using CompaniesHouse.Response; +using CompaniesHouse.Response.CompanyProfile; using Shouldly; using Xunit; namespace CompaniesHouse.IntegrationTests.Tests.CompanyProfileTests { - + public class CompanyProfileTestsValid : CompanyProfileTestsBase { // Google UK company number, unlikely to go away soon private const string ValidCompanyNumber = "03977902"; - + protected override async Task When() { await WhenRetrievingAValidCompanyProfile() @@ -23,6 +25,47 @@ public void ThenTheProfileIsReturned() _result.Data.CompanyName.ShouldNotBeEmpty(); } + [Fact] + public async Task ThenAPlainCompanyProfileIncludesExemptionsAndHasSuperSecurePscs() + { + var result = await _client.GetCompanyProfileAsync("00445790"); + + result.Data.ShouldNotBeNull(); + result.Data.CompanyStatus.ShouldBe(CompanyStatus.Active); + result.Data.Type.ShouldBe(CompanyType.Plc); + result.Data.Links.ShouldNotBeNull(); + result.Data.Links.Exemptions.ShouldNotBeNullOrWhiteSpace(); + result.Data.HasSuperSecurePscs.ShouldBe(false); + } + + [Fact] + public async Task ThenAForeignCompanyProfileIncludesForeignCompanyDetails() + { + var result = await _client.GetCompanyProfileAsync("FC040879"); + + result.Data.ShouldNotBeNull(); + result.Data.Type.ShouldBe(CompanyType.OverseaCompany); + result.Data.ExternalRegistrationNumber.ShouldBe("198600479406"); + result.Data.ForeignCompanyDetails.ShouldNotBeNull(); + result.Data.ForeignCompanyDetails.AccountingRequirement.ShouldNotBeNull(); + result.Data.ForeignCompanyDetails.AccountingRequirement.ForeignAccountType.ShouldBe( + ForeignAccountType.AccountingRequirementsOfOriginatingCountryApply); + result.Data.ForeignCompanyDetails.AccountingRequirement.TermsOfAccountPublication.ShouldBe( + TermsOfAccountPublication.AccountsPublicationDateSuppliedByCompany); + result.Data.ForeignCompanyDetails.IsACreditFinancialInstitution.ShouldBe(true); + result.Data.Links.UkEstablishments.ShouldNotBeNullOrWhiteSpace(); + } + + [Fact] + public async Task ThenACommunityInterestCompanyProfileIncludesSubtype() + { + var result = await _client.GetCompanyProfileAsync("13507518"); + + result.Data.ShouldNotBeNull(); + result.Data.IsCommunityInterestCompany.ShouldBe(true); + result.Data.Subtype.ShouldBe(CompanySubtype.CommunityInterestCompany); + } + private async Task WhenRetrievingAValidCompanyProfile() { _result = await _client.GetCompanyProfileAsync(ValidCompanyNumber) diff --git a/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs new file mode 100644 index 0000000..2c6cf1c --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs @@ -0,0 +1,119 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using CompaniesHouse.Response.CompanyProfile; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class CompanyProfileDeserializationScenarioTests + { + [Fact] + public void PlainCompanyProfile_DeserializesKnownFields() + { + var profile = JsonSerializer.Deserialize(PlainCompanyJson, CompaniesHouseJsonSerializerOptions.Default); + + profile.ShouldNotBeNull(); + profile.CompanyNumber.ShouldBe("00445790"); + profile.CompanyStatus.ShouldBe(CompanyStatus.Active); + profile.Type.ShouldBe(CompanyType.Plc); + profile.Jurisdiction.ShouldBe(Jurisdiction.EnglandWales); + profile.HasSuperSecurePscs.ShouldBe(false); + profile.Links.Exemptions.ShouldBe("/company/00445790/exemptions"); + profile.PreviousCompanyNames.Length.ShouldBe(2); + profile.SicCodes.ShouldBe(["47110"]); + } + + [Fact] + public void ForeignCompanyProfile_DeserializesForeignCompanyDetails() + { + var profile = JsonSerializer.Deserialize(ForeignCompanyJson, CompaniesHouseJsonSerializerOptions.Default); + + profile.ShouldNotBeNull(); + profile.CompanyNumber.ShouldBe("FC040879"); + profile.Type.ShouldBe(CompanyType.OverseaCompany); + profile.ExternalRegistrationNumber.ShouldBe("198600479406"); + profile.ForeignCompanyDetails.ShouldNotBeNull(); + profile.ForeignCompanyDetails.AccountingRequirement.ForeignAccountType.ShouldBe( + ForeignAccountType.AccountingRequirementsOfOriginatingCountryApply); + profile.ForeignCompanyDetails.AccountingRequirement.TermsOfAccountPublication.ShouldBe( + TermsOfAccountPublication.AccountsPublicationDateSuppliedByCompany); + profile.ForeignCompanyDetails.Accounts.AccountPeriodFrom.Day.ShouldBe(1); + profile.ForeignCompanyDetails.Accounts.AccountPeriodTo.Month.ShouldBe(12); + profile.ForeignCompanyDetails.Accounts.MustFileWithin.Months.ShouldBe("12"); + profile.ForeignCompanyDetails.IsACreditFinancialInstitution.ShouldBe(true); + profile.Links.UkEstablishments.ShouldBe("/company/FC040879/uk-establishments"); + } + + [Fact] + public void CommunityInterestCompanyProfile_DeserializesSubtype() + { + var profile = JsonSerializer.Deserialize(CommunityInterestCompanyJson, CompaniesHouseJsonSerializerOptions.Default); + + profile.ShouldNotBeNull(); + profile.CompanyNumber.ShouldBe("13507518"); + profile.IsCommunityInterestCompany.ShouldBe(true); + profile.Subtype.ShouldBe(CompanySubtype.CommunityInterestCompany); + profile.Type.ShouldBe(CompanyType.PrivateLimitedGuarantNsc); + } + + private const string PlainCompanyJson = """ + { + "accounts": {"accounting_reference_date": {"day": "26", "month": "02"}, "last_accounts": {"made_up_to": "2025-02-26", "period_end_on": "2025-02-26", "period_start_on": "2024-02-25", "type": "group"}, "next_accounts": {"due_on": "2026-08-26", "overdue": false, "period_end_on": "2026-02-26", "period_start_on": "2025-02-27"}, "next_due": "2026-08-26", "next_made_up_to": "2026-02-26", "overdue": false}, + "can_file": true, "company_name": "TESCO PLC", "company_number": "00445790", "company_status": "active", + "confirmation_statement": {"last_made_up_to": "2026-06-18", "next_due": "2027-07-02", "next_made_up_to": "2027-06-18", "overdue": false}, + "date_of_creation": "1947-11-27", "etag": "80217136743211b43fe97348238217cf2539d2c9", "has_been_liquidated": false, "has_charges": false, "has_insolvency_history": false, + "jurisdiction": "england-wales", "last_full_members_list_date": "2013-06-07", + "links": {"self": "/company/00445790", "charges": "/company/00445790/charges", "filing_history": "/company/00445790/filing-history", "officers": "/company/00445790/officers", "exemptions": "/company/00445790/exemptions"}, + "previous_company_names": [{"ceased_on": "1983-08-25", "effective_from": "1981-12-14", "name": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY"}, {"ceased_on": "1981-12-14", "effective_from": "1947-11-27", "name": "TESCO STORES (HOLDINGS) LIMITED"}], + "registered_office_address": {"address_line_1": "Tesco House, Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA"}, + "registered_office_is_in_dispute": false, "sic_codes": ["47110"], "type": "plc", "undeliverable_registered_office_address": false, "has_super_secure_pscs": false + } + """; + + private const string ForeignCompanyJson = """ + { + "accounts": {"last_accounts": {"made_up_to": "2021-12-31", "period_end_on": "2021-12-31", "type": "null"}, "next_accounts": {"overdue": false, "period_end_on": "2022-12-31"}, "next_made_up_to": "2022-12-31", "overdue": false}, + "can_file": false, "company_name": "ABSA UK PERMANENT ESTABLISHMENT", "company_number": "FC040879", "company_status": "active", + "date_of_creation": "2022-03-01", "etag": "185a52c646d2f03c05127df15915f784e41acf60", + "external_registration_number": "198600479406", + "foreign_company_details": { + "accounting_requirement": {"foreign_account_type": "accounting-requirements-of-originating-country-apply", "terms_of_account_publication": "accounts-publication-date-supplied-by-company"}, + "accounts": {"account_period_from": {"day": "1", "month": "1"}, "account_period_to": {"day": "31", "month": "12"}, "must_file_within": {"months": "12"}}, + "business_activity": "Financial Services", + "governed_by": "South African Companies Act 71 Of 2008, South African Banks Act 94 0f 1990", + "is_a_credit_financial_institution": true, + "originating_registry": {"country": "SOUTH AFRICA", "name": "Registered With Companies And Intellectural Property Commission"}, + "registration_number": "198600479406", + "legal_form": "Limited And A Public Company" + }, + "has_charges": false, "has_insolvency_history": false, "jurisdiction": "united-kingdom", + "links": {"self": "/company/FC040879", "filing_history": "/company/FC040879/filing-history", "officers": "/company/FC040879/officers", "uk_establishments": "/company/FC040879/uk-establishments"}, + "registered_office_address": {"address_line_1": "Absa Towers West", "address_line_2": "15 Troye Street", "country": "South Africa", "locality": "Johannesburg", "region": "Gauteng 2000"}, + "registered_office_is_in_dispute": false, "type": "oversea-company", "undeliverable_registered_office_address": false, "has_super_secure_pscs": false + } + """; + + private const string CommunityInterestCompanyJson = """ + { + "can_file": true, + "company_name": "COMMUNITY INTEREST SAMPLE", + "company_number": "13507518", + "company_status": "active", + "date_of_creation": "2021-07-15", + "etag": "sample", + "has_charges": false, + "has_insolvency_history": false, + "has_super_secure_pscs": false, + "is_community_interest_company": true, + "jurisdiction": "england-wales", + "links": {"persons_with_significant_control": "/company/13507518/persons-with-significant-control", "persons_with_significant_control_statements": "/company/13507518/persons-with-significant-control-statements", "self": "/company/13507518", "filing_history": "/company/13507518/filing-history", "officers": "/company/13507518/officers"}, + "registered_office_address": {"address_line_1": "1 Example Street", "locality": "London", "postal_code": "SW1A 1AA"}, + "registered_office_is_in_dispute": false, + "subtype": "community-interest-company", + "type": "private-limited-guarant-nsc", + "undeliverable_registered_office_address": false + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs index 19af8ec..756dccd 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; +using System.Text; +using System.Threading; using System.Threading.Tasks; +using CompaniesHouse.Response; +using CompaniesHouse.Response.CompanyProfile; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; using Moq; @@ -14,10 +19,10 @@ namespace CompaniesHouse.Tests.CompaniesHouseCompanyProfileClientTests { public class CompaniesHouseCompanyProfileClientTests { - private CompaniesHouseCompanyProfileClient _client; + private required CompaniesHouseCompanyProfileClient _client; - private CompaniesHouseClientResponse _result; - private ResourceBuilders.CompanyProfile _companyProfile; + private required CompaniesHouseClientResponse _result; + private required ResourceBuilders.CompanyProfile _companyProfile; [Theory] [MemberData(nameof(TestCases))] @@ -42,6 +47,48 @@ public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyPr EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _companyProfile); } + [Fact] + public async Task GivenARealisticPayload_WhenGettingACompanyProfile_ThenNewFieldsAreDeserialized() + { + var uri = new Uri("https://wibble.com/company/00445790"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, TescoCompanyProfileJson); + + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny())) + .Returns(uri); + + _client = new CompaniesHouseCompanyProfileClient(new HttpClient(handler), uriBuilder.Object); + + _result = await _client.GetCompanyProfileAsync("00445790"); + + _result.StatusCode.ShouldBe(200); + _result.Data.ShouldNotBeNull(); + _result.Data.CompanyStatus.ShouldBe(CompanyStatus.Active); + _result.Data.Type.ShouldBe(CompanyType.Plc); + _result.Data.Jurisdiction.ShouldBe(Jurisdiction.EnglandWales); + _result.Data.HasSuperSecurePscs.ShouldBe(false); + _result.Data.Links.Exemptions.ShouldBe("/company/00445790/exemptions"); + } + + [Fact] + public async Task GivenA404Response_WhenGettingACompanyProfile_ThenNullDataAndStatusAreReturned() + { + var uri = new Uri("https://wibble.com/company/missing"); + + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny())) + .Returns(uri); + + _client = new CompaniesHouseCompanyProfileClient(new HttpClient(new NotFoundHttpMessageHandler()), uriBuilder.Object); + + _result = await _client.GetCompanyProfileAsync("missing"); + + _result.ShouldNotBeNull(); + _result.Data.ShouldBeNull(); + _result.IsSuccess.ShouldBeFalse(); + _result.StatusCode.ShouldBe(404); + } + public static IEnumerable TestCases() { @@ -102,5 +149,30 @@ public static IEnumerable TestCases() .Select(testCase => new object[] { testCase }); } + private sealed class NotFoundHttpMessageHandler : HttpMessageHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound) + { + Content = new StringContent(string.Empty, Encoding.UTF8, "application/json"), + ReasonPhrase = "Not Found", + }); + } + } + + private const string TescoCompanyProfileJson = """ + { + "accounts": {"accounting_reference_date": {"day": "26", "month": "02"}, "last_accounts": {"made_up_to": "2025-02-26", "period_end_on": "2025-02-26", "period_start_on": "2024-02-25", "type": "group"}, "next_accounts": {"due_on": "2026-08-26", "overdue": false, "period_end_on": "2026-02-26", "period_start_on": "2025-02-27"}, "next_due": "2026-08-26", "next_made_up_to": "2026-02-26", "overdue": false}, + "can_file": true, "company_name": "TESCO PLC", "company_number": "00445790", "company_status": "active", + "confirmation_statement": {"last_made_up_to": "2026-06-18", "next_due": "2027-07-02", "next_made_up_to": "2027-06-18", "overdue": false}, + "date_of_creation": "1947-11-27", "etag": "80217136743211b43fe97348238217cf2539d2c9", "has_been_liquidated": false, "has_charges": false, "has_insolvency_history": false, + "jurisdiction": "england-wales", "last_full_members_list_date": "2013-06-07", + "links": {"self": "/company/00445790", "charges": "/company/00445790/charges", "filing_history": "/company/00445790/filing-history", "officers": "/company/00445790/officers", "exemptions": "/company/00445790/exemptions"}, + "previous_company_names": [{"ceased_on": "1983-08-25", "effective_from": "1981-12-14", "name": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY"}, {"ceased_on": "1981-12-14", "effective_from": "1947-11-27", "name": "TESCO STORES (HOLDINGS) LIMITED"}], + "registered_office_address": {"address_line_1": "Tesco House, Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA"}, + "registered_office_is_in_dispute": false, "sic_codes": ["47110"], "type": "plc", "undeliverable_registered_office_address": false, "has_super_secure_pscs": false + } + """; } } diff --git a/tests/CompaniesHouse.Tests/EnumerationMappings.cs b/tests/CompaniesHouse.Tests/EnumerationMappings.cs index e870472..7dac3c5 100644 --- a/tests/CompaniesHouse.Tests/EnumerationMappings.cs +++ b/tests/CompaniesHouse.Tests/EnumerationMappings.cs @@ -52,21 +52,21 @@ public static class EnumerationMappings {"petition-to-restore-dissolved", CompanyStatusDetail.PetitionToRestoreDissolved}, {"transformed-to-se", CompanyStatusDetail.TransformedToSe}, {"converted-to-plc", CompanyStatusDetail.ConvertedToPlc}, - {"converted-to-ukeig", CompanyStatusDetail.ConvertedToUnitedKingdomEconomicInterestGroupings}, - {"converted-to-uk-societas", CompanyStatusDetail.ConvertedToUnitedKingdomSocietas}, + {"converted-to-ukeig", CompanyStatusDetail.ConvertedToUkeig}, + {"converted-to-uk-societas", CompanyStatusDetail.ConvertedToUkSocietas}, }; public static readonly IReadOnlyDictionary PossibleJurisdictions = new Dictionary () { - {"england-wales", Jurisdiction.EnglandAndWales}, + {"england-wales", Jurisdiction.EnglandWales}, {"wales", Jurisdiction.Wales}, {"scotland", Jurisdiction.Scotland}, {"northern-ireland", Jurisdiction.NorthernIreland}, {"european-union", Jurisdiction.EuropeanUnion}, {"united-kingdom", Jurisdiction.UnitedKingdom}, {"england", Jurisdiction.England}, - {"noneu", Jurisdiction.NonEu} + {"noneu", Jurisdiction.Noneu} }; public static readonly IReadOnlyDictionary PossibleOfficerRoles = new Dictionary @@ -260,7 +260,7 @@ public static class EnumerationMappings {"part-satisfied", ChargeStatus.PartSatisfied}, {"satisfied", ChargeStatus.Satisfied} }; - + public static readonly IReadOnlyDictionary PossibleRegisteredOfficeAddressCountry = new Dictionary { {"England", OfficeAddressCountry.England}, diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusDetailTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusDetailTests.cs new file mode 100644 index 0000000..248942d --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/CompanyStatusDetailTests.cs @@ -0,0 +1,59 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class CompanyStatusDetailTests + { + [Theory] + [InlineData("transferred-from-uk")] + [InlineData("active-proposal-to-strike-off")] + [InlineData("petition-to-restore-dissolved")] + [InlineData("transformed-to-se")] + [InlineData("converted-to-plc")] + [InlineData("converted-to-ukeig")] + [InlineData("converted-to-uk-societas")] + public void Deserializing_KnownValue_RoundTripsAndIsKnown(string value) + { + var json = $"\"{value}\""; + + var statusDetail = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + statusDetail.Value.ShouldBe(value); + statusDetail.IsKnown.ShouldBeTrue(); + statusDetail.HasValue.ShouldBeTrue(); + JsonSerializer.Serialize(statusDetail, CompaniesHouseJsonSerializerOptions.Default).ShouldBe(json); + } + + [Fact] + public void Deserializing_UnknownValue_DoesNotThrowAndPreservesRawValue() + { + const string json = "\"future-company-status-detail\""; + + var statusDetail = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + statusDetail.Value.ShouldBe("future-company-status-detail"); + statusDetail.IsKnown.ShouldBeFalse(); + statusDetail.Description.ShouldBeNull(); + } + + [Fact] + public void Deserializing_Null_ReturnsDefaultWithNoValue() + { + var statusDetail = JsonSerializer.Deserialize("null", CompaniesHouseJsonSerializerOptions.Default); + + statusDetail.ShouldBe(default); + statusDetail.HasValue.ShouldBeFalse(); + statusDetail.Value.ShouldBe(string.Empty); + } + + [Fact] + public void Description_ReturnsFriendlyTextForKnownValues() + { + CompanyStatusDetail.ActiveProposalToStrikeOff.Description.ShouldBe("Active proposal to strike off"); + CompanyStatusDetail.ConvertedToUkeig.Description.ShouldBe("Converted to UKEIG"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/JurisdictionTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/JurisdictionTests.cs new file mode 100644 index 0000000..7fe2bea --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/JurisdictionTests.cs @@ -0,0 +1,60 @@ +using System.Text.Json; +using CompaniesHouse.Response.CompanyProfile; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class JurisdictionTests + { + [Theory] + [InlineData("england-wales")] + [InlineData("wales")] + [InlineData("scotland")] + [InlineData("northern-ireland")] + [InlineData("european-union")] + [InlineData("united-kingdom")] + [InlineData("england")] + [InlineData("noneu")] + public void Deserializing_KnownValue_RoundTripsAndIsKnown(string value) + { + var json = $"\"{value}\""; + + var jurisdiction = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + jurisdiction.Value.ShouldBe(value); + jurisdiction.IsKnown.ShouldBeTrue(); + jurisdiction.HasValue.ShouldBeTrue(); + JsonSerializer.Serialize(jurisdiction, CompaniesHouseJsonSerializerOptions.Default).ShouldBe(json); + } + + [Fact] + public void Deserializing_UnknownValue_DoesNotThrowAndPreservesRawValue() + { + const string json = "\"future-jurisdiction\""; + + var jurisdiction = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + jurisdiction.Value.ShouldBe("future-jurisdiction"); + jurisdiction.IsKnown.ShouldBeFalse(); + jurisdiction.Description.ShouldBeNull(); + } + + [Fact] + public void Deserializing_Null_ReturnsDefaultWithNoValue() + { + var jurisdiction = JsonSerializer.Deserialize("null", CompaniesHouseJsonSerializerOptions.Default); + + jurisdiction.ShouldBe(default); + jurisdiction.HasValue.ShouldBeFalse(); + jurisdiction.Value.ShouldBe(string.Empty); + } + + [Fact] + public void Description_ReturnsFriendlyTextForKnownValues() + { + Jurisdiction.EnglandWales.Description.ShouldBe("England/Wales"); + Jurisdiction.Noneu.Description.ShouldBe("Foreign (Non E.U.)"); + } + } +} From 74fa7058e41d7ec7ff67c4255860f951ac87e6f7 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 23:23:57 +0100 Subject: [PATCH 10/38] Fix CompanyProfile required-property regression from plan 07 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. --- .../Response/CompanyProfile/CompanyProfile.cs | 22 +++++++++---------- .../CompanyProfile/CompanyProfileLinks.cs | 16 +++++++------- ...CompaniesHouseCompanyProfileClientTests.cs | 6 ++--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs index 7139a67..8f589a0 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs @@ -10,25 +10,25 @@ public class CompanyProfile public CompanyType Type { get; set; } [JsonPropertyName("etag")] - public required string ETag { get; set; } + public string ETag { get; set; } [JsonPropertyName("accounts")] - public required Accounts Accounts { get; set; } + public Accounts Accounts { get; set; } [JsonPropertyName("annual_return")] - public required AnnualReturn AnnualReturn { get; set; } + public AnnualReturn AnnualReturn { get; set; } [JsonPropertyName("confirmation_statement")] - public required ConfirmationStatement ConfirmationStatement { get; set; } + public ConfirmationStatement ConfirmationStatement { get; set; } [JsonPropertyName("can_file")] public bool? CanFile { get; set; } [JsonPropertyName("company_name")] - public required string CompanyName { get; set; } + public string CompanyName { get; set; } [JsonPropertyName("company_number")] - public required string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -75,24 +75,24 @@ public class CompanyProfile public DateTime? LastFullMembersListDate { get; set; } [JsonPropertyName("links")] - public required CompanyProfileLinks Links { get; set; } + public CompanyProfileLinks Links { get; set; } [JsonPropertyName("previous_company_names")] - public required PreviousCompanyName[] PreviousCompanyNames { get; set; } + public PreviousCompanyName[] PreviousCompanyNames { get; set; } [JsonPropertyName("registered_office_address")] - public required Address RegisteredOfficeAddress { get; set; } + public Address RegisteredOfficeAddress { get; set; } [JsonPropertyName("registered_office_is_in_dispute")] public bool? RegisteredOfficeIsInDispute { get; set; } [JsonPropertyName("sic_codes")] - public required string[] SicCodes { get; set; } + public string[] SicCodes { get; set; } [JsonPropertyName("undeliverable_registered_office_address")] public bool? UndeliverableRegisteredOfficeAddress { get; set; } [JsonPropertyName("branch_company_details")] - public required BranchCompanyDetails BranchCompanyDetails { get; set; } + public BranchCompanyDetails BranchCompanyDetails { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs index b94820f..45e373b 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs @@ -5,31 +5,31 @@ namespace CompaniesHouse.Response.CompanyProfile public class CompanyProfileLinks { [JsonPropertyName("charges")] - public required string Charges { get; set; } + public string? Charges { get; set; } [JsonPropertyName("exemptions")] public string? Exemptions { get; set; } [JsonPropertyName("filing_history")] - public required string FilingHistory { get; set; } + public string? FilingHistory { get; set; } [JsonPropertyName("insolvency")] - public required string Insolvency { get; set; } + public string? Insolvency { get; set; } [JsonPropertyName("officers")] - public required string Officers { get; set; } + public string? Officers { get; set; } [JsonPropertyName("persons_with_significant_control")] - public required string PersonsWithSignificantControl { get; set; } + public string? PersonsWithSignificantControl { get; set; } [JsonPropertyName("persons_with_significant_control_statements")] - public required string PersonsWithSignificantControlStatements { get; set; } + public string? PersonsWithSignificantControlStatements { get; set; } [JsonPropertyName("registers")] - public required string Registers { get; set; } + public string? Registers { get; set; } [JsonPropertyName("self")] - public required string Self { get; set; } + public string? Self { get; set; } [JsonPropertyName("uk_establishments")] public string? UkEstablishments { get; set; } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs index 756dccd..3b24b28 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs @@ -19,10 +19,10 @@ namespace CompaniesHouse.Tests.CompaniesHouseCompanyProfileClientTests { public class CompaniesHouseCompanyProfileClientTests { - private required CompaniesHouseCompanyProfileClient _client; + private CompaniesHouseCompanyProfileClient _client; - private required CompaniesHouseClientResponse _result; - private required ResourceBuilders.CompanyProfile _companyProfile; + private CompaniesHouseClientResponse _result; + private ResourceBuilders.CompanyProfile _companyProfile; [Theory] [MemberData(nameof(TestCases))] From 40eff67c0c717dccbc8e60daf47753e36022d203 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 23:39:33 +0100 Subject: [PATCH 11/38] Audit live search payloads and fix missing fields 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. --- .../Search/AdvancedCompanySearch/Company.cs | 6 +- .../Response/Search/AllSearch/AllSearch.cs | 3 + .../Response/Search/CompanySearch/Company.cs | 8 +- .../Response/Search/CompanySearch/Matches.cs | 8 +- .../DisqualifiedOfficerSearch.cs | 3 + .../DissolvedCompaniesSearch/Company.cs | 8 +- .../Search/OfficerSearch/OfficerSearch.cs | 3 + .../AdvancedCompanySearchTests.cs | 30 ++ .../Tests/SearchingTests/AllSearchTests.cs | 24 ++ .../CompaniesAlphabeticalSearchTests.cs | 21 + .../SearchingTests/CompanySearchTests.cs | 32 ++ .../DisqualifiedOfficersSearchTests.cs | 9 + .../DissolvedCompaniesSearchTests.cs | 29 ++ .../SearchingTests/OfficersSearchTests.cs | 12 + ...rchResponseDeserializationScenarioTests.cs | 375 ++++++++++++++++++ ...sHouseSearchClientTestsForCompanySearch.cs | 8 + .../CompanySearchResource/CompanyDetails.cs | 6 + .../CompanySearchResourceBuilder.cs | 28 +- 18 files changed, 595 insertions(+), 18 deletions(-) create mode 100644 tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs index 9b5d0b1..2a74582 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs @@ -17,7 +17,7 @@ public class Company public CompanyStatus CompanyStatus { get; set; } [JsonPropertyName("company_subtype")] - public CompanySubtype CompanySubtype { get; set; } + public CompanySubtype? CompanySubtype { get; set; } [JsonPropertyName("company_type")] public CompanyType CompanyType { get; set; } @@ -36,9 +36,9 @@ public class Company public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } [JsonPropertyName("registered_office_address")] - public Address RegisteredOfficeAddress { get; set; } + public Address? RegisteredOfficeAddress { get; set; } [JsonPropertyName("sic_codes")] - public string[] SicCodes { get; set; } + public string[]? SicCodes { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs index f4faa07..85531ae 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs @@ -17,6 +17,9 @@ public class AllSearch [JsonPropertyName("kind")] public string Kind { get; set; } + [JsonPropertyName("page_number")] + public int? PageNumber { get; set; } + [JsonPropertyName("start_index")] public int? StartIndex { get; set; } diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs index c240104..bf13fb8 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs @@ -9,6 +9,9 @@ public class Company : SearchItem [JsonPropertyName("address")] public Address Address { get; set; } + [JsonPropertyName("address_snippet")] + public string? AddressSnippet { get; set; } + [JsonPropertyName("company_number")] public string CompanyNumber { get; set; } @@ -29,7 +32,10 @@ public class Company : SearchItem public string Description { get; set; } [JsonPropertyName("description_identifier")] - public object[] DescriptionIdentifier { get; set; } + public string[] DescriptionIdentifier { get; set; } + + [JsonPropertyName("external_registration_number")] + public string? ExternalRegistrationNumber { get; set; } [JsonPropertyName("matches")] public Matches Matches { get; set; } diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs index b1dc53e..62291ef 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs @@ -4,8 +4,14 @@ namespace CompaniesHouse.Response.Search.CompanySearch { public class Matches { + [JsonPropertyName("address_snippet")] + public int[]? AddressSnippet { get; set; } + + [JsonPropertyName("snippet")] + public int[]? Snippet { get; set; } + [JsonPropertyName("title")] - public int[] Title { get; set; } + public int[]? Title { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs index aeb4605..fcf7868 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs @@ -13,6 +13,9 @@ public class DisqualifiedOfficerSearch [JsonPropertyName("kind")] public string Kind { get; set; } + [JsonPropertyName("page_number")] + public int? PageNumber { get; set; } + [JsonPropertyName("start_index")] public int StartIndex { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs index be53e7c..3cb49ee 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs @@ -27,15 +27,15 @@ public class Company public string Kind { get; set; } [JsonPropertyName("matched_previous_company_name")] - public PreviousCompanyName MatchedPreviousCompanyName { get; set; } + public PreviousCompanyName? MatchedPreviousCompanyName { get; set; } [JsonPropertyName("ordered_alpha_key_with_id")] - public string OrderedAlphaKeyWithId { get; set; } + public string? OrderedAlphaKeyWithId { get; set; } [JsonPropertyName("previous_company_names")] - public PreviousCompanyName[] PreviousCompanyNames { get; set; } + public PreviousCompanyName[]? PreviousCompanyNames { get; set; } [JsonPropertyName("registered_office_address")] - public Address RegisteredOfficeAddress { get; set; } + public Address? RegisteredOfficeAddress { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs index f2bcabf..ddf25ca 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs @@ -14,6 +14,9 @@ public class OfficerSearch [JsonPropertyName("kind")] public string Kind { get; set; } + [JsonPropertyName("page_number")] + public int? PageNumber { get; set; } + [JsonPropertyName("start_index")] public int StartIndex { get; set; } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs index ee86017..a7849d4 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response; +using System.Linq; using Shouldly; using Xunit; @@ -28,5 +29,34 @@ public async Task ThenCompaniesAreReturned() result.Data.ShouldNotBeNull(); result.Data.Items.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenCompanySubtypeCanBeUsedAsALiveFilter() + { + var result = await _client.AdvancedCompanySearchAsync(new AdvancedCompanySearchRequest + { + CompanySubtypes = new[] { CompanySubtype.CommunityInterestCompany }, + Size = 10, + }); + + result.Data.ShouldNotBeNull(); + result.Data.Items.ShouldContain(x => x.CompanySubtype == CompanySubtype.CommunityInterestCompany); + } + + [Fact] + public async Task ThenLocationAndSicCodeFiltersCanBeUsedTogether() + { + var result = await _client.AdvancedCompanySearchAsync(new AdvancedCompanySearchRequest + { + CompanyStatuses = new[] { CompanyStatus.Active }, + Location = "Manchester", + SicCodes = new[] { "62012" }, + Size = 10, + }); + + result.Data.ShouldNotBeNull(); + result.Data.Items.ShouldNotBeEmpty(); + result.Data.Items.ShouldContain(x => x.SicCodes != null && x.SicCodes.Contains("62012")); + } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs index ee591fb..21da3d0 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs @@ -1,5 +1,8 @@ +using System.Linq; using System.Threading.Tasks; using CompaniesHouse.Request; +using CompaniesHouse.Response.Search.CompanySearch; +using CompaniesHouse.Response.Search.OfficerSearch; using CompaniesHouse.Response.Search.AllSearch; using Shouldly; using Xunit; @@ -24,5 +27,26 @@ public async Task ThenItemsAreReturned(string query) result.Data.Items.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenPagingAndMixedItemTypesAreReturned() + { + var result = await _client.SearchAllAsync(new SearchAllRequest { Query = "john", ItemsPerPage = 20 }); + + result.Data.PageNumber.ShouldBe(1); + result.Data.Items.ShouldContain(x => x is Company); + result.Data.Items.ShouldContain(x => x is Officer); + } + + [Fact] + public async Task ThenCompanySpecificFieldsRoundTripFromSearchAll() + { + var result = await _client.SearchAllAsync(new SearchAllRequest { Query = "absa uk permanent establishment", ItemsPerPage = 20 }); + + var company = result.Data.Items.OfType().Single(x => x.CompanyNumber == "FC040879"); + company.CompanyNumber.ShouldBe("FC040879"); + company.AddressSnippet.ShouldBe("Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa"); + company.ExternalRegistrationNumber.ShouldBe("198600479406"); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs index df96cd4..34c4e34 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs @@ -28,5 +28,26 @@ public async Task ThenCompaniesAreReturned(string query) result.Data.ShouldNotBeNull(); result.Data.Items.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenAlphabeticalPagingParametersCanBeSent() + { + var firstPage = await _client.SearchCompaniesAlphabeticallyAsync(new SearchCompaniesAlphabeticallyRequest + { + Query = "tesco", + Size = 5, + }); + + var secondPage = await _client.SearchCompaniesAlphabeticallyAsync(new SearchCompaniesAlphabeticallyRequest + { + Query = "tesco", + Size = 5, + SearchAbove = firstPage.Data.Items[^1].OrderedAlphaKeyWithId, + }); + + firstPage.Data.Kind.ShouldBe("search#alphabetical-search"); + secondPage.Data.ShouldNotBeNull(); + secondPage.Data.Items.ShouldNotBeEmpty(); + } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs index 6d0192f..9978e0d 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.CompanySearch; +using System.Linq; using Shouldly; using Xunit; @@ -25,5 +26,36 @@ public async Task ThenCompaniesAreReturned(string query) result.Data.Companies.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenForeignCompanyFieldsAreReturned() + { + var result = await _client.SearchCompanyAsync(new SearchCompanyRequest + { + Query = "absa uk permanent establishment", + StartIndex = 0, + ItemsPerPage = 20, + }); + + var company = result.Data.Companies.Single(x => x.CompanyNumber == "FC040879"); + result.Data.PageNumber.ShouldBe(1); + company.AddressSnippet.ShouldBe("Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa"); + company.ExternalRegistrationNumber.ShouldBe("198600479406"); + company.DescriptionIdentifier.ShouldBe(["first-uk-establishment-opened-on"]); + } + + [Fact] + public async Task ThenRestrictionsCanBeSentToTheLiveApi() + { + var result = await _client.SearchCompanyAsync(new SearchCompanyRequest + { + Query = "tesco", + Restrictions = "active-companies-only", + ItemsPerPage = 10, + }); + + result.Data.ShouldNotBeNull(); + result.Data.Companies.ShouldNotBeEmpty(); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs index 838da72..e81bc53 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs @@ -22,5 +22,14 @@ public async Task ThenDisqualifiedOfficersAreReturned() result.Data.DisqualifiedOfficers.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenPagingMetadataAndDateOfBirthAreReturned() + { + var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "john", ItemsPerPage = 20 }); + + result.Data.PageNumber.ShouldBe(1); + result.Data.DisqualifiedOfficers[0].DateOfBirth.Year.ShouldBeGreaterThan(1900); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs index 7e22046..47d339f 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs @@ -29,5 +29,34 @@ public async Task ThenCompaniesAreReturned(string query) result.Data.ShouldNotBeNull(); result.Data.Items.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenPreviousNameSearchReturnsMatchedPreviousCompanyName() + { + var result = await _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest + { + Query = "radio rentals", + SearchType = "previous-name-dissolved", + Size = 10, + }); + + result.Data.Kind.ShouldBe("search#previous-name-dissolved"); + result.Data.TopHit.MatchedPreviousCompanyName.ShouldNotBeNull(); + result.Data.TopHit.MatchedPreviousCompanyName.Name.ShouldContain("RADIO RENTALS"); + } + + [Fact] + public async Task ThenAlphabeticalSearchReturnsOrderedAlphaKeys() + { + var result = await _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest + { + Query = "tes", + SearchType = "alphabetical", + Size = 10, + }); + + result.Data.Kind.ShouldBe("search#alphabetical-dissolved"); + result.Data.Items.ShouldContain(x => !string.IsNullOrWhiteSpace(x.OrderedAlphaKeyWithId)); + } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs index 740e52c..7fd2f8a 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.OfficerSearch; +using System.Linq; using Shouldly; using Xunit; @@ -22,5 +23,16 @@ public async Task ThenOfficersAreReturned() result.Data.Officers.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenLiveOfficerBirthMonthAndPagingMetadataAreReturned() + { + var result = await _client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Alan Sugar", ItemsPerPage = 20 }); + + var officer = result.Data.Officers.First(x => x.Title == "Lord Alan Michael SUGAR" && x.DateOfBirth?.Year == 1947); + result.Data.PageNumber.ShouldBe(1); + officer.DateOfBirth.ShouldNotBeNull(); + officer.DateOfBirth.Month.ShouldBe(3); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs new file mode 100644 index 0000000..307a3c7 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs @@ -0,0 +1,375 @@ +using System.Linq; +using System.Text.Json; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Search.AdvancedCompanySearch; +using CompaniesHouse.Response.Search.AllSearch; +using CompaniesHouse.Response.Search.CompanySearch; +using CompaniesHouse.Response.Search.DissolvedCompaniesSearch; +using CompaniesHouse.Response.Search.OfficerSearch; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class SearchResponseDeserializationScenarioTests + { + [Fact] + public void SearchAllPayload_DeserializesMixedCompanyAndOfficerItems() + { + var payload = JsonSerializer.Deserialize(SearchAllJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + payload.PageNumber.ShouldBe(1); + payload.TotalResults.ShouldBe(10000); + payload.Items.Length.ShouldBe(3); + payload.Items[0].ShouldBeOfType(); + payload.Items[2].ShouldBeOfType(); + } + + [Fact] + public void CompanySearchPayload_DeserializesAddressSnippetAndExternalRegistrationNumber() + { + var payload = JsonSerializer.Deserialize(CompanySearchJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + payload.PageNumber.ShouldBe(1); + payload.Companies.Length.ShouldBe(1); + payload.Companies[0].AddressSnippet.ShouldBe("Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa"); + payload.Companies[0].ExternalRegistrationNumber.ShouldBe("198600479406"); + payload.Companies[0].DescriptionIdentifier.ShouldBe(["first-uk-establishment-opened-on"]); + payload.Companies[0].Matches.Snippet.ShouldBeEmpty(); + } + + [Fact] + public void OfficerSearchPayload_DeserializesPageNumberAndOptionalDateOfBirth() + { + var payload = JsonSerializer.Deserialize(OfficerSearchJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + payload.PageNumber.ShouldBe(1); + payload.Officers.Length.ShouldBe(3); + payload.Officers[0].DateOfBirth.ShouldNotBeNull(); + payload.Officers[0].DateOfBirth.Month.ShouldBe(3); + payload.Officers[0].DateOfBirth.Year.ShouldBe(1947); + payload.Officers[2].DateOfBirth.ShouldBeNull(); + } + + [Fact] + public void AdvancedCompanySearchPayload_DeserializesOptionalSubtypeAndSicCodes() + { + var payload = JsonSerializer.Deserialize(AdvancedCompanySearchJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + payload.TopHit.CompanySubtype.ShouldBeNull(); + payload.Items[0].RegisteredOfficeAddress.ShouldNotBeNull(); + payload.Items[0].RegisteredOfficeAddress.AddressLine1.ShouldBeNull(); + payload.Items[0].SicCodes.ShouldBeNull(); + payload.Items[1].CompanySubtype.ShouldBe(CompanySubtype.CommunityInterestCompany); + payload.Items[1].SicCodes.ShouldBe(["86900"]); + } + + [Fact] + public void DissolvedCompaniesPayload_DeserializesSearchTypeSpecificOptionalFields() + { + var payload = JsonSerializer.Deserialize(DissolvedCompaniesSearchJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + payload.Kind.ShouldBe("search#previous-name-dissolved"); + payload.Hits.ShouldBe(932); + payload.TopHit.OrderedAlphaKeyWithId.ShouldBeNull(); + payload.TopHit.MatchedPreviousCompanyName.ShouldNotBeNull(); + payload.TopHit.MatchedPreviousCompanyName.Name.ShouldBe("RADIO RENTALS VODAFONE LIMITED"); + payload.TopHit.RegisteredOfficeAddress.ShouldBeNull(); + payload.Items.Single().PreviousCompanyNames.ShouldNotBeNull(); + payload.Items.Single().PreviousCompanyNames.Length.ShouldBe(3); + } + + private const string SearchAllJson = """ + { + "items": [ + { + "kind": "searchresults#company", + "description_identifier": ["dissolved-on"], + "company_status": "dissolved", + "date_of_creation": "2023-06-23", + "date_of_cessation": "2026-05-26", + "company_type": "ltd", + "company_number": "14957251", + "address": { + "address_line_1": "Fellows Road", + "address_line_2": "Flat 6", + "country": "England", + "locality": "London", + "postal_code": "NW3 3LJ", + "premises": "54" + }, + "title": "JOHN LIMITED", + "address_snippet": "54 Fellows Road, Flat 6, London, England, NW3 3LJ", + "description": "14957251 - Dissolved on 26 May 2026", + "links": { "self": "/company/14957251" }, + "snippet": "", + "matches": { "snippet": [] } + }, + { + "kind": "searchresults#company", + "description_identifier": ["dissolved-on"], + "company_status": "dissolved", + "date_of_creation": "2021-10-22", + "date_of_cessation": "2023-05-02", + "company_type": "ltd", + "company_number": "13698801", + "address": { + "address_line_1": "Beechwood Close", + "country": "England", + "locality": "Ascot", + "postal_code": "SL5 8QJ", + "premises": "19" + }, + "title": "JOHN LTD", + "address_snippet": "19 Beechwood Close, Ascot, England, SL5 8QJ", + "description": "13698801 - Dissolved on 2 May 2023", + "links": { "self": "/company/13698801" }, + "snippet": "", + "matches": { "snippet": [] } + }, + { + "kind": "searchresults#officer", + "appointment_count": 1, + "snippet": "", + "description_identifiers": ["appointment-count"], + "matches": { "snippet": [] }, + "title": "JOHN WYATT (FEED FATS) LIMITED", + "description": "Total number of appointments 1", + "links": { "self": "/officers/WofTSaSTk6iaYlGERAQOdurrDOE/appointments" }, + "address": { + "address_line_1": "Holbeck Lane", + "country": "United Kingdom", + "locality": "Leeds", + "postal_code": "LS11 9XE", + "premises": "Braithwaite Street" + }, + "address_snippet": "Braithwaite Street, Holbeck Lane, Leeds, United Kingdom, LS11 9XE" + } + ], + "kind": "search#all", + "page_number": 1, + "items_per_page": 5, + "total_results": 10000, + "start_index": 0 + } + """; + + private const string CompanySearchJson = """ + { + "items": [ + { + "kind": "searchresults#company", + "description_identifier": ["first-uk-establishment-opened-on"], + "company_status": "active", + "date_of_creation": "2022-03-01", + "external_registration_number": "198600479406", + "company_type": "oversea-company", + "company_number": "FC040879", + "address": { + "address_line_1": "15 Troye Street", + "country": "South Africa", + "locality": "Johannesburg", + "premises": "Absa Towers West", + "region": "Gauteng 2000" + }, + "title": "ABSA UK PERMANENT ESTABLISHMENT", + "address_snippet": "Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa", + "description": "FC040879 - First UK establishment opened on 1 March 2022", + "links": { "self": "/company/FC040879" }, + "snippet": "", + "matches": { "snippet": [] } + } + ], + "kind": "search#companies", + "page_number": 1, + "items_per_page": 1, + "total_results": 1, + "start_index": 0 + } + """; + + private const string OfficerSearchJson = """ + { + "items": [ + { + "kind": "searchresults#officer", + "appointment_count": 75, + "snippet": "", + "description_identifiers": ["appointment-count", "born-on"], + "matches": { "snippet": [] }, + "title": "Lord Alan Michael SUGAR", + "description": "Total number of appointments 75 - Born March 1947", + "links": { "self": "/officers/1fox8G7xzfgdlmkfSG5a24fprbM/appointments" }, + "address": { + "address_line_1": "Goldings Hill", + "country": "England", + "locality": "Loughton", + "postal_code": "IG10 2RW", + "premises": "Amshold House" + }, + "address_snippet": "Amshold House, Goldings Hill, Loughton, England, IG10 2RW", + "date_of_birth": { "month": 3, "year": 1947 } + }, + { + "kind": "searchresults#officer", + "appointment_count": 1, + "snippet": "", + "description_identifiers": ["appointment-count", "born-on"], + "matches": { "snippet": [] }, + "title": "Lord Alan Michael SUGAR", + "description": "Total number of appointments 1 - Born March 1947", + "links": { "self": "/officers/WjZAkMgKvhRNbn0rXNmOSms8sA0/appointments" }, + "address": { + "address_line_1": "Goldings Hill", + "country": "England", + "locality": "Loughton", + "postal_code": "IG10 2RW", + "premises": "Amshold House" + }, + "address_snippet": "Amshold House, Goldings Hill, Loughton, England, IG10 2RW", + "date_of_birth": { "month": 3, "year": 1947 } + }, + { + "kind": "searchresults#officer", + "appointment_count": 1, + "snippet": "", + "description_identifiers": ["appointment-count"], + "matches": { "snippet": [] }, + "title": "SUGARMAN INTERNATIONAL LTD", + "description": "Total number of appointments 1", + "links": { "self": "/officers/Jgko-GXS5n6KtAyggx9VUsuYJP0/appointments" }, + "address": { + "address_line_1": "C/O Srlv", + "address_line_2": "1 Conduit Street", + "locality": "London", + "postal_code": "W1S 2XA" + }, + "address_snippet": "C/O Srlv, 1 Conduit Street, London, W1S 2XA" + } + ], + "kind": "search#officers", + "page_number": 1, + "items_per_page": 5, + "total_results": 10000, + "start_index": 0 + } + """; + + private const string AdvancedCompanySearchJson = """ + { + "etag": "sample", + "top_hit": { + "company_name": "TESCO PERSONAL FINANCE LIFE LIMITED", + "company_number": "SA000044", + "company_status": "active", + "company_type": "assurance-company", + "kind": "search-results#company", + "links": { "company_profile": "/company/SA000044" }, + "registered_office_address": {} + }, + "items": [ + { + "company_name": "TESCO PERSONAL FINANCE LIFE LIMITED", + "company_number": "SA000044", + "company_status": "active", + "company_type": "assurance-company", + "kind": "search-results#company", + "links": { "company_profile": "/company/SA000044" }, + "registered_office_address": {} + }, + { + "company_name": "SKILLS FOR LIFE - LEARNING CENTRE C.I.C", + "company_number": "NI066532", + "company_status": "dissolved", + "company_type": "private-limited-guarant-nsc", + "company_subtype": "community-interest-company", + "kind": "search-results#company", + "links": { "company_profile": "/company/NI066532" }, + "date_of_cessation": "2019-02-12", + "date_of_creation": "2007-10-08", + "registered_office_address": { + "address_line_1": "18 Knights Green", + "address_line_2": "Belfast", + "locality": "Co Down", + "postal_code": "BT6 9LA" + }, + "sic_codes": ["86900"] + } + ], + "kind": "search#advanced-search", + "hits": 255 + } + """; + + private const string DissolvedCompaniesSearchJson = """ + { + "etag": "sample", + "top_hit": { + "company_name": "THORN EMI DATAPHONE LIMITED", + "company_number": "00368502", + "company_status": "dissolved", + "kind": "searchresults#dissolved-company", + "date_of_cessation": "1994-01-11", + "date_of_creation": "1941-08-02", + "previous_company_names": [ + { + "ceased_on": "1985-09-25", + "effective_from": "1984-12-04", + "name": "RADIO RENTALS VODAFONE LIMITED", + "company_number": "00368502" + } + ], + "matched_previous_company_name": { + "ceased_on": "1985-09-25", + "effective_from": "1984-12-04", + "name": "RADIO RENTALS VODAFONE LIMITED", + "company_number": "00368502" + } + }, + "items": [ + { + "company_name": "THORN EMI DATAPHONE LIMITED", + "company_number": "00368502", + "company_status": "dissolved", + "kind": "searchresults#dissolved-company", + "date_of_cessation": "1994-01-11", + "date_of_creation": "1941-08-02", + "previous_company_names": [ + { + "ceased_on": "1985-09-25", + "effective_from": "1984-12-04", + "name": "RADIO RENTALS VODAFONE LIMITED", + "company_number": "00368502" + }, + { + "ceased_on": "1984-12-04", + "effective_from": "1984-12-03", + "name": "RADIO RENTALS VODAFONE LIMITED", + "company_number": "00368502" + }, + { + "ceased_on": "1984-12-03", + "effective_from": "1941-08-02", + "name": "CHARLES MIDLANDS RELAYS LIMITED ", + "company_number": "00368502" + } + ], + "matched_previous_company_name": { + "ceased_on": "1985-09-25", + "effective_from": "1984-12-04", + "name": "RADIO RENTALS VODAFONE LIMITED", + "company_number": "00368502" + } + } + ], + "kind": "search#previous-name-dissolved", + "hits": 932 + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs index 4e7e8ca..be0051f 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs @@ -94,14 +94,18 @@ public void ThenTheCompanyWithUnknownDateOfCessationIsReturned() actual.Address.PoBox.ShouldBe(_companyWithUnknownDateOfCessation.PoBox); actual.Address.PostalCode.ShouldBe(_companyWithUnknownDateOfCessation.PostalCode); actual.Address.Region.ShouldBe(_companyWithUnknownDateOfCessation.Region); + actual.AddressSnippet.ShouldBe(_companyWithUnknownDateOfCessation.AddressSnippet); actual.CompanyStatus.ShouldBe(ExpectedCompanyStatus[_companyWithUnknownDateOfCessation.CompanyStatus]); actual.CompanyType.ShouldBe(ExpectedCompanyType[_companyWithUnknownDateOfCessation.CompanyType]); actual.DateOfCessation.ShouldBeNull(); actual.DateOfCreation.ShouldBe(_companyWithUnknownDateOfCessation.DateOfCreation.Date); actual.Description.ShouldBe(_companyWithUnknownDateOfCessation.Description); + actual.DescriptionIdentifier.ShouldBe(["incorporated-on"]); + actual.ExternalRegistrationNumber.ShouldBe(_companyWithUnknownDateOfCessation.ExternalRegistrationNumber); actual.Kind.ShouldBe(_companyWithUnknownDateOfCessation.Kind); actual.Links.Self.ShouldBe(_companyWithUnknownDateOfCessation.LinksSelf); + actual.Matches.Snippet.ShouldBe(_companyWithUnknownDateOfCessation.MatchesSnippet); actual.Matches.Title.ShouldBe(_companyWithUnknownDateOfCessation.MatchesTitle); actual.Snippet.ShouldBe(_companyWithUnknownDateOfCessation.Snippet); actual.Title.ShouldBe(_companyWithUnknownDateOfCessation.Title); @@ -130,14 +134,18 @@ public void ThenTheCompaniesAreCorrect() actual.Address.PoBox.ShouldBe(companyDetails.PoBox); actual.Address.PostalCode.ShouldBe(companyDetails.PostalCode); actual.Address.Region.ShouldBe(companyDetails.Region); + actual.AddressSnippet.ShouldBe(companyDetails.AddressSnippet); actual.CompanyStatus.ShouldBe(ExpectedCompanyStatus[companyDetails.CompanyStatus ?? ""]); actual.CompanyType.ShouldBe(ExpectedCompanyType[companyDetails.CompanyType]); actual.DateOfCessation.ShouldBe(companyDetails.DateOfCessation.Date); actual.DateOfCreation.ShouldBe(companyDetails.DateOfCreation.Date); actual.Description.ShouldBe(companyDetails.Description); + actual.DescriptionIdentifier.ShouldBe(["incorporated-on"]); + actual.ExternalRegistrationNumber.ShouldBe(companyDetails.ExternalRegistrationNumber); actual.Kind.ShouldBe(companyDetails.Kind); actual.Links.Self.ShouldBe(companyDetails.LinksSelf); + actual.Matches.Snippet.ShouldBe(companyDetails.MatchesSnippet); actual.Matches.Title.ShouldBe(companyDetails.MatchesTitle); actual.Snippet.ShouldBe(companyDetails.Snippet); actual.Title.ShouldBe(companyDetails.Title); diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs index cba888c..477bb06 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs @@ -26,12 +26,16 @@ public class CompanyDetails public string CompanyType { get; set; } + public string AddressSnippet { get; set; } + public DateTime DateOfCessation { get; set; } public DateTime DateOfCreation { get; set; } public string Description { get; set; } + public string ExternalRegistrationNumber { get; set; } + public string Kind { get; set; } public string LinksSelf { get; set; } @@ -40,6 +44,8 @@ public class CompanyDetails public string Title { get; set; } + public int[] MatchesSnippet { get; set; } + public int[] MatchesTitle { get; set; } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanySearchResourceBuilder.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanySearchResourceBuilder.cs index a5d1465..31103fd 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanySearchResourceBuilder.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanySearchResourceBuilder.cs @@ -5,7 +5,7 @@ namespace CompaniesHouse.Tests.ResourceBuilders.CompanySearchResource public class CompanySearchResourceBuilder { private readonly List _itemBlocks = new List(); - + public string CreateResource(ResourceDetails companySearch) { var resource = @@ -35,11 +35,11 @@ public CompanySearchResourceBuilder AddCompanies(IEnumerable com public CompanySearchResourceBuilder AddCompany(CompanyDetails companyDetails) { - var companyStatusField = companyDetails.CompanyStatus == null - ? "null" - : "\"" + companyDetails.CompanyStatus + "\""; + var companyStatusField = companyDetails.CompanyStatus == null + ? "null" + : "\"" + companyDetails.CompanyStatus + "\""; - var itemBlock = + var itemBlock = $@" {{ ""address"": {{ ""address_line_1"" : ""{companyDetails.AddressLine1}"", @@ -56,24 +56,29 @@ public CompanySearchResourceBuilder AddCompany(CompanyDetails companyDetails) ""company_type"" : ""{companyDetails.CompanyType}"", ""date_of_cessation"" : ""{companyDetails.DateOfCessation.ToString("yyyy-MM-dd")}"", ""date_of_creation"" : ""{companyDetails.DateOfCreation.ToString("yyyy-MM-dd")}"", + ""external_registration_number"" : ""{companyDetails.ExternalRegistrationNumber}"", + ""address_snippet"" : ""{companyDetails.AddressSnippet}"", ""description"" : ""{companyDetails.Description}"", ""description_identifier"" : [ - null + ""incorporated-on"" ], ""kind"" : ""{companyDetails.Kind}"", ""links"" : {{ ""self"" : ""{companyDetails.LinksSelf}"" }}, ""matches"" : {{ + ""snippet"" : [ + {string.Join(", ", companyDetails.MatchesSnippet)} + ], ""title"" : [ - {string.Join(", ",companyDetails.MatchesTitle)} + {string.Join(", ", companyDetails.MatchesTitle)} ] }}, ""snippet"" : ""{companyDetails.Snippet}"", ""title"" : ""{companyDetails.Title}"" }}"; _itemBlocks.Add(itemBlock); - + return this; } @@ -97,15 +102,20 @@ public CompanySearchResourceBuilder AddCompanyWithUnknownDateOfCessation(Company ""company_type"" : ""{companyDetails.CompanyType}"", ""date_of_cessation"" : ""Unknown"", ""date_of_creation"" : ""{companyDetails.DateOfCreation.ToString("yyyy-MM-dd")}"", + ""external_registration_number"" : ""{companyDetails.ExternalRegistrationNumber}"", + ""address_snippet"" : ""{companyDetails.AddressSnippet}"", ""description"" : ""{companyDetails.Description}"", ""description_identifier"" : [ - null + ""incorporated-on"" ], ""kind"" : ""{companyDetails.Kind}"", ""links"" : {{ ""self"" : ""{companyDetails.LinksSelf}"" }}, ""matches"" : {{ + ""snippet"" : [ + {string.Join(", ", companyDetails.MatchesSnippet)} + ], ""title"" : [ {string.Join(", ", companyDetails.MatchesTitle)} ] From 3aa42a382c1e8dcc98574476b7942481dace9794 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 23:41:25 +0100 Subject: [PATCH 12/38] Implement officers v-next schema fixes 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. --- src/CompaniesHouse/CompaniesHouseClient.cs | 20 ++- .../CompaniesHouseOfficersClient.cs | 11 +- .../ICompaniesHouseOfficersClient.cs | 10 +- .../Officers/IdentityVerificationDetails.cs | 30 ++++ .../Response/Officers/Officer.cs | 38 ++++- .../Officers/OfficerAppointmentLink.cs | 34 +++- .../Officers/OfficerIdentification.cs | 10 +- .../Response/Officers/OfficerLinks.cs | 5 +- .../Response/Officers/OfficerRole.cs | 99 ----------- .../Response/Officers/Officers.cs | 19 ++- .../Response/Officers/OfficersListLinks.cs | 10 ++ .../UriBuilders/IOfficersUriBuilder.cs | 2 +- .../UriBuilders/OfficersUriBuilder.cs | 37 ++++- src/CompaniesHouse/enum-map.txt | 2 + .../OfficerByAppointmentSchemaTests.cs | 28 ++++ .../Tests/OfficerTests/OfficersSchemaTests.cs | 54 ++++++ .../OfficersDeserializationScenarioTests.cs | 154 ++++++++++++++++++ ...niesHouseOfficersAppointmentClientTests.cs | 55 ++++++- .../OfficerBuilder.cs | 8 +- ...ompaniesHouseCompanyOfficersClientTests.cs | 138 +++++++++++++++- .../EnumerationMappings.cs | 2 + .../OfficersResourceBuilder.cs | 5 + .../Response/Officers/OfficerTests.cs | 46 ++++++ .../IdentificationTypeTests.cs | 100 ++++++++++++ .../ResponseValueTypes/OfficerRoleTests.cs | 128 +++++++++++++++ .../OfficersUriBuilderTests.cs | 10 +- 26 files changed, 916 insertions(+), 139 deletions(-) create mode 100644 src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs delete mode 100644 src/CompaniesHouse/Response/Officers/OfficerRole.cs create mode 100644 src/CompaniesHouse/Response/Officers/OfficersListLinks.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs create mode 100644 tests/CompaniesHouse.Tests/Response/Officers/OfficerTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/IdentificationTypeTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/OfficerRoleTests.cs diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index f459117..cc4c082 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -51,9 +51,9 @@ public CompaniesHouseClient(HttpClient httpClient) _companiesHouseRegisteredOfficeAddressClient = new CompaniesHouseRegisteredOfficeAddressClient(_httpClient, new RegisteredOfficeAddressUriBuilder()); _companiesHouseOfficerByAppointmentClient = new CompaniesHouseOfficerByByAppointmentClient(_httpClient, new OfficersAppointmentUriBuilder()); } - + public CompaniesHouseClient(ICompaniesHouseSettings settings) - :this(new HttpClientFactory(settings).CreateHttpClient()) + : this(new HttpClientFactory(settings).CreateHttpClient()) { } @@ -101,15 +101,23 @@ public CompaniesHouseClient(ICompaniesHouseSettings settings) { return _companiesHouseCompanyFilingHistoryClient.GetCompanyFilingHistoryAsync(companyNumber, startIndex, pageSize, cancellationToken); } - + public Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default) { return _companiesHouseCompanyFilingHistoryClient.GetFilingHistoryByTransactionAsync(companyNumber, transactionId, cancellationToken); } - public Task> GetOfficersAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)) + // Companies House defaults officer lists to 35 items, unlike several other paged endpoints. + public Task> GetOfficersAsync( + string companyNumber, + int startIndex = 0, + int pageSize = 35, + string? registerType = null, + bool? registerView = null, + string? orderBy = null, + CancellationToken cancellationToken = default(CancellationToken)) { - return _companiesHouseOfficersClient.GetOfficersAsync(companyNumber, startIndex, pageSize, cancellationToken); + return _companiesHouseOfficersClient.GetOfficersAsync(companyNumber, startIndex, pageSize, registerType, registerView, orderBy, cancellationToken); } public Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) @@ -130,7 +138,7 @@ public Task> GetFilingHistoryByT public Task> GetChargesListAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default) { - return _companiesHouseChargesClient.GetChargesListAsync(companyNumber,startIndex, pageSize, cancellationToken); + return _companiesHouseChargesClient.GetChargesListAsync(companyNumber, startIndex, pageSize, cancellationToken); } public Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken = default) diff --git a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs index 552b590..b282081 100644 --- a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs +++ b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs @@ -19,9 +19,16 @@ public CompaniesHouseOfficersClient(HttpClient httpClient, IOfficersUriBuilder o _officersUriBuilder = officersUriBuilder; } - public async Task> GetOfficersAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetOfficersAsync( + string companyNumber, + int startIndex, + int pageSize, + string? registerType = null, + bool? registerView = null, + string? orderBy = null, + CancellationToken cancellationToken = default(CancellationToken)) { - var requestUri = _officersUriBuilder.Build(companyNumber, startIndex, pageSize); + var requestUri = _officersUriBuilder.Build(companyNumber, startIndex, pageSize, registerType, registerView, orderBy); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); diff --git a/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs b/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs index f8c6316..b501f3e 100644 --- a/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs @@ -6,6 +6,14 @@ namespace CompaniesHouse { public interface ICompaniesHouseOfficersClient { - Task> GetOfficersAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)); + // Companies House defaults officer lists to 35 items, unlike several other paged endpoints. + Task> GetOfficersAsync( + string companyNumber, + int startIndex = 0, + int pageSize = 35, + string? registerType = null, + bool? registerView = null, + string? orderBy = null, + CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs b/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs new file mode 100644 index 0000000..90176ad --- /dev/null +++ b/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs @@ -0,0 +1,30 @@ +using System; +using System.Text.Json.Serialization; +using CompaniesHouse.JsonConverters; + +namespace CompaniesHouse.Response.Officers +{ + public class IdentityVerificationDetails + { + [JsonPropertyName("anti_money_laundering_supervisory_bodies")] + public string[]? AntiMoneyLaunderingSupervisoryBodies { get; set; } + + [JsonPropertyName("appointment_verification_end_on")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? AppointmentVerificationEndOn { get; set; } + + [JsonPropertyName("appointment_verification_start_on")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? AppointmentVerificationStartOn { get; set; } + + [JsonPropertyName("authorised_corporate_service_provider_name")] + public string? AuthorisedCorporateServiceProviderName { get; set; } + + [JsonPropertyName("identity_verified_on")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? IdentityVerifiedOn { get; set; } + + [JsonPropertyName("preferred_name")] + public string? PreferredName { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Officers/Officer.cs b/src/CompaniesHouse/Response/Officers/Officer.cs index aa74b15..c687365 100644 --- a/src/CompaniesHouse/Response/Officers/Officer.cs +++ b/src/CompaniesHouse/Response/Officers/Officer.cs @@ -1,44 +1,64 @@ using System; using System.Text.Json.Serialization; +using CompaniesHouse.JsonConverters; namespace CompaniesHouse.Response.Officers { public class Officer { + [JsonPropertyName("etag")] + public string? ETag { get; set; } + [JsonPropertyName("appointed_on")] public DateTime? AppointedOn { get; set; } + [JsonPropertyName("appointed_before")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? AppointedBefore { get; set; } + [JsonPropertyName("resigned_on")] public DateTime? ResignedOn { get; set; } [JsonPropertyName("date_of_birth")] - public OfficerDateOfBirth DateOfBirth { get; set; } + public OfficerDateOfBirth? DateOfBirth { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("officer_role")] public OfficerRole OfficerRole { get; set; } [JsonPropertyName("nationality")] - public string Nationality { get; set; } + public string? Nationality { get; set; } [JsonPropertyName("occupation")] - public string Occupation { get; set; } + public string? Occupation { get; set; } [JsonPropertyName("address")] - public Address Address { get; set; } + public Address? Address { get; set; } [JsonPropertyName("country_of_residence")] - public string CountryOfResidence { get; set; } + public string? CountryOfResidence { get; set; } [JsonPropertyName("former_names")] - public OfficerFormerName[] FormerNames { get; set; } + public OfficerFormerName[]? FormerNames { get; set; } [JsonPropertyName("identification")] - public OfficerIdentification Identification { get; set; } + public OfficerIdentification? Identification { get; set; } [JsonPropertyName("links")] - public OfficerLinks Links { get; set; } + public OfficerLinks? Links { get; set; } + + [JsonPropertyName("person_number")] + public string? PersonNumber { get; set; } + + [JsonPropertyName("is_pre_1992_appointment")] + public bool? IsPre1992Appointment { get; set; } + + [JsonPropertyName("identity_verification_details")] + public IdentityVerificationDetails? IdentityVerificationDetails { get; set; } + + [JsonIgnore] + public string? OfficerId => Links?.Officer?.OfficerId; } } diff --git a/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs b/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs index accdce4..5dd2261 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs @@ -1,3 +1,4 @@ +using System; using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers @@ -5,8 +6,37 @@ namespace CompaniesHouse.Response.Officers public class OfficerAppointmentLink { [JsonPropertyName("appointments")] - public string AppointmentsResource { get; set; } + public string? AppointmentsResource { get; set; } - public string OfficerId => AppointmentsResource?.Split('/')[2]; + [JsonIgnore] + public string? OfficerId + { + get + { + if (string.IsNullOrWhiteSpace(AppointmentsResource)) + { + return null; + } + + const string prefix = "/officers/"; + const string suffix = "/appointments"; + + var officerStart = AppointmentsResource.IndexOf(prefix, StringComparison.Ordinal); + if (officerStart < 0) + { + return null; + } + + officerStart += prefix.Length; + + var officerEnd = AppointmentsResource.IndexOf(suffix, officerStart, StringComparison.Ordinal); + if (officerEnd <= officerStart) + { + return null; + } + + return AppointmentsResource.Substring(officerStart, officerEnd - officerStart); + } + } } } diff --git a/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs b/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs index dd858ef..b5b6dd9 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerIdentification.cs @@ -5,18 +5,18 @@ namespace CompaniesHouse.Response.Officers public class OfficerIdentification { [JsonPropertyName("identification_type")] - public string IdentificationType { get; set; } + public IdentificationType IdentificationType { get; set; } [JsonPropertyName("legal_authority")] - public string LegalAuthority { get; set; } + public string? LegalAuthority { get; set; } [JsonPropertyName("legal_form")] - public string LegalForm { get; set; } + public string? LegalForm { get; set; } [JsonPropertyName("place_registered")] - public string PlaceRegistered { get; set; } + public string? PlaceRegistered { get; set; } [JsonPropertyName("registration_number")] - public string RegistrationNumber { get; set; } + public string? RegistrationNumber { get; set; } } } diff --git a/src/CompaniesHouse/Response/Officers/OfficerLinks.cs b/src/CompaniesHouse/Response/Officers/OfficerLinks.cs index 9776420..e0fa606 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerLinks.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerLinks.cs @@ -4,7 +4,10 @@ namespace CompaniesHouse.Response.Officers { public class OfficerLinks { + [JsonPropertyName("self")] + public string? Self { get; set; } + [JsonPropertyName("officer")] - public OfficerAppointmentLink Officer { get; set; } + public OfficerAppointmentLink? Officer { get; set; } } } diff --git a/src/CompaniesHouse/Response/Officers/OfficerRole.cs b/src/CompaniesHouse/Response/Officers/OfficerRole.cs deleted file mode 100644 index 0cd8950..0000000 --- a/src/CompaniesHouse/Response/Officers/OfficerRole.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.Officers -{ - public enum OfficerRole - { - None = 0, - - [EnumMember(Value = "cic-manager")] - CicManager, - - [EnumMember(Value = "corporate-director")] - CorporateDirector, - - [EnumMember(Value = "corporate-llp-designated-member")] - CorporateLlpDesignatedMember, - - [EnumMember(Value = "corporate-llp-member")] - CorporateLlpMember, - - [EnumMember(Value = "corporate-manager-of-an-eeig")] - CorporateManagerOfAnEeig, - - [EnumMember(Value = "corporate-managing-officer")] - CorporateManagingOfficer, - - [EnumMember(Value = "corporate-member-of-a-management-organ")] - CorporateMemberOfAManagementOrgan, - - [EnumMember(Value = "corporate-member-of-a-supervisory-organ")] - CorporateMemberOfASupervisoryOrgan, - - [EnumMember(Value = "corporate-member-of-an-administrative-organ")] - CorporateMemberOfAnAdministrativeOrgan, - - [EnumMember(Value = "corporate-nominee-director")] - CorporateNomineeDirector, - - [EnumMember(Value = "corporate-nominee-secretary")] - CorporateNomineeSecretary, - - [EnumMember(Value = "corporate-secretary")] - CorporateSecretary, - - [EnumMember(Value = "director")] - Director, - - [EnumMember(Value = "general-partner-in-a-limited-partnership")] - GeneralPartnerInALimitedPartnership, - - [EnumMember(Value = "judicial-factor")] - JudicialFactor, - - [EnumMember(Value = "limited-partner-in-a-limited-partnership")] - LimitedPartnerInALimitedPartnership, - - [EnumMember(Value = "llp-designated-member")] - LlpDesignatedMember, - - [EnumMember(Value = "llp-member")] - LlpMember, - - [EnumMember(Value = "manager-of-an-eeig")] - ManagerOfAnEeig, - - [EnumMember(Value = "managing-officer")] - ManagingOfficer, - - [EnumMember(Value = "member-of-a-management-organ")] - MemberOfAManagementOrgan, - - [EnumMember(Value = "member-of-a-supervisory-organ")] - MemberOfASupervisoryOrgan, - - [EnumMember(Value = "member-of-an-administrative-organ")] - MemberOfAnAdministrativeOrgan, - - [EnumMember(Value = "nominee-director")] - NomineeDirector, - - [EnumMember(Value = "nominee-secretary")] - NomineeSecretary, - - [EnumMember(Value = "person-authorised-to-accept")] - PersonAuthorisedToAccept, - - [EnumMember(Value = "person-authorised-to-represent")] - PersonAuthorisedToRepresent, - - [EnumMember(Value = "person-authorised-to-represent-and-accept")] - PersonAuthorisedToRepresentAndAccept, - - [EnumMember(Value = "receiver-and-manager")] - ReceiverAndManager, - - [EnumMember(Value = "secretary")] - Secretary - } -} diff --git a/src/CompaniesHouse/Response/Officers/Officers.cs b/src/CompaniesHouse/Response/Officers/Officers.cs index 8507be2..18dfc09 100644 --- a/src/CompaniesHouse/Response/Officers/Officers.cs +++ b/src/CompaniesHouse/Response/Officers/Officers.cs @@ -4,18 +4,33 @@ namespace CompaniesHouse.Response.Officers { public class Officers { + [JsonPropertyName("etag")] + public string? ETag { get; set; } + [JsonPropertyName("active_count")] public int? ActiveCount { get; set; } + [JsonPropertyName("inactive_count")] + public int? InactiveCount { get; set; } + [JsonPropertyName("items")] public Officer[] Items { get; set; } + [JsonPropertyName("items_per_page")] + public int? ItemsPerPage { get; set; } + + [JsonPropertyName("kind")] + public string? Kind { get; set; } + + [JsonPropertyName("links")] + public OfficersListLinks? Links { get; set; } + [JsonPropertyName("resigned_count")] public int? ResignedCount { get; set; } - + [JsonPropertyName("total_results")] public int TotalResults { get; set; } - + [JsonPropertyName("start_index")] public int StartIndex { get; set; } } diff --git a/src/CompaniesHouse/Response/Officers/OfficersListLinks.cs b/src/CompaniesHouse/Response/Officers/OfficersListLinks.cs new file mode 100644 index 0000000..65b2677 --- /dev/null +++ b/src/CompaniesHouse/Response/Officers/OfficersListLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Officers +{ + public class OfficersListLinks + { + [JsonPropertyName("self")] + public string? Self { get; set; } + } +} diff --git a/src/CompaniesHouse/UriBuilders/IOfficersUriBuilder.cs b/src/CompaniesHouse/UriBuilders/IOfficersUriBuilder.cs index 51644cf..625c11d 100644 --- a/src/CompaniesHouse/UriBuilders/IOfficersUriBuilder.cs +++ b/src/CompaniesHouse/UriBuilders/IOfficersUriBuilder.cs @@ -4,6 +4,6 @@ namespace CompaniesHouse.UriBuilders { public interface IOfficersUriBuilder { - Uri Build(string companyNumber, int startIndex, int pageSize); + Uri Build(string companyNumber, int startIndex, int pageSize, string? registerType, bool? registerView, string? orderBy); } } \ No newline at end of file diff --git a/src/CompaniesHouse/UriBuilders/OfficersUriBuilder.cs b/src/CompaniesHouse/UriBuilders/OfficersUriBuilder.cs index 321c9a5..793cea3 100644 --- a/src/CompaniesHouse/UriBuilders/OfficersUriBuilder.cs +++ b/src/CompaniesHouse/UriBuilders/OfficersUriBuilder.cs @@ -1,14 +1,47 @@ using System; +using System.Collections.Generic; +using System.Globalization; namespace CompaniesHouse.UriBuilders { public class OfficersUriBuilder : IOfficersUriBuilder { - public Uri Build(string companyNumber, int startIndex, int pageSize) + public Uri Build(string companyNumber, int startIndex, int pageSize, string? registerType, bool? registerView, string? orderBy) { - var path = $"company/{Uri.EscapeDataString(companyNumber)}/officers?items_per_page={pageSize}&start_index={startIndex}"; + var queryParts = new List + { + "items_per_page=" + pageSize.ToString(CultureInfo.InvariantCulture), + "start_index=" + startIndex.ToString(CultureInfo.InvariantCulture), + }; + + AddString("register_type", registerType); + AddBool("register_view", registerView); + AddString("order_by", orderBy); + + var query = "?" + string.Join("&", queryParts); + var path = $"company/{Uri.EscapeDataString(companyNumber)}/officers{query}"; return new Uri(path, UriKind.Relative); + + void AddString(string key, string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return; + } + + queryParts.Add(key + "=" + Uri.EscapeDataString(value)); + } + + void AddBool(string key, bool? value) + { + if (!value.HasValue) + { + return; + } + + queryParts.Add(key + "=" + value.Value.ToString().ToLowerInvariant()); + } } } } diff --git a/src/CompaniesHouse/enum-map.txt b/src/CompaniesHouse/enum-map.txt index 53a5b81..d2857d9 100644 --- a/src/CompaniesHouse/enum-map.txt +++ b/src/CompaniesHouse/enum-map.txt @@ -14,3 +14,5 @@ company_status_detail|CompaniesHouse.Response|CompanyStatusDetail|true jurisdiction|CompaniesHouse.Response.CompanyProfile|Jurisdiction|true foreign_account_type|CompaniesHouse.Response.CompanyProfile|ForeignAccountType|true terms_of_account_publication|CompaniesHouse.Response.CompanyProfile|TermsOfAccountPublication|true +officer_role|CompaniesHouse.Response.Officers|OfficerRole|true +identification_type|CompaniesHouse.Response.Officers|IdentificationType|true diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs new file mode 100644 index 0000000..1fe73ee --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using CompaniesHouse.Response.Officers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests +{ + public class OfficerByAppointmentSchemaTests + { + [Fact] + public async Task GetOfficerByAppointmentIdAsync_DeserializesTheSharedOfficerShape() + { + var client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + + var result = await client.GetOfficerByAppointmentIdAsync("00445790", "gE7Pw_lx4HWJvqSfwqudfusS9Ig"); + + result.Data.ShouldNotBeNull(); + result.Data.OfficerRole.ShouldBe(OfficerRole.Director); + result.Data.PersonNumber.ShouldBe("248450070003"); + result.Data.IsPre1992Appointment.ShouldBe(false); + result.Data.OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); + result.Data.IdentityVerificationDetails.ShouldNotBeNull(); + result.Data.IdentityVerificationDetails.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); + result.Data.IdentityVerificationDetails.PreferredName.ShouldBe("Melissa Bethell"); + } + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs new file mode 100644 index 0000000..4e6225f --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using CompaniesHouse.Response.Officers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests +{ + public class OfficersSchemaTests + { + [Fact] + public async Task GetOfficersAsync_DeserializesConfirmedTescoFields() + { + var client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + + var result = await client.GetOfficersAsync("00445790", pageSize: 100); + + result.Data.ShouldNotBeNull(); + result.Data.TotalResults.ShouldBeGreaterThan(0); + result.Data.ItemsPerPage.ShouldBe(100); + result.Data.Kind.ShouldBe("officer-list"); + result.Data.Links?.Self.ShouldBe("/company/00445790/officers"); + + var melissaBethell = result.Data.Items.Single(x => x.PersonNumber == "248450070003"); + melissaBethell.OfficerRole.ShouldBe(OfficerRole.Director); + melissaBethell.OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); + melissaBethell.IdentityVerificationDetails.ShouldNotBeNull(); + melissaBethell.IdentityVerificationDetails.AppointmentVerificationStartOn.ShouldBe(new DateTime(2026, 07, 01)); + melissaBethell.IdentityVerificationDetails.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); + } + + [Fact] + public async Task GetOfficersAsync_DeserializesCorporateIdentificationAndAppointedBefore() + { + var client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + + var tescoResult = await client.GetOfficersAsync("00445790", pageSize: 100); + var informaResult = await client.GetOfficersAsync("03610056", pageSize: 100); + + var pre1992Officer = tescoResult.Data.Items.Single( + x => x.Links?.Self == "/company/00445790/appointments/MyVEHTFfF_vmr04twNlBb1DmQFY"); + pre1992Officer.AppointedBefore.ShouldBe(new DateTime(1991, 06, 07)); + pre1992Officer.IsPre1992Appointment.ShouldBe(true); + + var corporateSecretary = informaResult.Data.Items.Single( + x => x.Links?.Self == "/company/03610056/appointments/4F3DS_j7LgOTlBEE2xIfmM7wGhs"); + corporateSecretary.OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); + corporateSecretary.Identification.ShouldNotBeNull(); + corporateSecretary.Identification.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); + corporateSecretary.Identification.RegistrationNumber.ShouldBe("3849195"); + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs new file mode 100644 index 0000000..e9f8b8d --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs @@ -0,0 +1,154 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response.Officers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class OfficersDeserializationScenarioTests + { + [Fact] + public void OfficerList_DeserializesConfirmedListEnvelopeAndIdentityVerificationDetails() + { + var officers = JsonSerializer.Deserialize(OfficerListJson, CompaniesHouseJsonSerializerOptions.Default); + + officers.ShouldNotBeNull(); + officers.ETag.ShouldBe("566e7c60f7de5940734cef04ea94006c91cdbb4a"); + officers.ItemsPerPage.ShouldBe(5); + officers.Kind.ShouldBe("officer-list"); + officers.Links?.Self.ShouldBe("/company/00445790/officers"); + officers.TotalResults.ShouldBe(74); + officers.Items.Length.ShouldBe(2); + officers.Items[1].OfficerRole.ShouldBe(OfficerRole.Director); + officers.Items[1].PersonNumber.ShouldBe("248450070003"); + officers.Items[1].OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); + officers.Items[1].IdentityVerificationDetails?.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); + } + + [Fact] + public void OfficerAppointment_DeserializesUsingTheSharedOfficerShape() + { + var officer = JsonSerializer.Deserialize(OfficerAppointmentJson, CompaniesHouseJsonSerializerOptions.Default); + + officer.ShouldNotBeNull(); + officer.ETag.ShouldBe("5ad20f5a7c2d801107af20d5f413ab70bc0a3175"); + officer.OfficerRole.ShouldBe(OfficerRole.Director); + officer.PersonNumber.ShouldBe("248450070003"); + officer.IsPre1992Appointment.ShouldBe(false); + officer.OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); + officer.IdentityVerificationDetails?.PreferredName.ShouldBe("Melissa Bethell"); + } + + [Fact] + public void CorporateOfficerList_DeserializesIdentificationType() + { + var officers = JsonSerializer.Deserialize(CorporateOfficerListJson, CompaniesHouseJsonSerializerOptions.Default); + + officers.ShouldNotBeNull(); + officers.Items.Length.ShouldBe(1); + officers.Items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); + officers.Items[0].Identification.ShouldNotBeNull(); + officers.Items[0].Identification.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); + officers.Items[0].Identification.RegistrationNumber.ShouldBe("3849195"); + } + + private const string OfficerListJson = """ + { + "active_count": 11, + "etag": "566e7c60f7de5940734cef04ea94006c91cdbb4a", + "items": [ + { + "etag": "566e7c60f7de5940734cef04ea94006c91cdbb4a", + "address": {"address_line_1": "Tesco House, Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA"}, + "appointed_on": "2025-04-14", + "is_pre_1992_appointment": false, + "links": {"self": "/company/00445790/appointments/lnfNdAqKHBZCL2akA7SXLkkA8KI", "officer": {"appointments": "/officers/uJ_F_UGCbPiYELlJ_fHc-J_goqo/appointments"}}, + "name": "TAYLOR, Christopher Jon", + "officer_role": "secretary", + "person_number": "334718260001" + }, + { + "etag": "5ad20f5a7c2d801107af20d5f413ab70bc0a3175", + "address": {"address_line_1": "Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA", "premises": "Tesco House"}, + "appointed_on": "2018-09-24", + "is_pre_1992_appointment": false, + "country_of_residence": "United Kingdom", + "date_of_birth": {"month": 9, "year": 1974}, + "links": {"self": "/company/00445790/appointments/gE7Pw_lx4HWJvqSfwqudfusS9Ig", "officer": {"appointments": "/officers/aqrS_F-2zIvSaMNtl1opqDV4-w0/appointments"}}, + "name": "BETHELL, Melissa", + "nationality": "British", + "officer_role": "director", + "person_number": "248450070003", + "identity_verification_details": { + "anti_money_laundering_supervisory_bodies": ["Faculty Office of the Archbishop of Canterbury (FO)"], + "appointment_verification_end_on": "9999-12-31", + "appointment_verification_start_on": "2026-07-01", + "authorised_corporate_service_provider_name": "DE PINNA LLP ACSP", + "identity_verified_on": "2025-07-29", + "preferred_name": "Melissa Bethell" + } + } + ], + "items_per_page": 5, + "kind": "officer-list", + "links": {"self": "/company/00445790/officers"}, + "resigned_count": 63, + "inactive_count": 0, + "start_index": 0, + "total_results": 74 + } + """; + + private const string OfficerAppointmentJson = """ + { + "etag": "5ad20f5a7c2d801107af20d5f413ab70bc0a3175", + "address": {"address_line_1": "Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA", "premises": "Tesco House"}, + "appointed_on": "2018-09-24", + "is_pre_1992_appointment": false, + "country_of_residence": "United Kingdom", + "date_of_birth": {"month": 9, "year": 1974}, + "links": {"self": "/company/00445790/appointments/gE7Pw_lx4HWJvqSfwqudfusS9Ig", "officer": {"appointments": "/officers/aqrS_F-2zIvSaMNtl1opqDV4-w0/appointments"}}, + "name": "BETHELL, Melissa", + "nationality": "British", + "officer_role": "director", + "person_number": "248450070003", + "identity_verification_details": { + "anti_money_laundering_supervisory_bodies": ["Faculty Office of the Archbishop of Canterbury (FO)"], + "appointment_verification_end_on": "9999-12-31", + "appointment_verification_start_on": "2026-07-01", + "authorised_corporate_service_provider_name": "DE PINNA LLP ACSP", + "identity_verified_on": "2025-07-29", + "preferred_name": "Melissa Bethell" + } + } + """; + + private const string CorporateOfficerListJson = """ + { + "active_count": 1, + "etag": "b0f14fcd8f8a9cfd6789dbdcbdb2c08b7fbf84e4", + "items": [ + { + "etag": "b0f14fcd8f8a9cfd6789dbdcbdb2c08b7fbf84e4", + "address": {"address_line_1": "Howick Place", "country": "United Kingdom", "locality": "London", "postal_code": "SW1P 1WG", "premises": "5"}, + "appointed_on": "2021-12-31", + "is_pre_1992_appointment": false, + "links": {"self": "/company/03610056/appointments/4F3DS_j7LgOTlBEE2xIfmM7wGhs", "officer": {"appointments": "/officers/YwIOmduyS6PW5axJgQQrsTGyRD0/appointments"}}, + "name": "INFORMA COSEC LIMITED", + "officer_role": "corporate-secretary", + "identification": {"identification_type": "uk-limited-company", "registration_number": "3849195"}, + "person_number": "279172060001" + } + ], + "items_per_page": 1, + "kind": "officer-list", + "links": {"self": "/company/03610056/officers"}, + "resigned_count": 0, + "inactive_count": 0, + "start_index": 0, + "total_results": 1 + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs index 18b6f79..8e276ee 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using CompaniesHouse.Response.Officers; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; using Moq; @@ -20,17 +21,17 @@ public async Task GivenACompaniesHouseOffficerAppointmentClient_WhenGettingAnOff { var officersAppointment = OfficerBuilder.Build(testCase); var resource = OfficersResourceBuilder.CreateSingle(officersAppointment); - + var uri = new Uri("https://wibble.com/company/wobble/registered-office-address"); HttpMessageHandler handler = new StubHttpMessageHandler(uri, resource); var uriBuilder = new Mock(); uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny())).Returns(uri); - + var client = new CompaniesHouseOfficerByByAppointmentClient(new HttpClient(handler), uriBuilder.Object); var result = await client.GetOfficerByAppointmentIdAsync("abc", "1"); - + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, officersAppointment); } @@ -41,6 +42,54 @@ public static IEnumerable TestCases() => OfficerRole = x }) .Select(testCase => new object[] { testCase }); + + [Fact] + public async Task GivenARealCapturedAppointment_WhenGettingAnOfficerByAppointmentId_ThenListShapeFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/company/00445790/appointments/gE7Pw_lx4HWJvqSfwqudfusS9Ig"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, RealAppointmentJson); + + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny())).Returns(uri); + + var client = new CompaniesHouseOfficerByByAppointmentClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetOfficerByAppointmentIdAsync("00445790", "gE7Pw_lx4HWJvqSfwqudfusS9Ig"); + + result.Data.ShouldNotBeNull(); + result.Data.ETag.ShouldBe("5ad20f5a7c2d801107af20d5f413ab70bc0a3175"); + result.Data.OfficerRole.ShouldBe(OfficerRole.Director); + result.Data.PersonNumber.ShouldBe("248450070003"); + result.Data.IsPre1992Appointment.ShouldBe(false); + result.Data.Links?.Self.ShouldBe("/company/00445790/appointments/gE7Pw_lx4HWJvqSfwqudfusS9Ig"); + result.Data.OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); + result.Data.IdentityVerificationDetails.ShouldNotBeNull(); + result.Data.IdentityVerificationDetails.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); + result.Data.IdentityVerificationDetails.PreferredName.ShouldBe("Melissa Bethell"); + } + + private const string RealAppointmentJson = """ + { + "etag": "5ad20f5a7c2d801107af20d5f413ab70bc0a3175", + "address": {"address_line_1": "Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA", "premises": "Tesco House"}, + "appointed_on": "2018-09-24", + "is_pre_1992_appointment": false, + "country_of_residence": "United Kingdom", + "date_of_birth": {"month": 9, "year": 1974}, + "links": {"self": "/company/00445790/appointments/gE7Pw_lx4HWJvqSfwqudfusS9Ig", "officer": {"appointments": "/officers/aqrS_F-2zIvSaMNtl1opqDV4-w0/appointments"}}, + "name": "BETHELL, Melissa", + "nationality": "British", + "officer_role": "director", + "person_number": "248450070003", + "identity_verification_details": { + "anti_money_laundering_supervisory_bodies": ["Faculty Office of the Archbishop of Canterbury (FO)"], + "appointment_verification_end_on": "9999-12-31", + "appointment_verification_start_on": "2026-07-01", + "authorised_corporate_service_provider_name": "DE PINNA LLP ACSP", + "identity_verified_on": "2025-07-29", + "preferred_name": "Melissa Bethell" + } + } + """; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/OfficerBuilder.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/OfficerBuilder.cs index bb10c31..5fb79ba 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/OfficerBuilder.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/OfficerBuilder.cs @@ -10,16 +10,16 @@ public static ResourceBuilders.Officer Build(CompaniesHouseOfficerByAppointmentT var fixture = new Fixture(); fixture.Customizations.Add(new UniversalDateSpecimenBuilder(x => x.AppointedOn)); fixture.Customizations.Add(new UniversalDateSpecimenBuilder(x => x.ResignedOn)); - + return fixture .Build() - .With(x => x.Links, + .With(x => x.Links, fixture .Build() - .With(x => x.Officer, + .With(x => x.Officer, fixture .Build() - .With(x => x.AppointmentsResource, "/officer/xyz/appointments") + .With(x => x.AppointmentsResource, "/officers/xyz/appointments") .Create()) .Create()) .With(x => x.OfficerRole, testCase.OfficerRole) diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs index 97c526c..fd64cd2 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs @@ -1,6 +1,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using CompaniesHouse.Response.Officers; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; using Moq; @@ -28,7 +29,7 @@ public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyPr HttpMessageHandler handler = new StubHttpMessageHandler(uri, resource); var uriBuilder = new Mock(); - uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())) + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), null, null, null)) .Returns(uri); _client = new CompaniesHouseOfficersClient(new HttpClient(handler), uriBuilder.Object); @@ -37,5 +38,140 @@ public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyPr EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _officers); } + + [Fact] + public async Task GivenARealCapturedOfficerList_WhenGettingOfficers_ThenMissingLiveFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/company/00445790/officers"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, RealOfficerListJson); + + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(uri); + + var client = new CompaniesHouseOfficersClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetOfficersAsync("00445790", 0, 5); + + result.Data.ShouldNotBeNull(); + result.Data.ETag.ShouldBe("566e7c60f7de5940734cef04ea94006c91cdbb4a"); + result.Data.ItemsPerPage.ShouldBe(5); + result.Data.Kind.ShouldBe("officer-list"); + result.Data.InactiveCount.ShouldBe(0); + result.Data.Links?.Self.ShouldBe("/company/00445790/officers"); + result.Data.TotalResults.ShouldBe(74); + result.Data.Items.Length.ShouldBe(2); + + var officer = result.Data.Items[1]; + officer.ETag.ShouldBe("5ad20f5a7c2d801107af20d5f413ab70bc0a3175"); + officer.PersonNumber.ShouldBe("248450070003"); + officer.IsPre1992Appointment.ShouldBe(false); + officer.OfficerRole.ShouldBe(OfficerRole.Director); + officer.OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); + officer.Links?.Self.ShouldBe("/company/00445790/appointments/gE7Pw_lx4HWJvqSfwqudfusS9Ig"); + officer.IdentityVerificationDetails.ShouldNotBeNull(); + officer.IdentityVerificationDetails.AntiMoneyLaunderingSupervisoryBodies.ShouldBe( + ["Faculty Office of the Archbishop of Canterbury (FO)"]); + officer.IdentityVerificationDetails.AppointmentVerificationStartOn.ShouldBe(new DateTime(2026, 07, 01)); + officer.IdentityVerificationDetails.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); + officer.IdentityVerificationDetails.AuthorisedCorporateServiceProviderName.ShouldBe("DE PINNA LLP ACSP"); + officer.IdentityVerificationDetails.IdentityVerifiedOn.ShouldBe(new DateTime(2025, 07, 29)); + officer.IdentityVerificationDetails.PreferredName.ShouldBe("Melissa Bethell"); + } + + [Fact] + public async Task GivenARealCapturedCorporateOfficerList_WhenGettingOfficers_ThenIdentificationTypeDeserializes() + { + var uri = new Uri("https://wibble.com/company/03610056/officers"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, RealCorporateOfficerListJson); + + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(uri); + + var client = new CompaniesHouseOfficersClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetOfficersAsync("03610056", 0, 1); + + result.Data.ShouldNotBeNull(); + result.Data.Items.Length.ShouldBe(1); + result.Data.Items[0].Identification.ShouldNotBeNull(); + result.Data.Items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); + result.Data.Items[0].Identification.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); + result.Data.Items[0].Identification.RegistrationNumber.ShouldBe("3849195"); + result.Data.Items[0].OfficerId.ShouldBe("YwIOmduyS6PW5axJgQQrsTGyRD0"); + } + + private const string RealOfficerListJson = """ + { + "active_count": 11, + "etag": "566e7c60f7de5940734cef04ea94006c91cdbb4a", + "items": [ + { + "etag": "566e7c60f7de5940734cef04ea94006c91cdbb4a", + "address": {"address_line_1": "Tesco House, Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA"}, + "appointed_on": "2025-04-14", + "is_pre_1992_appointment": false, + "links": {"self": "/company/00445790/appointments/lnfNdAqKHBZCL2akA7SXLkkA8KI", "officer": {"appointments": "/officers/uJ_F_UGCbPiYELlJ_fHc-J_goqo/appointments"}}, + "name": "TAYLOR, Christopher Jon", + "officer_role": "secretary", + "person_number": "334718260001" + }, + { + "etag": "5ad20f5a7c2d801107af20d5f413ab70bc0a3175", + "address": {"address_line_1": "Shire Park", "address_line_2": "Kestrel Way", "country": "United Kingdom", "locality": "Welwyn Garden City", "postal_code": "AL7 1GA", "premises": "Tesco House"}, + "appointed_on": "2018-09-24", + "is_pre_1992_appointment": false, + "country_of_residence": "United Kingdom", + "date_of_birth": {"month": 9, "year": 1974}, + "links": {"self": "/company/00445790/appointments/gE7Pw_lx4HWJvqSfwqudfusS9Ig", "officer": {"appointments": "/officers/aqrS_F-2zIvSaMNtl1opqDV4-w0/appointments"}}, + "name": "BETHELL, Melissa", + "nationality": "British", + "officer_role": "director", + "person_number": "248450070003", + "identity_verification_details": { + "anti_money_laundering_supervisory_bodies": ["Faculty Office of the Archbishop of Canterbury (FO)"], + "appointment_verification_end_on": "9999-12-31", + "appointment_verification_start_on": "2026-07-01", + "authorised_corporate_service_provider_name": "DE PINNA LLP ACSP", + "identity_verified_on": "2025-07-29", + "preferred_name": "Melissa Bethell" + } + } + ], + "items_per_page": 5, + "kind": "officer-list", + "links": {"self": "/company/00445790/officers"}, + "resigned_count": 63, + "inactive_count": 0, + "start_index": 0, + "total_results": 74 + } + """; + + private const string RealCorporateOfficerListJson = """ + { + "active_count": 1, + "etag": "b0f14fcd8f8a9cfd6789dbdcbdb2c08b7fbf84e4", + "items": [ + { + "etag": "b0f14fcd8f8a9cfd6789dbdcbdb2c08b7fbf84e4", + "address": {"address_line_1": "Howick Place", "country": "United Kingdom", "locality": "London", "postal_code": "SW1P 1WG", "premises": "5"}, + "appointed_on": "2021-12-31", + "is_pre_1992_appointment": false, + "links": {"self": "/company/03610056/appointments/4F3DS_j7LgOTlBEE2xIfmM7wGhs", "officer": {"appointments": "/officers/YwIOmduyS6PW5axJgQQrsTGyRD0/appointments"}}, + "name": "INFORMA COSEC LIMITED", + "officer_role": "corporate-secretary", + "identification": {"identification_type": "uk-limited-company", "registration_number": "3849195"}, + "person_number": "279172060001" + } + ], + "items_per_page": 1, + "kind": "officer-list", + "links": {"self": "/company/03610056/officers"}, + "resigned_count": 0, + "inactive_count": 0, + "start_index": 0, + "total_results": 1 + } + """; } } diff --git a/tests/CompaniesHouse.Tests/EnumerationMappings.cs b/tests/CompaniesHouse.Tests/EnumerationMappings.cs index 7dac3c5..a175614 100644 --- a/tests/CompaniesHouse.Tests/EnumerationMappings.cs +++ b/tests/CompaniesHouse.Tests/EnumerationMappings.cs @@ -86,8 +86,10 @@ public static class EnumerationMappings {"corporate-secretary", OfficerRole.CorporateSecretary}, {"director", OfficerRole.Director}, {"general-partner-in-a-limited-partnership", OfficerRole.GeneralPartnerInALimitedPartnership}, + {"corporate-general-partner-in-a-limited-partnership", OfficerRole.CorporateGeneralPartnerInALimitedPartnership}, {"judicial-factor", OfficerRole.JudicialFactor}, {"limited-partner-in-a-limited-partnership", OfficerRole.LimitedPartnerInALimitedPartnership}, + {"corporate-limited-partner-in-a-limited-partnership", OfficerRole.CorporateLimitedPartnerInALimitedPartnership}, {"llp-designated-member", OfficerRole.LlpDesignatedMember}, {"llp-member", OfficerRole.LlpMember}, {"manager-of-an-eeig", OfficerRole.ManagerOfAnEeig}, diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs index 1f8ca67..c58de21 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs @@ -28,6 +28,10 @@ public string Create() public static string CreateSingle(Officer officer) { + var selfProperty = string.IsNullOrWhiteSpace(officer.Links?.Self) + ? string.Empty + : $@"""self"" : ""{officer.Links.Self}"","; + return $@" {{ ""appointed_on"" : ""{officer.AppointedOn.ToString("yyyy-MM-dd")}"", ""resigned_on"" : ""{officer.ResignedOn.ToString("yyyy-MM-dd")}"", @@ -37,6 +41,7 @@ public static string CreateSingle(Officer officer) ""year"" : {officer.DateOfBirth.Year} }}, ""links"" : {{ + {selfProperty} ""officer"" : {{ ""appointments"" : ""{officer.Links.Officer.AppointmentsResource}"" }} diff --git a/tests/CompaniesHouse.Tests/Response/Officers/OfficerTests.cs b/tests/CompaniesHouse.Tests/Response/Officers/OfficerTests.cs new file mode 100644 index 0000000..4cac414 --- /dev/null +++ b/tests/CompaniesHouse.Tests/Response/Officers/OfficerTests.cs @@ -0,0 +1,46 @@ +using System.Text.Json; +using CompaniesHouse.Response.Officers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.OfficerResponses +{ + public class OfficerTests + { + [Fact] + public void OfficerId_ExtractsTheOfficerIdFromARealisticAppointmentsLink() + { + var officer = new Officer + { + Links = new OfficerLinks + { + Officer = new OfficerAppointmentLink + { + AppointmentsResource = "/officers/uJ_F_UGCbPiYELlJ_fHc-J_goqo/appointments", + }, + }, + }; + + officer.OfficerId.ShouldBe("uJ_F_UGCbPiYELlJ_fHc-J_goqo"); + } + + [Fact] + public void OfficerId_IsIgnoredDuringSerialization() + { + var officer = new Officer + { + Links = new OfficerLinks + { + Officer = new OfficerAppointmentLink + { + AppointmentsResource = "/officers/uJ_F_UGCbPiYELlJ_fHc-J_goqo/appointments", + }, + }, + }; + + var json = JsonSerializer.Serialize(officer, CompaniesHouseJsonSerializerOptions.Default); + + json.ShouldNotContain("officer_id"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/IdentificationTypeTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/IdentificationTypeTests.cs new file mode 100644 index 0000000..9c0e52b --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/IdentificationTypeTests.cs @@ -0,0 +1,100 @@ +using System.Text.Json; +using CompaniesHouse.Response.Officers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class IdentificationTypeTests + { + [Theory] + [InlineData("non-eea")] + [InlineData("eea")] + [InlineData("uk-limited-company")] + [InlineData("other-corporate-body-or-firm")] + [InlineData("registered-overseas-entity-corporate-managing-officer")] + [InlineData("limited-partnership-corporate-partner")] + public void Deserializing_KnownValue_RoundTripsAndIsKnown(string value) + { + var json = $"\"{value}\""; + + var identificationType = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + identificationType.Value.ShouldBe(value); + identificationType.IsKnown.ShouldBeTrue(); + identificationType.HasValue.ShouldBeTrue(); + + var reserialized = JsonSerializer.Serialize(identificationType, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe(json); + } + + [Fact] + public void Deserializing_UnknownValue_DoesNotThrowAndPreservesRawValue() + { + const string json = "\"some-brand-new-identification-type\""; + + var identificationType = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + identificationType.Value.ShouldBe("some-brand-new-identification-type"); + identificationType.IsKnown.ShouldBeFalse(); + identificationType.HasValue.ShouldBeTrue(); + identificationType.Description.ShouldBeNull(); + + var reserialized = JsonSerializer.Serialize(identificationType, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe(json); + } + + [Fact] + public void Deserializing_Null_ReturnsDefaultWithNoValue() + { + var identificationType = JsonSerializer.Deserialize("null", CompaniesHouseJsonSerializerOptions.Default); + + identificationType.ShouldBe(default); + identificationType.HasValue.ShouldBeFalse(); + identificationType.Value.ShouldBe(string.Empty); + identificationType.IsKnown.ShouldBeFalse(); + + var reserialized = JsonSerializer.Serialize(identificationType, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe("null"); + } + + [Fact] + public void KnownValues_CompareEqualToStaticMembers() + { + new IdentificationType("uk-limited-company").ShouldBe(IdentificationType.UkLimitedCompany); + new IdentificationType("non-eea").ShouldBe(IdentificationType.NonEea); + + (IdentificationType.UkLimitedCompany == new IdentificationType("uk-limited-company")).ShouldBeTrue(); + (IdentificationType.UkLimitedCompany == IdentificationType.Eea).ShouldBeFalse(); + } + + [Fact] + public void SwitchExpression_MatchesOnKnownValues() + { + var identificationType = new IdentificationType("uk-limited-company"); + + var description = identificationType switch + { + _ when identificationType == IdentificationType.UkLimitedCompany => "is a UK company", + _ when identificationType == IdentificationType.NonEea => "is non-EEA", + _ => "something else", + }; + + description.ShouldBe("is a UK company"); + } + + [Fact] + public void Description_ReturnsFriendlyTextForKnownValues() + { + IdentificationType.NonEea.Description.ShouldBe("Non European Economic Area"); + IdentificationType.RegisteredOverseasEntityCorporateManagingOfficer.Description.ShouldBe("Corporate managing officer"); + } + + [Fact] + public void ToString_ReturnsRawValue() + { + IdentificationType.UkLimitedCompany.ToString().ShouldBe("uk-limited-company"); + default(IdentificationType).ToString().ShouldBe(string.Empty); + } + } +} diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/OfficerRoleTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/OfficerRoleTests.cs new file mode 100644 index 0000000..b04a216 --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/OfficerRoleTests.cs @@ -0,0 +1,128 @@ +using System.Text.Json; +using CompaniesHouse.Response.Officers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class OfficerRoleTests + { + [Theory] + [InlineData("cic-manager")] + [InlineData("corporate-director")] + [InlineData("corporate-llp-designated-member")] + [InlineData("corporate-llp-member")] + [InlineData("corporate-manager-of-an-eeig")] + [InlineData("corporate-managing-officer")] + [InlineData("corporate-member-of-a-management-organ")] + [InlineData("corporate-member-of-a-supervisory-organ")] + [InlineData("corporate-member-of-an-administrative-organ")] + [InlineData("corporate-nominee-director")] + [InlineData("corporate-nominee-secretary")] + [InlineData("corporate-secretary")] + [InlineData("director")] + [InlineData("general-partner-in-a-limited-partnership")] + [InlineData("corporate-general-partner-in-a-limited-partnership")] + [InlineData("limited-partner-in-a-limited-partnership")] + [InlineData("corporate-limited-partner-in-a-limited-partnership")] + [InlineData("judicial-factor")] + [InlineData("llp-designated-member")] + [InlineData("llp-member")] + [InlineData("manager-of-an-eeig")] + [InlineData("managing-officer")] + [InlineData("member-of-a-management-organ")] + [InlineData("member-of-a-supervisory-organ")] + [InlineData("member-of-an-administrative-organ")] + [InlineData("nominee-director")] + [InlineData("nominee-secretary")] + [InlineData("person-authorised-to-accept")] + [InlineData("person-authorised-to-represent")] + [InlineData("person-authorised-to-represent-and-accept")] + [InlineData("receiver-and-manager")] + [InlineData("secretary")] + public void Deserializing_KnownValue_RoundTripsAndIsKnown(string value) + { + var json = $"\"{value}\""; + + var role = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + role.Value.ShouldBe(value); + role.IsKnown.ShouldBeTrue(); + role.HasValue.ShouldBeTrue(); + + var reserialized = JsonSerializer.Serialize(role, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe(json); + } + + [Fact] + public void Deserializing_UnknownValue_DoesNotThrowAndPreservesRawValue() + { + const string json = "\"some-brand-new-officer-role\""; + + var role = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + role.Value.ShouldBe("some-brand-new-officer-role"); + role.IsKnown.ShouldBeFalse(); + role.HasValue.ShouldBeTrue(); + role.Description.ShouldBeNull(); + + var reserialized = JsonSerializer.Serialize(role, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe(json); + } + + [Fact] + public void Deserializing_Null_ReturnsDefaultWithNoValue() + { + var role = JsonSerializer.Deserialize("null", CompaniesHouseJsonSerializerOptions.Default); + + role.ShouldBe(default); + role.HasValue.ShouldBeFalse(); + role.Value.ShouldBe(string.Empty); + role.IsKnown.ShouldBeFalse(); + + var reserialized = JsonSerializer.Serialize(role, CompaniesHouseJsonSerializerOptions.Default); + reserialized.ShouldBe("null"); + } + + [Fact] + public void KnownValues_CompareEqualToStaticMembers() + { + new OfficerRole("director").ShouldBe(OfficerRole.Director); + new OfficerRole("secretary").ShouldBe(OfficerRole.Secretary); + new OfficerRole("corporate-general-partner-in-a-limited-partnership") + .ShouldBe(OfficerRole.CorporateGeneralPartnerInALimitedPartnership); + + (OfficerRole.Director == new OfficerRole("director")).ShouldBeTrue(); + (OfficerRole.Director == OfficerRole.Secretary).ShouldBeFalse(); + } + + [Fact] + public void SwitchExpression_MatchesOnKnownValues() + { + var role = new OfficerRole("director"); + + var description = role switch + { + _ when role == OfficerRole.Director => "is a director", + _ when role == OfficerRole.Secretary => "is a secretary", + _ => "something else", + }; + + description.ShouldBe("is a director"); + } + + [Fact] + public void Description_ReturnsFriendlyTextForKnownValues() + { + OfficerRole.CorporateManagingOfficer.Description.ShouldBe("Managing Officer"); + OfficerRole.PersonAuthorisedToRepresentAndAccept.Description.ShouldBe("Person Authorised to Represent and Accept"); + } + + [Fact] + public void ToString_ReturnsRawValue() + { + OfficerRole.Director.ToString().ShouldBe("director"); + default(OfficerRole).ToString().ShouldBe(string.Empty); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs index 3fb789e..3c9b33e 100644 --- a/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs +++ b/tests/CompaniesHouse.Tests/UriBuilders/OfficersUriBuilderTests/OfficersUriBuilderTests.cs @@ -21,7 +21,7 @@ public OfficersUriBuilderTests() _pageSize = 10; _startIndex = 5; _companyNumber = "123456789"; - _actualUri = _uriBuilder.Build(_companyNumber, _startIndex, _pageSize); + _actualUri = _uriBuilder.Build(_companyNumber, _startIndex, _pageSize, null, null, null); } [Fact] @@ -45,5 +45,13 @@ public void ThenTheUriQueryStringIsCorrect() var expected = $"?items_per_page={_pageSize}&start_index={_startIndex}"; uri.Query.ShouldBe(expected); } + + [Fact] + public void Build_AppendsOnlySuppliedOptionalParameters() + { + var uri = new Uri(_baseUri, _uriBuilder.Build(_companyNumber, _startIndex, _pageSize, "directors", true, "appointed_on")); + + uri.Query.ShouldBe("?items_per_page=10&start_index=5®ister_type=directors®ister_view=true&order_by=appointed_on"); + } } } From 91098cd1db7954367f82f9dd316492f5e4deddc1 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 23:43:26 +0100 Subject: [PATCH 13/38] Fix officers invalid-company integration test to match live API behaviour 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. --- .../Tests/OfficerTests/OfficersTestsInvalid.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs index 9289cdc..e9cc3bd 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs @@ -17,9 +17,14 @@ protected override async Task When() } [Fact] - public void ThenTheDataItemsAreNull() + public void ThenTheDataItemsAreEmpty() { - Result.Data.ShouldBeNull(); + // The Companies House API returns 200 with an empty officer list for a + // malformed/non-existent company number rather than 404, so Data is + // populated but contains no items. + Result.Data.ShouldNotBeNull(); + Result.Data.Items.ShouldBeEmpty(); + Result.Data.TotalResults.ShouldBe(0); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() From 47a0faf0d98f63e6af9c038092921b8cf7e03153 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Wed, 1 Jul 2026 23:56:00 +0100 Subject: [PATCH 14/38] Add integration-test skip mechanism and generator snapshot tests (plan 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. --- .../IntegrationFactAttribute.cs | 23 +++ .../IntegrationTheoryAttribute.cs | 19 ++ tests/CompaniesHouse.IntegrationTests/Keys.cs | 7 + .../CompanyProfileTestsInvalid.cs | 2 +- .../CompanyProfileTestsValid.cs | 8 +- .../OfficerByAppointmentSchemaTests.cs | 2 +- .../OfficerByAppointmentTestsInvalid.cs | 2 +- .../OfficerByAppointmentTestsValid.cs | 2 +- .../Tests/OfficerTests/OfficersSchemaTests.cs | 4 +- .../OfficerTests/OfficersTestsInvalid.cs | 2 +- .../Tests/OfficerTests/OfficersTestsValid.cs | 2 +- .../AdvancedCompanySearchTests.cs | 6 +- .../Tests/SearchingTests/AllSearchTests.cs | 6 +- .../CompaniesAlphabeticalSearchTests.cs | 4 +- .../SearchingTests/CompanySearchTests.cs | 6 +- .../DisqualifiedOfficersSearchTests.cs | 4 +- .../DissolvedCompaniesSearchTests.cs | 6 +- .../SearchingTests/OfficersSearchTests.cs | 4 +- .../ValueTypeEmitterSnapshotTests.cs | 168 ++++++++++++++++++ 19 files changed, 247 insertions(+), 30 deletions(-) create mode 100644 tests/CompaniesHouse.IntegrationTests/IntegrationFactAttribute.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/IntegrationTheoryAttribute.cs create mode 100644 tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs diff --git a/tests/CompaniesHouse.IntegrationTests/IntegrationFactAttribute.cs b/tests/CompaniesHouse.IntegrationTests/IntegrationFactAttribute.cs new file mode 100644 index 0000000..6350360 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/IntegrationFactAttribute.cs @@ -0,0 +1,23 @@ +using System; +using Xunit; + +namespace CompaniesHouse.IntegrationTests +{ + /// + /// A that skips itself cleanly when the + /// COMPANIES_HOUSE_API_KEY environment variable is not set, rather than failing with + /// an unauthenticated/expired-key error. See plan 10 (testing strategy): integration tests + /// must be skippable offline (e.g. in CI forks without a secret, or local dev without a key) + /// without being reported as failures. + /// + public sealed class IntegrationFactAttribute : FactAttribute + { + public IntegrationFactAttribute() + { + if (string.IsNullOrWhiteSpace(Keys.ApiKeyOrNull)) + { + Skip = "COMPANIES_HOUSE_API_KEY environment variable is not set - skipping integration test that calls the real API."; + } + } + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/IntegrationTheoryAttribute.cs b/tests/CompaniesHouse.IntegrationTests/IntegrationTheoryAttribute.cs new file mode 100644 index 0000000..044b656 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/IntegrationTheoryAttribute.cs @@ -0,0 +1,19 @@ +using Xunit; + +namespace CompaniesHouse.IntegrationTests +{ + /// + /// A counterpart to - + /// skips cleanly when COMPANIES_HOUSE_API_KEY is not set. + /// + public sealed class IntegrationTheoryAttribute : TheoryAttribute + { + public IntegrationTheoryAttribute() + { + if (string.IsNullOrWhiteSpace(Keys.ApiKeyOrNull)) + { + Skip = "COMPANIES_HOUSE_API_KEY environment variable is not set - skipping integration test that calls the real API."; + } + } + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Keys.cs b/tests/CompaniesHouse.IntegrationTests/Keys.cs index 5349fa2..efa047f 100644 --- a/tests/CompaniesHouse.IntegrationTests/Keys.cs +++ b/tests/CompaniesHouse.IntegrationTests/Keys.cs @@ -5,5 +5,12 @@ namespace CompaniesHouse.IntegrationTests public static class Keys { public static string ApiKey { get; } = Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY")!; + + /// + /// Same value as without the null-forgiving suppression, for use by + /// / to decide + /// whether to skip a test cleanly when no key is configured. + /// + public static string? ApiKeyOrNull { get; } = Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY"); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs index 20af11e..6cb7b19 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs @@ -16,7 +16,7 @@ await WhenRetrievingAnInvalidCompanyProfile() ; } - [Fact] + [IntegrationFact] public void ThenTheProfileIsNotReturned() { _result.Data.ShouldBeNull(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs index 9c7641d..fde4c40 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs @@ -19,13 +19,13 @@ await WhenRetrievingAValidCompanyProfile() ; } - [Fact] + [IntegrationFact] public void ThenTheProfileIsReturned() { _result.Data.CompanyName.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenAPlainCompanyProfileIncludesExemptionsAndHasSuperSecurePscs() { var result = await _client.GetCompanyProfileAsync("00445790"); @@ -38,7 +38,7 @@ public async Task ThenAPlainCompanyProfileIncludesExemptionsAndHasSuperSecurePsc result.Data.HasSuperSecurePscs.ShouldBe(false); } - [Fact] + [IntegrationFact] public async Task ThenAForeignCompanyProfileIncludesForeignCompanyDetails() { var result = await _client.GetCompanyProfileAsync("FC040879"); @@ -56,7 +56,7 @@ public async Task ThenAForeignCompanyProfileIncludesForeignCompanyDetails() result.Data.Links.UkEstablishments.ShouldNotBeNullOrWhiteSpace(); } - [Fact] + [IntegrationFact] public async Task ThenACommunityInterestCompanyProfileIncludesSubtype() { var result = await _client.GetCompanyProfileAsync("13507518"); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs index 1fe73ee..ed1bf86 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs @@ -8,7 +8,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests { public class OfficerByAppointmentSchemaTests { - [Fact] + [IntegrationFact] public async Task GetOfficerByAppointmentIdAsync_DeserializesTheSharedOfficerShape() { var client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs index 1426a93..bdec3e5 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs @@ -14,7 +14,7 @@ public class OfficerByAppointmentTestsInvalid : OfficersTestBase protected override async Task When() => Result = await Client.GetOfficerByAppointmentIdAsync(InvalidCompanyNumber, InvalidAppointmentId); - [Fact] + [IntegrationFact] public void ThenTheDataIsNull() => Result.Data.ShouldBeNull(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs index 874ca18..dc314dd 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs @@ -25,7 +25,7 @@ private async Task WhenRetrievingAnCompanyFilingHistoryForAValidCompany() => ; - [Fact] + [IntegrationFact] public void ThenTheDataIsNotNull() => Result.Data.ShouldNotBeNull(); } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs index 4e6225f..c72b6bc 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs @@ -9,7 +9,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests { public class OfficersSchemaTests { - [Fact] + [IntegrationFact] public async Task GetOfficersAsync_DeserializesConfirmedTescoFields() { var client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); @@ -30,7 +30,7 @@ public async Task GetOfficersAsync_DeserializesConfirmedTescoFields() melissaBethell.IdentityVerificationDetails.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); } - [Fact] + [IntegrationFact] public async Task GetOfficersAsync_DeserializesCorporateIdentificationAndAppointedBefore() { var client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs index e9cc3bd..1ace931 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs @@ -16,7 +16,7 @@ protected override async Task When() await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany(); } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreEmpty() { // The Companies House API returns 200 with an empty officer list for a diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs index 2e4d7b8..adb2653 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsValid.cs @@ -18,7 +18,7 @@ await WhenRetrievingAnCompanyFilingHistoryForAValidCompany() ; } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreNotEmpty() { Result.Data.Items.ShouldNotBeEmpty(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs index a7849d4..dae3f14 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs @@ -16,7 +16,7 @@ public AdvancedCompanySearchTests() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Fact] + [IntegrationFact] public async Task ThenCompaniesAreReturned() { var result = await _client.AdvancedCompanySearchAsync(new AdvancedCompanySearchRequest @@ -30,7 +30,7 @@ public async Task ThenCompaniesAreReturned() result.Data.Items.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenCompanySubtypeCanBeUsedAsALiveFilter() { var result = await _client.AdvancedCompanySearchAsync(new AdvancedCompanySearchRequest @@ -43,7 +43,7 @@ public async Task ThenCompanySubtypeCanBeUsedAsALiveFilter() result.Data.Items.ShouldContain(x => x.CompanySubtype == CompanySubtype.CommunityInterestCompany); } - [Fact] + [IntegrationFact] public async Task ThenLocationAndSicCodeFiltersCanBeUsedTogether() { var result = await _client.AdvancedCompanySearchAsync(new AdvancedCompanySearchRequest diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs index 21da3d0..5087c90 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs @@ -18,7 +18,7 @@ public AllSearchTests() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Theory] + [IntegrationTheory] [InlineData("British Gas")] [InlineData("Kevin")] public async Task ThenItemsAreReturned(string query) @@ -28,7 +28,7 @@ public async Task ThenItemsAreReturned(string query) result.Data.Items.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenPagingAndMixedItemTypesAreReturned() { var result = await _client.SearchAllAsync(new SearchAllRequest { Query = "john", ItemsPerPage = 20 }); @@ -38,7 +38,7 @@ public async Task ThenPagingAndMixedItemTypesAreReturned() result.Data.Items.ShouldContain(x => x is Officer); } - [Fact] + [IntegrationFact] public async Task ThenCompanySpecificFieldsRoundTripFromSearchAll() { var result = await _client.SearchAllAsync(new SearchAllRequest { Query = "absa uk permanent establishment", ItemsPerPage = 20 }); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs index 34c4e34..d583d00 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs @@ -14,7 +14,7 @@ public CompaniesAlphabeticalSearchTests() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Theory] + [IntegrationTheory] [InlineData("TESCO")] [InlineData("TESCO PERSONAL FINANCE")] public async Task ThenCompaniesAreReturned(string query) @@ -29,7 +29,7 @@ public async Task ThenCompaniesAreReturned(string query) result.Data.Items.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenAlphabeticalPagingParametersCanBeSent() { var firstPage = await _client.SearchCompaniesAlphabeticallyAsync(new SearchCompaniesAlphabeticallyRequest diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs index 9978e0d..4ea41d0 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs @@ -16,7 +16,7 @@ public CompanySearchTests() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Theory] + [IntegrationTheory] [InlineData("brighouse computers")] [InlineData("British Gas")] [InlineData("Bay Horse")] @@ -27,7 +27,7 @@ public async Task ThenCompaniesAreReturned(string query) result.Data.Companies.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenForeignCompanyFieldsAreReturned() { var result = await _client.SearchCompanyAsync(new SearchCompanyRequest @@ -44,7 +44,7 @@ public async Task ThenForeignCompanyFieldsAreReturned() company.DescriptionIdentifier.ShouldBe(["first-uk-establishment-opened-on"]); } - [Fact] + [IntegrationFact] public async Task ThenRestrictionsCanBeSentToTheLiveApi() { var result = await _client.SearchCompanyAsync(new SearchCompanyRequest diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs index e81bc53..efcae75 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs @@ -15,7 +15,7 @@ public DisqualifiedOfficersSearchTests() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Fact] + [IntegrationFact] public async Task ThenDisqualifiedOfficersAreReturned() { var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "Kevin" }); @@ -23,7 +23,7 @@ public async Task ThenDisqualifiedOfficersAreReturned() result.Data.DisqualifiedOfficers.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenPagingMetadataAndDateOfBirthAreReturned() { var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "john", ItemsPerPage = 20 }); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs index 47d339f..10853fa 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs @@ -14,7 +14,7 @@ public DissolvedCompaniesSearchTests() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Theory] + [IntegrationTheory] [InlineData("CARILLION")] [InlineData("BLOCKBUSTER")] public async Task ThenCompaniesAreReturned(string query) @@ -30,7 +30,7 @@ public async Task ThenCompaniesAreReturned(string query) result.Data.Items.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenPreviousNameSearchReturnsMatchedPreviousCompanyName() { var result = await _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest @@ -45,7 +45,7 @@ public async Task ThenPreviousNameSearchReturnsMatchedPreviousCompanyName() result.Data.TopHit.MatchedPreviousCompanyName.Name.ShouldContain("RADIO RENTALS"); } - [Fact] + [IntegrationFact] public async Task ThenAlphabeticalSearchReturnsOrderedAlphaKeys() { var result = await _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs index 7fd2f8a..365af67 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs @@ -16,7 +16,7 @@ public OfficersSearchTests() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Fact] + [IntegrationFact] public async Task ThenOfficersAreReturned() { var result = await _client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Kevin" }); @@ -24,7 +24,7 @@ public async Task ThenOfficersAreReturned() result.Data.Officers.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenLiveOfficerBirthMonthAndPagingMetadataAreReturned() { var result = await _client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Alan Sugar", ItemsPerPage = 20 }); diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs new file mode 100644 index 0000000..e6c5abf --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs @@ -0,0 +1,168 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using CompaniesHouse.SourceGenerator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.SourceGenerator.Tests +{ + /// + /// Full-source "snapshot" tests: given a small fixed YAML input, assert the entire + /// generated file content matches byte-for-byte (rather than spot-checking a handful + /// of lines like ). Guards against accidental + /// whitespace/shape regressions in - see plan 10 + /// (testing strategy), "Generator snapshot tests". + /// + public class ValueTypeEmitterSnapshotTests + { + [Fact] + public void GeneratesTheExpectedValueTypeSourceForASimpleGroup() + { + var additionalFiles = new[] + { + new InMemoryAdditionalText(@"external\api-enumerations\constants.yml", """ + widget_state: + 'on' : "On" + 'off' : "Off" + """), + new InMemoryAdditionalText(@"enum-map.txt", "widget_state|CompaniesHouse.Snapshot|WidgetState|true"), + }; + + var generated = RunGenerator(additionalFiles); + + generated["WidgetState.g.cs"].ShouldBe( + """ + // + // Generated by CompaniesHouse.SourceGenerator from the api-enumerations + // submodule and enumerations/extra overlay. Do not hand-edit - see + // .plans/completed/04-enum-source-generator.md. + #nullable enable + + using System; + using System.Collections.Generic; + using System.Text.Json.Serialization; + using CompaniesHouse.JsonConverters; + + namespace CompaniesHouse.Snapshot + { + [JsonConverter(typeof(WidgetStateJsonConverter))] + public readonly record struct WidgetState + { + private readonly string? _value; + + public WidgetState(string? value) + { + _value = value; + } + + public string Value => _value ?? string.Empty; + + public bool HasValue => !string.IsNullOrEmpty(_value); + + public bool IsKnown => KnownValues.Contains(Value); + + public string? Description => Descriptions.TryGetValue(Value, out var description) ? description : null; + + public static WidgetState On => new("on"); + public static WidgetState Off => new("off"); + + public override string ToString() => Value; + + private static readonly HashSet KnownValues = new(StringComparer.Ordinal) + { + "on", + "off", + }; + + private static readonly IReadOnlyDictionary Descriptions = new Dictionary(StringComparer.Ordinal) + { + ["on"] = "On", + ["off"] = "Off", + }; + } + } + + """, + StringCompareShould.IgnoreLineEndings); + + generated["WidgetStateJsonConverter.g.cs"].ShouldBe( + """ + // + // Generated by CompaniesHouse.SourceGenerator - do not hand-edit. + #nullable enable + + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + using CompaniesHouse.Snapshot; + + namespace CompaniesHouse.JsonConverters + { + public sealed class WidgetStateJsonConverter : JsonConverter + { + public override WidgetState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + return new WidgetState(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, WidgetState value, JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + return; + } + + writer.WriteStringValue(value.Value); + } + } + } + + """, + StringCompareShould.IgnoreLineEndings); + } + + private static Dictionary RunGenerator(IEnumerable additionalFiles) + { + var compilation = CSharpCompilation.Create( + "CompaniesHouse.SourceGenerator.Tests.SnapshotAssembly", + references: new[] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + }, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + var generator = new EnumValueTypeGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(new IIncrementalGenerator[] { generator }); + driver = driver.AddAdditionalTexts(additionalFiles.Cast().ToImmutableArray()); + + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out var runDiagnostics); + var runResult = driver.GetRunResult(); + + var faulted = runResult.Results.FirstOrDefault(r => r.Exception is not null); + if (faulted.Exception is not null) + { + throw faulted.Exception; + } + + if (runDiagnostics.Any()) + { + throw new System.Exception("Generator diagnostics: " + string.Join("\n", runDiagnostics)); + } + + return runResult.Results + .Where(r => !r.GeneratedSources.IsDefault) + .SelectMany(r => r.GeneratedSources) + .ToDictionary(s => s.HintName, s => s.SourceText.ToString()); + } + } +} From 1ca61d9cb9ff85ce2c040130f8a223483352c1ef Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 2 Jul 2026 00:08:42 +0100 Subject: [PATCH 15/38] Modernize remaining master-backed endpoint clients 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. --- enumerations/extra/charges.yml | 29 ++++ enumerations/extra/filing.yml | 81 +++++++++ enumerations/extra/insolvency.yml | 31 ++++ enumerations/extra/insolvency_case_type.yml | 23 +++ enumerations/extra/psc.yml | 84 ++++++++++ ...sHouseClientServiceCollectionExtensions.cs | 4 + .../CompaniesHouseAppointmentsClient.cs | 7 +- src/CompaniesHouse/CompaniesHouseClient.cs | 4 +- ...HouseCompanyInsolvencyInformationClient.cs | 9 +- src/CompaniesHouse/ICompaniesHouseClient.cs | 3 +- ...aniesHouseRegisteredOfficeAddressClient.cs | 4 +- .../EnumArrayOrSingleJsonConverterFactory.cs | 31 +++- .../Response/Appointments/AppointedTo.cs | 6 +- .../Response/Appointments/Appointment.cs | 26 ++- .../Response/Appointments/AppointmentLinks.cs | 10 ++ .../Response/Appointments/Appointments.cs | 29 +++- .../Appointments/AppointmentsLinks.cs | 10 ++ .../Response/AssetsCeasedReleased.cs | 31 ---- src/CompaniesHouse/Response/ChargeStatus.cs | 22 --- src/CompaniesHouse/Response/Charges/Charge.cs | 20 +-- .../Response/Charges/Charges.cs | 20 +-- .../Response/Charges/Classification.cs | 4 +- .../Response/Charges/InsolvencyCase.cs | 8 +- .../Response/Charges/InsolvencyCaseLinks.cs | 2 +- src/CompaniesHouse/Response/Charges/Links.cs | 2 +- .../Response/Charges/Particular.cs | 4 +- .../Response/Charges/PersonEntitled.cs | 2 +- .../Response/Charges/SecuredDetail.cs | 2 +- .../Response/Charges/Transaction.cs | 6 +- .../Response/Charges/TransactionLinks.cs | 6 +- .../Response/ClassificationChargeType.cs | 16 -- .../CompanyFiling/CompanyFilingHistory.cs | 6 +- .../CompanyFiling/FilingHistoryItem.cs | 19 ++- .../FilingHistoryItemAnnotation.cs | 2 +- .../FilingHistoryItemAssociatedFiling.cs | 8 +- .../FilingHistoryItemResolution.cs | 6 +- .../Response/CompanyFiling/Links.cs | 4 +- .../Response/Document/DocumentMetadata.cs | 20 ++- .../Document/DocumentMetadataContentLength.cs | 2 +- src/CompaniesHouse/Response/Document/Links.cs | 4 +- src/CompaniesHouse/Response/FilingCategory.cs | 87 ---------- .../Response/FilingHistoryStatus.cs | 42 ----- .../Response/FilingSubcategory.cs | 93 ----------- .../Response/Insolvency/Address.cs | 12 +- .../Response/Insolvency/Case.cs | 10 +- .../Response/Insolvency/CaseDate.cs | 2 +- .../Response/Insolvency/CaseDateType.cs | 58 ------- .../CompanyInsolvencyInformation.cs | 6 +- .../Response/Insolvency/InsolvencyStatus.cs | 43 ----- .../Response/Insolvency/Links.cs | 2 +- .../Response/Insolvency/Practitioner.cs | 10 +- src/CompaniesHouse/Response/ParticularType.cs | 22 --- .../PersonWithSignificantControl.cs | 27 +-- ...sonWithSignificantControlIdentification.cs | 11 +- .../PersonWithSignificantControlKind.cs | 31 ---- .../PersonWithSignificantControlLinks.cs | 4 +- ...onWithSignificantControlNatureOfControl.cs | 152 ----------------- .../PersonsWithSignificantControl.cs | 14 +- .../Response/RegisteredOfficeAddress/Links.cs | 2 +- .../RegisteredOfficeAddress/OfficeAddress.cs | 36 ++-- .../OfficeAddressCountry.cs | 28 ---- .../Response/ResolutionCategory.cs | 33 ---- .../Response/SecuredDetailType.cs | 16 -- .../UriBuilders/AppointmentsUriBuilder.cs | 14 ++ .../CompanyInsolvencyInformationUriBuilder.cs | 14 ++ .../UriBuilders/IAppointmentsUriBuilder.cs | 9 + ...ICompanyInsolvencyInformationUriBuilder.cs | 9 + src/CompaniesHouse/enum-map.txt | 14 ++ .../ServiceCollectionExtensionsTests.cs | 2 + .../CollectionBehavior.cs | 3 + .../AppointmentsTests/OfficersTestsValid.cs | 11 +- .../ChargesTests/ChargeByIdTestsValid.cs | 14 +- .../ChargesTests/ChargesListTestsValid.cs | 11 ++ .../CompanyFilingHistoryTestsInvalid.cs | 5 +- .../CompanyFilingHistoryTestsValid.cs | 11 ++ .../FilingHistoryByTransactionIdTestsValid.cs | 15 +- .../CompanyInsolvencyInformationTestsValid.cs | 10 +- .../DocumentMetadataTestsValid.cs | 13 +- ...rsonsWithSignificantControlTestsInValid.cs | 7 +- ...PersonsWithSignificantControlTestsValid.cs | 12 +- .../RegisteredOfficeAddressesTestsValid.cs | 11 +- .../DissolvedCompaniesSearchTests.cs | 22 ++- .../AppointmentsAndPscScenarios.cs | 81 +++++++++ .../FilingAndChargesScenarios.cs | 74 ++++++++ .../InsolvencyScenarios.cs | 41 +++++ .../RegisteredOfficeAndDocumentsScenarios.cs | 63 +++++++ .../CompaniesHouseAppointmentsClientTests.cs | 113 +++++++++++++ .../CompaniesHouseChargesClientTests.cs | 52 +++++- ...iesHouseCompanyFilingHistoryClientTests.cs | 36 ++++ ...CompanyInsolvencyInformationClientTests.cs | 53 ++++++ ...mpaniesHouseDocumentMetadataClientTests.cs | 36 +++- .../DocumentMetadataTestCase.cs | 2 +- ...HousePersonsWithSignificantControlTests.cs | 44 +++++ ...paniesHouseRegisteredOfficeAddressTests.cs | 43 +++-- .../EnumerationMappings.cs | 158 +++++++++--------- ...ieldEnumConverterTestsForMultipleValues.cs | 2 +- ...OrFieldEnumConverterTestsForSingleValue.cs | 2 +- .../ChargeValueTypeTests.cs | 26 +++ .../FilingValueTypeTests.cs | 34 ++++ .../InsolvencyValueTypeTests.cs | 30 ++++ .../ResponseValueTypes/PscValueTypeTests.cs | 32 ++++ .../AppointmentsUriBuilderTests.cs | 18 ++ .../ChargesUriBuilderTests.cs | 26 +++ ...anyInsolvencyInformationUriBuilderTests.cs | 18 ++ .../DocumentUriBuilderTests.cs | 26 +++ 105 files changed, 1604 insertions(+), 961 deletions(-) create mode 100644 enumerations/extra/charges.yml create mode 100644 enumerations/extra/filing.yml create mode 100644 enumerations/extra/insolvency.yml create mode 100644 enumerations/extra/insolvency_case_type.yml create mode 100644 enumerations/extra/psc.yml create mode 100644 src/CompaniesHouse/Response/Appointments/AppointmentLinks.cs create mode 100644 src/CompaniesHouse/Response/Appointments/AppointmentsLinks.cs delete mode 100644 src/CompaniesHouse/Response/AssetsCeasedReleased.cs delete mode 100644 src/CompaniesHouse/Response/ChargeStatus.cs delete mode 100644 src/CompaniesHouse/Response/ClassificationChargeType.cs delete mode 100644 src/CompaniesHouse/Response/FilingCategory.cs delete mode 100644 src/CompaniesHouse/Response/FilingHistoryStatus.cs delete mode 100644 src/CompaniesHouse/Response/FilingSubcategory.cs delete mode 100644 src/CompaniesHouse/Response/Insolvency/CaseDateType.cs delete mode 100644 src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs delete mode 100644 src/CompaniesHouse/Response/ParticularType.cs delete mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs delete mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs delete mode 100644 src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs delete mode 100644 src/CompaniesHouse/Response/ResolutionCategory.cs delete mode 100644 src/CompaniesHouse/Response/SecuredDetailType.cs create mode 100644 src/CompaniesHouse/UriBuilders/AppointmentsUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/CompanyInsolvencyInformationUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/IAppointmentsUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/ICompanyInsolvencyInformationUriBuilder.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/CollectionBehavior.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseAppointmentsClientTests/CompaniesHouseAppointmentsClientTests.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/ChargeValueTypeTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/FilingValueTypeTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/InsolvencyValueTypeTests.cs create mode 100644 tests/CompaniesHouse.Tests/ResponseValueTypes/PscValueTypeTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/AppointmentsUriBuilderTests/AppointmentsUriBuilderTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/ChargesUriBuilderTests/ChargesUriBuilderTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/CompanyInsolvencyInformationUriBuilderTests/CompanyInsolvencyInformationUriBuilderTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/DocumentUriBuilderTests/DocumentUriBuilderTests.cs diff --git a/enumerations/extra/charges.yml b/enumerations/extra/charges.yml new file mode 100644 index 0000000..10e9b55 --- /dev/null +++ b/enumerations/extra/charges.yml @@ -0,0 +1,29 @@ +charge_status: + 'outstanding': '' + 'fully-satisfied': '' + 'part-satisfied': '' + 'satisfied': '' + +classification_charge_type: + 'charge-description': '' + 'nature-of-charge': '' + +particular_type: + 'short-particulars': '' + 'charged-property-description': '' + 'charged-property-or-undertaking-description': '' + 'brief-description': '' + +secured_detail_type: + 'amount-secured': '' + 'obligations-secured': '' + +assets_ceased_released: + 'property-ceased-to-belong': '' + 'part-property-release-and-ceased-to-belong': '' + 'part-property-released': '' + 'part-property-ceased-to-belong': '' + 'whole-property-released': '' + 'multiple-filings': '' + 'whole-property-released-and-ceased-to-belong': '' + diff --git a/enumerations/extra/filing.yml b/enumerations/extra/filing.yml new file mode 100644 index 0000000..d50c2a8 --- /dev/null +++ b/enumerations/extra/filing.yml @@ -0,0 +1,81 @@ +filing_history_status: + 'filing-history-available': '' + 'filing-history-not-available-invalid-format': '' + 'filing-history-available-no-images-limited-partnership-from-1988': '' + 'filing-history-available-assurance-company-before-2004': '' + 'filing-history-available-limited-partnership-from-2014': '' + 'filing-history-not-available-industrial-and-provident-society': '' + 'filing-history-not-available-limited-partnership-before-1988': '' + 'filing-history-not-available-royal-charter': '' + 'filing-history-not-available-scottish-industrial-and-provident-society': '' + 'filing-history-not-available-northern-ireland-industrial-and-provident-society': '' + 'filing-history-not-available-unknown-prefix': '' + +filing_category: + 'auditors': '' + 'accounts': '' + 'address': '' + 'annual-return': '' + 'capital': '' + 'gazette': '' + 'change-of-name': '' + 'incorporation': '' + 'liquidation': '' + 'miscellaneous': '' + 'mortgage': '' + 'officers': '' + 'resolution': '' + 'change-of-constitution': '' + 'document-replacement': '' + 'insolvency': '' + 'confirmation-statement': '' + 'persons-with-significant-control': '' + 'historical': '' + 'dissolution': '' + 'restoration': '' + 'return': '' + 'other': '' + 'court-order': '' + 'reregistration': '' + 'certificate': '' + +filing_subcategory: + 'annual-return': '' + 'resolution': '' + 'change': '' + 'create': '' + 'certificate': '' + 'appointments': '' + 'satisfy': '' + 'termination': '' + 'release-cease': '' + 'voluntary': '' + 'administration': '' + 'compulsory': '' + 'court-order': '' + 'other': '' + 'notifications': '' + 'officers': '' + 'document-replacement': '' + 'statements': '' + 'voluntary-arrangement': '' + 'alter': '' + 'register': '' + 'receiver': '' + 'voluntary-arrangement-moratoria': '' + 'acquire': '' + 'trustee': '' + 'mortgage': '' + 'transfer': '' + 'debenture': '' + +resolution_category: + 'capital': '' + 'incorporation': '' + 'miscellaneous': '' + 'resolution': '' + 'change-of-name': '' + 'liquidation': '' + 'auditors': '' + 'insolvency': '' + diff --git a/enumerations/extra/insolvency.yml b/enumerations/extra/insolvency.yml new file mode 100644 index 0000000..b5fe75f --- /dev/null +++ b/enumerations/extra/insolvency.yml @@ -0,0 +1,31 @@ +insolvency_status: + 'live-propopsed-transfer-from-gb': '' + 'voluntary-arrangement': '' + 'voluntary-arrangement-receivership': '' + 'administration-order': '' + 'live-receiver-manager-on-at-least-one-charge': '' + 'administrative-receiver': '' + 'receiver-manager-or-administrative-receiver': '' + 'receiver-manager': '' + 'receivership': '' + 'in-administration': '' + 'liquidation': '' + +insolvency_case_date_type: + 'instrumented-on': '' + 'administration-started-on': '' + 'administration-discharged-on': '' + 'administration-ended-on': '' + 'concluded-winding-up-on': '' + 'petitioned-on': '' + 'ordered-to-wind-up-on': '' + 'due-to-be-dissolved-on': '' + 'case-end-on': '' + 'wound-up-on': '' + 'voluntary-arrangement-started-on': '' + 'voluntary-arrangement-ended-on': '' + 'moratorium-started-on': '' + 'moratorium-ended-on': '' + 'declaration-solvent-on': '' + 'dissolved-on': '' + diff --git a/enumerations/extra/insolvency_case_type.yml b/enumerations/extra/insolvency_case_type.yml new file mode 100644 index 0000000..d74c7bf --- /dev/null +++ b/enumerations/extra/insolvency_case_type.yml @@ -0,0 +1,23 @@ +insolvency_case_type: + 'compulsory-liquidation' : "Compulsory liquidation" + 'in-administration' : "In administration" + 'creditors-voluntary-liquidation' : "Creditors voluntary liquidation" + 'members-voluntary-liquidation' : "Members voluntary liquidation" + 'foreign-insolvency' : "Foreign insolvency" + 'administrative-receiver' : "Administrative receiver appointed" + 'scottish-administrative-receiver' : "Receiver (Scotland) appointed" + 'administration-order' : "Administration order" + 'corporate-voluntary-arrangement' : "Corporate voluntary arrangement (CVA)" + 'receiver-manager' : "Receiver/Manager appointed" + 'corporate-voluntary-arrangement-moratorium' : "Corporate voluntary arrangement moratorium" + 'order-of-court-restructuring-plan' : "Restructuring plan" + 'liquidation-moratorium-commencement-of-moratorium' : "Commencement of Moratorium" + 'liquidation-moratorium-extension-of-moratorium' : "Moratorium has been ended or extended" + 'liquidation-moratorium-early-end-of-moratorium' : "Early end of Moratorium" + 'liquidation-moratorium-end-of-moratorium-by-monitor' : "End of Moratorium by a Monitor" + 'liquidation-moratorium-end-of-moratorium-by-court' : "End of Moratorium by a Court" + 'liquidation-moratorium-end-of-moratorium-following-disposal-of-application-for-extension-by-court-or-following-cva-proposal-taking-effect-or-being-withdrawn' : "End of Moratorium following disposal of application for extension by the court or following CVA proposal taking effect or being withdrawn" + 'liquidation-moratorium-court-order-permitting-disposal-of-goods' : "Court order permitting disposal of property or goods" + 'liquidation-moratorium-replacement-or-additonal-monitor-following-court-order' : "Replacement or additional monitor (following court order)" + 'liquidation-moratorium-monitor-ceasing-to-act-following-court-order' : "Monitor ceasing to act following court" + 'moratorium' : "Moratorium" diff --git a/enumerations/extra/psc.yml b/enumerations/extra/psc.yml new file mode 100644 index 0000000..a380762 --- /dev/null +++ b/enumerations/extra/psc.yml @@ -0,0 +1,84 @@ +person_with_significant_control_kind: + 'corporate-entity-person-with-significant-control': '' + 'corporate-entity-beneficial-owner': '' + 'individual-person-with-significant-control': '' + 'individual-beneficial-owner': '' + 'super-secure-person-with-significant-control': '' + 'super-secure-beneficial-owner': '' + 'legal-person-person-with-significant-control': '' + 'legal-person-beneficial-owner': '' + +person_with_significant_control_nature_of_control: + 'ownership-of-shares-25-to-50-percent': '' + 'ownership-of-shares-50-to-75-percent': '' + 'ownership-of-shares-75-to-100-percent': '' + 'ownership-of-shares-25-to-50-percent-as-trust': '' + 'ownership-of-shares-50-to-75-percent-as-trust': '' + 'ownership-of-shares-75-to-100-percent-as-trust': '' + 'ownership-of-shares-25-to-50-percent-as-firm': '' + 'ownership-of-shares-50-to-75-percent-as-firm': '' + 'ownership-of-shares-75-to-100-percent-as-firm': '' + 'ownership-of-shares-more-than-25-percent-registered-overseas-entity': '' + 'ownership-of-shares-more-than-25-percent-as-trust-registered-overseas-entity': '' + 'ownership-of-shares-more-than-25-percent-as-firm-registered-overseas-entity': '' + 'voting-rights-25-to-50-percent': '' + 'voting-rights-50-to-75-percent': '' + 'voting-rights-75-to-100-percent': '' + 'voting-rights-25-to-50-percent-as-trust': '' + 'voting-rights-50-to-75-percent-as-trust': '' + 'voting-rights-75-to-100-percent-as-trust': '' + 'voting-rights-25-to-50-percent-as-firm': '' + 'voting-rights-50-to-75-percent-as-firm': '' + 'voting-rights-75-to-100-percent-as-firm': '' + 'voting-rights-more-than-25-percent-registered-overseas-entity': '' + 'voting-rights-more-than-25-percent-as-trust-registered-overseas-entity': '' + 'voting-rights-more-than-25-percent-as-firm-registered-overseas-entity': '' + 'right-to-appoint-and-remove-directors': '' + 'right-to-appoint-and-remove-directors-as-trust': '' + 'right-to-appoint-and-remove-directors-as-firm': '' + 'significant-influence-or-control': '' + 'significant-influence-or-control-as-trust': '' + 'significant-influence-or-control-as-firm': '' + 'right-to-share-surplus-assets-25-to-50-percent-limited-liability-partnership': '' + 'right-to-share-surplus-assets-50-to-75-percent-limited-liability-partnership': '' + 'right-to-share-surplus-assets-75-to-100-percent-limited-liability-partnership': '' + 'right-to-share-surplus-assets-25-to-50-percent-as-trust-limited-liability-partnership': '' + 'right-to-share-surplus-assets-50-to-75-percent-as-trust-limited-liability-partnership': '' + 'right-to-share-surplus-assets-75-to-100-percent-as-trust-limited-liability-partnership': '' + 'right-to-share-surplus-assets-25-to-50-percent-as-firm-limited-liability-partnership': '' + 'right-to-share-surplus-assets-50-to-75-percent-as-firm-limited-liability-partnership': '' + 'right-to-share-surplus-assets-75-to-100-percent-as-firm-limited-liability-partnership': '' + 'voting-rights-25-to-50-percent-limited-liability-partnership': '' + 'voting-rights-50-to-75-percent-limited-liability-partnership': '' + 'voting-rights-75-to-100-percent-limited-liability-partnership': '' + 'voting-rights-25-to-50-percent-as-trust-limited-liability-partnership': '' + 'voting-rights-50-to-75-percent-as-trust-limited-liability-partnership': '' + 'voting-rights-75-to-100-percent-as-trust-limited-liability-partnership': '' + 'voting-rights-25-to-50-percent-as-firm-limited-liability-partnership': '' + 'voting-rights-50-to-75-percent-as-firm-limited-liability-partnership': '' + 'voting-rights-75-to-100-percent-as-firm-limited-liability-partnership': '' + 'right-to-appoint-and-remove-members-limited-liability-partnership': '' + 'right-to-appoint-and-remove-members-as-trust-limited-liability-partnership': '' + 'right-to-appoint-and-remove-members-as-firm-limited-liability-partnership': '' + 'significant-influence-or-control-limited-liability-partnership': '' + 'significant-influence-or-control-as-trust-limited-liability-partnership': '' + 'significant-influence-or-control-as-firm-limited-liability-partnership': '' + 'significant-influence-or-control-registered-overseas-entity': '' + 'significant-influence-or-control-as-trust-registered-overseas-entity': '' + 'significant-influence-or-control-as-firm-registered-overseas-entity': '' + 'part-right-to-share-surplus-assets-25-to-50-percent': '' + 'part-right-to-share-surplus-assets-50-to-75-percent': '' + 'part-right-to-share-surplus-assets-75-to-100-percent': '' + 'part-right-to-share-surplus-assets-25-to-50-percent-as-trust': '' + 'part-right-to-share-surplus-assets-50-to-75-percent-as-trust': '' + 'part-right-to-share-surplus-assets-75-to-100-percent-as-trust': '' + 'part-right-to-share-surplus-assets-25-to-50-percent-as-firm': '' + 'part-right-to-share-surplus-assets-50-to-75-percent-as-firm': '' + 'part-right-to-share-surplus-assets-75-to-100-percent-as-firm': '' + 'right-to-appoint-and-remove-person': '' + 'right-to-appoint-and-remove-person-as-firm': '' + 'right-to-appoint-and-remove-person-as-trust': '' + 'right-to-appoint-and-remove-directors-registered-overseas-entity': '' + 'right-to-appoint-and-remove-directors-as-trust-registered-overseas-entity': '' + 'right-to-appoint-and-remove-directors-as-firm-registered-overseas-entity': '' + diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs index ea7e3b7..5633e79 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouseClientServiceCollectionExtensions.cs @@ -175,6 +175,8 @@ private static IServiceCollection TryAddCompaniesHouseSubClients(this IServiceCo provider.GetRequiredService()); services.TryAddTransient(provider => provider.GetRequiredService()); + services.TryAddTransient(provider => + provider.GetRequiredService()); return services; } @@ -355,6 +357,8 @@ private static IServiceCollection TryAddKeyedCompaniesHouseSubClients(this IServ provider.GetRequiredKeyedService(key)); services.TryAddKeyedTransient(name, (provider, key) => provider.GetRequiredKeyedService(key)); + services.TryAddKeyedTransient(name, (provider, key) => + provider.GetRequiredKeyedService(key)); return services; } diff --git a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs index ee17e34..b2c41e2 100644 --- a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs +++ b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Appointments; +using CompaniesHouse.UriBuilders; namespace CompaniesHouse { @@ -10,15 +11,17 @@ namespace CompaniesHouse public class CompaniesHouseAppointmentsClient : ICompaniesHouseAppointmentsClient { private readonly HttpClient _httpClient; + private readonly IAppointmentsUriBuilder _appointmentsUriBuilder; - public CompaniesHouseAppointmentsClient(HttpClient httpClient) + public CompaniesHouseAppointmentsClient(HttpClient httpClient, IAppointmentsUriBuilder appointmentsUriBuilder) { _httpClient = httpClient; + _appointmentsUriBuilder = appointmentsUriBuilder; } public async Task> GetAppointmentsAsync(string officerId, int startIndex, int pageSize, CancellationToken cancellationToken) { - var requestUri = $"officers/{officerId}/appointments?items_per_page={pageSize}&start_index={startIndex}"; + var requestUri = _appointmentsUriBuilder.Build(officerId, startIndex, pageSize); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index cc4c082..9270ace 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -44,8 +44,8 @@ public CompaniesHouseClient(HttpClient httpClient) _companiesHouseCompanyProfileClient = new CompaniesHouseCompanyProfileClient(_httpClient, new CompanyProfileUriBuilder()); _companiesHouseCompanyFilingHistoryClient = new CompaniesHouseCompanyFilingHistoryClient(_httpClient, new CompanyFilingHistoryUriBuilder()); _companiesHouseOfficersClient = new CompaniesHouseOfficersClient(_httpClient, new OfficersUriBuilder()); - _companiesHouseCompanyInsolvencyInformationClient = new CompaniesHouseCompanyInsolvencyInformationClient(_httpClient); - _companiesHouseCompanyAppointmentsClient = new CompaniesHouseAppointmentsClient(_httpClient); + _companiesHouseCompanyInsolvencyInformationClient = new CompaniesHouseCompanyInsolvencyInformationClient(_httpClient, new CompanyInsolvencyInformationUriBuilder()); + _companiesHouseCompanyAppointmentsClient = new CompaniesHouseAppointmentsClient(_httpClient, new AppointmentsUriBuilder()); _companiesHousePersonsWithSignificantControlClient = new CompaniesHousePersonsWithSignificantControlClient(_httpClient, new PersonsWithSignificantControlBuilder()); _companiesHouseChargesClient = new CompaniesHouseChargesClient(_httpClient, new ChargesUriBuilder()); _companiesHouseRegisteredOfficeAddressClient = new CompaniesHouseRegisteredOfficeAddressClient(_httpClient, new RegisteredOfficeAddressUriBuilder()); diff --git a/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs index 7b11202..464e697 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Insolvency; +using CompaniesHouse.UriBuilders; namespace CompaniesHouse { @@ -10,15 +11,17 @@ namespace CompaniesHouse public class CompaniesHouseCompanyInsolvencyInformationClient : ICompaniesHouseCompanyInsolvencyInformationClient { private readonly HttpClient _httpClient; + private readonly ICompanyInsolvencyInformationUriBuilder _uriBuilder; - public CompaniesHouseCompanyInsolvencyInformationClient(HttpClient httpClient) + public CompaniesHouseCompanyInsolvencyInformationClient(HttpClient httpClient, ICompanyInsolvencyInformationUriBuilder uriBuilder) { _httpClient = httpClient; + _uriBuilder = uriBuilder; } - public async Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default (CancellationToken)) + public async Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) { - var requestUri = $"company/{companyNumber}/insolvency"; + var requestUri = _uriBuilder.Build(companyNumber); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); diff --git a/src/CompaniesHouse/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index 209a2a9..37956c5 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -14,7 +14,8 @@ public interface ICompaniesHouseClient : ICompaniesHouseCompanyInsolvencyInformationClient, ICompaniesHouseAppointmentsClient, ICompaniesHousePersonsWithSignificantControlClient, - ICompaniesHouseChargesClient + ICompaniesHouseChargesClient, + ICompaniesHouseRegisteredOfficeAddressClient { } diff --git a/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs b/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs index b2a3596..068eba4 100644 --- a/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs @@ -4,8 +4,8 @@ namespace CompaniesHouse { - internal interface ICompaniesHouseRegisteredOfficeAddressClient + public interface ICompaniesHouseRegisteredOfficeAddressClient { - Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken); + Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs b/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs index b523bde..e473304 100644 --- a/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs +++ b/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs @@ -12,8 +12,22 @@ namespace CompaniesHouse.JsonConverters /// public sealed class EnumArrayOrSingleJsonConverterFactory : JsonConverterFactory { - public override bool CanConvert(Type typeToConvert) => - typeToConvert.IsArray && typeToConvert.GetElementType() is { IsEnum: true }; + public override bool CanConvert(Type typeToConvert) + { + if (!typeToConvert.IsArray) + { + return false; + } + + var elementType = typeToConvert.GetElementType(); + if (elementType is null) + { + return false; + } + + return elementType.IsEnum + || Attribute.IsDefined(elementType, typeof(JsonConverterAttribute), inherit: false); + } public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { @@ -22,10 +36,9 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer return (JsonConverter)Activator.CreateInstance(converterType)!; } - private sealed class EnumArrayOrSingleJsonConverter : JsonConverter - where TEnum : struct, Enum + private sealed class EnumArrayOrSingleJsonConverter : JsonConverter { - public override TEnum[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TElement[]? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) { @@ -34,21 +47,21 @@ private sealed class EnumArrayOrSingleJsonConverter : JsonConverter(); + var items = new System.Collections.Generic.List(); while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) { - items.Add(JsonSerializer.Deserialize(ref reader, options)); + items.Add(JsonSerializer.Deserialize(ref reader, options)!); } return items.ToArray(); } - var value = JsonSerializer.Deserialize(ref reader, options); + var value = JsonSerializer.Deserialize(ref reader, options)!; return new[] { value }; } - public override void Write(Utf8JsonWriter writer, TEnum[] value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TElement[] value, JsonSerializerOptions options) { writer.WriteStartArray(); diff --git a/src/CompaniesHouse/Response/Appointments/AppointedTo.cs b/src/CompaniesHouse/Response/Appointments/AppointedTo.cs index 58a709e..18d5e9d 100644 --- a/src/CompaniesHouse/Response/Appointments/AppointedTo.cs +++ b/src/CompaniesHouse/Response/Appointments/AppointedTo.cs @@ -5,13 +5,13 @@ namespace CompaniesHouse.Response.Appointments public class AppointedTo { [JsonPropertyName("company_status")] - public string CompanyStatus { get; set; } + public CompanyStatus CompanyStatus { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string? CompanyNumber { get; set; } [JsonPropertyName("company_name")] - public string CompanyName { get; set; } + public string? CompanyName { get; set; } } } diff --git a/src/CompaniesHouse/Response/Appointments/Appointment.cs b/src/CompaniesHouse/Response/Appointments/Appointment.cs index 690a6fe..d56e255 100644 --- a/src/CompaniesHouse/Response/Appointments/Appointment.cs +++ b/src/CompaniesHouse/Response/Appointments/Appointment.cs @@ -6,34 +6,46 @@ namespace CompaniesHouse.Response.Appointments { public class Appointment { + [JsonPropertyName("etag")] + public string? ETag { get; set; } + [JsonPropertyName("officer_role")] public OfficerRole OfficerRole { get; set; } [JsonPropertyName("name_elements")] - public NameElements NameElements { get; set; } + public NameElements? NameElements { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("appointed_to")] - public AppointedTo Appointed { get; set; } + public AppointedTo? Appointed { get; set; } [JsonPropertyName("nationality")] - public string Nationality { get; set; } + public string? Nationality { get; set; } [JsonPropertyName("country_of_residence")] - public string CountryOfResidence { get; set; } + public string? CountryOfResidence { get; set; } [JsonPropertyName("occupation")] - public string Occupation { get; set; } + public string? Occupation { get; set; } [JsonPropertyName("address")] - public Address Address { get; set; } + public Address? Address { get; set; } [JsonPropertyName("appointed_on")] public DateTime? AppointedOn { get; set; } [JsonPropertyName("resigned_on")] public DateTime? ResignedOn { get; set; } + + [JsonPropertyName("is_pre_1992_appointment")] + public bool? IsPre1992Appointment { get; set; } + + [JsonPropertyName("identification")] + public OfficerIdentification? Identification { get; set; } + + [JsonPropertyName("links")] + public AppointmentLinks? Links { get; set; } } } diff --git a/src/CompaniesHouse/Response/Appointments/AppointmentLinks.cs b/src/CompaniesHouse/Response/Appointments/AppointmentLinks.cs new file mode 100644 index 0000000..a13949c --- /dev/null +++ b/src/CompaniesHouse/Response/Appointments/AppointmentLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Appointments +{ + public class AppointmentLinks + { + [JsonPropertyName("company")] + public string? Company { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Appointments/Appointments.cs b/src/CompaniesHouse/Response/Appointments/Appointments.cs index b66ba3a..6378fa6 100644 --- a/src/CompaniesHouse/Response/Appointments/Appointments.cs +++ b/src/CompaniesHouse/Response/Appointments/Appointments.cs @@ -7,20 +7,43 @@ namespace CompaniesHouse.Response.Appointments { public class Appointments { + [JsonPropertyName("active_count")] + public int ActiveCount { get; set; } + + [JsonPropertyName("etag")] + public string? ETag { get; set; } + + [JsonPropertyName("inactive_count")] + public int InactiveCount { get; set; } + [JsonPropertyName("total_results")] public int TotalResults { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string? Kind { get; set; } [JsonPropertyName("is_corporate_officer")] public bool IsCorporateOfficer { get; set; } [JsonPropertyName("date_of_birth")] - public DateOfBirth DateOfBirth { get; set; } + public DateOfBirth? DateOfBirth { get; set; } [JsonPropertyName("items")] - public Appointment[] Items { get; set; } + public Appointment[]? Items { get; set; } + + [JsonPropertyName("items_per_page")] + public int ItemsPerPage { get; set; } + + [JsonPropertyName("links")] + public AppointmentsLinks? Links { get; set; } + + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("resigned_count")] + public int ResignedCount { get; set; } + [JsonPropertyName("start_index")] + public int StartIndex { get; set; } } } diff --git a/src/CompaniesHouse/Response/Appointments/AppointmentsLinks.cs b/src/CompaniesHouse/Response/Appointments/AppointmentsLinks.cs new file mode 100644 index 0000000..d1c69b1 --- /dev/null +++ b/src/CompaniesHouse/Response/Appointments/AppointmentsLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Appointments +{ + public class AppointmentsLinks + { + [JsonPropertyName("self")] + public string? Self { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/AssetsCeasedReleased.cs b/src/CompaniesHouse/Response/AssetsCeasedReleased.cs deleted file mode 100644 index df05258..0000000 --- a/src/CompaniesHouse/Response/AssetsCeasedReleased.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum AssetsCeasedReleased - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "property-ceased-to-belong")] - PropertyCeasedToBelong, - - [EnumMember(Value = "part-property-release-and-ceased-to-belong")] - PartPropertyReleaseAndCeasedToBelong, - - [EnumMember(Value = "part-property-released")] - PartPropertyReleased, - - [EnumMember(Value = "part-property-ceased-to-belong")] - PartPropertyCeasedToBelong, - - [EnumMember(Value = "whole-property-released")] - WholePropertyReleased, - - [EnumMember(Value = "multiple-filings")] - MultipleFilings, - - [EnumMember(Value = "whole-property-released-and-ceased-to-belong")] - WholePropertyReleasedAndCeasedToBelong - } -} diff --git a/src/CompaniesHouse/Response/ChargeStatus.cs b/src/CompaniesHouse/Response/ChargeStatus.cs deleted file mode 100644 index 8c07f38..0000000 --- a/src/CompaniesHouse/Response/ChargeStatus.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum ChargeStatus - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "outstanding")] - Outstanding, - - [EnumMember(Value = "fully-satisfied")] - FullySatisfied, - - [EnumMember(Value = "part-satisfied")] - PartSatisfied, - - [EnumMember(Value = "satisfied")] - Satisfied, - } -} diff --git a/src/CompaniesHouse/Response/Charges/Charge.cs b/src/CompaniesHouse/Response/Charges/Charge.cs index c1c5b7a..1a0c9af 100644 --- a/src/CompaniesHouse/Response/Charges/Charge.cs +++ b/src/CompaniesHouse/Response/Charges/Charge.cs @@ -13,7 +13,7 @@ public class Charge public AssetsCeasedReleased AssetsCeasedReleased { get; set; } [JsonPropertyName("charge_code")] - public string ChargeCode { get; set; } + public string? ChargeCode { get; set; } [JsonPropertyName("charge_number")] public int? ChargeNumber { get; set; } @@ -31,25 +31,25 @@ public class Charge public DateTime? DeliveredOn { get; set; } [JsonPropertyName("etag")] - public string Etag { get; set; } + public string? Etag { get; set; } [JsonPropertyName("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonPropertyName("insolvency_cases")] - public InsolvencyCase[] InsolvencyCases { get; set; } + public InsolvencyCase[]? InsolvencyCases { get; set; } [JsonPropertyName("links")] - public Links Links { get; set; } + public Links? Links { get; set; } [JsonPropertyName("more_than_four_persons_entitled")] public bool? MoreThanFourPersonsEntitled { get; set; } [JsonPropertyName("particulars")] - public Particular Particular { get; set; } + public Particular? Particular { get; set; } [JsonPropertyName("persons_entitled")] - public PersonEntitled[] PersonsEntitled { get; set; } + public PersonEntitled[]? PersonsEntitled { get; set; } [JsonPropertyName("resolved_on")] public DateTime? ResolvedOn { get; set; } @@ -58,15 +58,15 @@ public class Charge public DateTime? SatisfiedOn { get; set; } [JsonPropertyName("scottish_alterations")] - public ScottishAlterations ScottishAlterations { get; set; } + public ScottishAlterations? ScottishAlterations { get; set; } [JsonPropertyName("secured_details")] - public SecuredDetail SecuredDetail { get; set; } + public SecuredDetail? SecuredDetail { get; set; } [JsonPropertyName("status")] public ChargeStatus Status { get; set; } [JsonPropertyName("transactions")] - public Transaction[] Transactions { get; set; } + public Transaction[]? Transactions { get; set; } } } diff --git a/src/CompaniesHouse/Response/Charges/Charges.cs b/src/CompaniesHouse/Response/Charges/Charges.cs index 0d2fbb4..5ebc51d 100644 --- a/src/CompaniesHouse/Response/Charges/Charges.cs +++ b/src/CompaniesHouse/Response/Charges/Charges.cs @@ -4,22 +4,22 @@ namespace CompaniesHouse.Response.Charges { public class Charges { - [JsonPropertyName("Etag")] - public string Etag { get; set; } - + [JsonPropertyName("etag")] + public string? Etag { get; set; } + [JsonPropertyName("items")] - public Charge[] Items { get; set; } - + public Charge[]? Items { get; set; } + [JsonPropertyName("part_satisfied_count")] public int? PartSatisfiedCount { get; set; } - + [JsonPropertyName("satisfied_count")] public int? SatisfiedCount { get; set; } - + [JsonPropertyName("total_count")] public int? TotalCount { get; set; } - - [JsonPropertyName("unfiletered_count")] - public int? UnfileteredCount { get; set; } + + [JsonPropertyName("unfiltered_count")] + public int? UnfilteredCount { get; set; } } } diff --git a/src/CompaniesHouse/Response/Charges/Classification.cs b/src/CompaniesHouse/Response/Charges/Classification.cs index f38d4f8..6c363f0 100644 --- a/src/CompaniesHouse/Response/Charges/Classification.cs +++ b/src/CompaniesHouse/Response/Charges/Classification.cs @@ -6,8 +6,8 @@ namespace CompaniesHouse.Response.Charges public class Classification { [JsonPropertyName("description")] - public string Description { get; set; } - + public string? Description { get; set; } + [JsonPropertyName("type")] public ClassificationChargeType Type { get; set; } } diff --git a/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs b/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs index bf946be..0b69281 100644 --- a/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs +++ b/src/CompaniesHouse/Response/Charges/InsolvencyCase.cs @@ -5,11 +5,11 @@ namespace CompaniesHouse.Response.Charges public class InsolvencyCase { [JsonPropertyName("case_number")] - public string CaseNumber { get; set; } - + public string? CaseNumber { get; set; } + [JsonPropertyName("links")] - public InsolvencyCaseLinks Links { get; set; } - + public InsolvencyCaseLinks? Links { get; set; } + [JsonPropertyName("transaction_id")] public long? TransactionId { get; set; } } diff --git a/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs b/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs index 4259240..71ccd1b 100644 --- a/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs +++ b/src/CompaniesHouse/Response/Charges/InsolvencyCaseLinks.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Charges public class InsolvencyCaseLinks { [JsonPropertyName("case")] - public string Case { get; set; } + public string? Case { get; set; } } } diff --git a/src/CompaniesHouse/Response/Charges/Links.cs b/src/CompaniesHouse/Response/Charges/Links.cs index c079fcd..c13d808 100644 --- a/src/CompaniesHouse/Response/Charges/Links.cs +++ b/src/CompaniesHouse/Response/Charges/Links.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Charges public class Links { [JsonPropertyName("self")] - public string Self { get; set; } + public string? Self { get; set; } } } diff --git a/src/CompaniesHouse/Response/Charges/Particular.cs b/src/CompaniesHouse/Response/Charges/Particular.cs index e66d7bf..f336eb1 100644 --- a/src/CompaniesHouse/Response/Charges/Particular.cs +++ b/src/CompaniesHouse/Response/Charges/Particular.cs @@ -17,8 +17,8 @@ public class Particular [JsonPropertyName("contains_negative_pledge")] public bool? ContainsNegativePledge { get; set; } - [JsonPropertyName("description")] - public string Description { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } [JsonPropertyName("floating_charge_covers_all")] public bool? FloatingChargeCoversAll { get; set; } diff --git a/src/CompaniesHouse/Response/Charges/PersonEntitled.cs b/src/CompaniesHouse/Response/Charges/PersonEntitled.cs index 1666008..724fc34 100644 --- a/src/CompaniesHouse/Response/Charges/PersonEntitled.cs +++ b/src/CompaniesHouse/Response/Charges/PersonEntitled.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Charges public class PersonEntitled { [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } } } diff --git a/src/CompaniesHouse/Response/Charges/SecuredDetail.cs b/src/CompaniesHouse/Response/Charges/SecuredDetail.cs index 3b3f2e1..2770d2a 100644 --- a/src/CompaniesHouse/Response/Charges/SecuredDetail.cs +++ b/src/CompaniesHouse/Response/Charges/SecuredDetail.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse.Response.Charges public class SecuredDetail { [JsonPropertyName("description")] - public string Description { get; set; } + public string? Description { get; set; } [JsonPropertyName("type")] public SecuredDetailType Type { get; set; } diff --git a/src/CompaniesHouse/Response/Charges/Transaction.cs b/src/CompaniesHouse/Response/Charges/Transaction.cs index 8456867..5801dca 100644 --- a/src/CompaniesHouse/Response/Charges/Transaction.cs +++ b/src/CompaniesHouse/Response/Charges/Transaction.cs @@ -9,15 +9,15 @@ public class Transaction public DateTime? DeliveredOn { get; set; } [JsonPropertyName("filing_type")] - public string FilingType { get; set; } + public string? FilingType { get; set; } [JsonPropertyName("insolvency_case_number")] public int? InsolvencyCaseNumber { get; set; } [JsonPropertyName("links")] - public TransactionLinks Links { get; set; } + public TransactionLinks? Links { get; set; } [JsonPropertyName("transaction_id")] - public int? TransactionId { get; set; } + public long? TransactionId { get; set; } } } diff --git a/src/CompaniesHouse/Response/Charges/TransactionLinks.cs b/src/CompaniesHouse/Response/Charges/TransactionLinks.cs index 38b75ca..1ed5f02 100644 --- a/src/CompaniesHouse/Response/Charges/TransactionLinks.cs +++ b/src/CompaniesHouse/Response/Charges/TransactionLinks.cs @@ -5,9 +5,9 @@ namespace CompaniesHouse.Response.Charges public class TransactionLinks { [JsonPropertyName("filing")] - public string Filing { get; set; } - + public string? Filing { get; set; } + [JsonPropertyName("insolvency_case")] - public string InsolvencyCase { get; set; } + public string? InsolvencyCase { get; set; } } } diff --git a/src/CompaniesHouse/Response/ClassificationChargeType.cs b/src/CompaniesHouse/Response/ClassificationChargeType.cs deleted file mode 100644 index 128a4fd..0000000 --- a/src/CompaniesHouse/Response/ClassificationChargeType.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum ClassificationChargeType - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value="charge-description")] - ChargeDescription, - - [EnumMember(Value="nature-of-charge")] - NatureOfCharge - } -} diff --git a/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs b/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs index c353435..4318330 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs @@ -9,7 +9,7 @@ public class CompanyFilingHistory public FilingHistoryStatus HistoryStatus { get; set; } [JsonPropertyName("etag")] - public string ETag { get; set; } + public string? ETag { get; set; } [JsonPropertyName("total_count")] public int TotalCount { get; set; } @@ -21,9 +21,9 @@ public class CompanyFilingHistory public int StartIndex { get; set; } [JsonPropertyName("items")] - public FilingHistoryItem[] Items { get; set; } + public FilingHistoryItem[]? Items { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string? Kind { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs index 912e974..0a1234a 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs @@ -15,19 +15,22 @@ public class FilingHistoryItem : IDescriptable public FilingSubcategory[] Subcategory { get; set; } [JsonPropertyName("transaction_id")] - public string TransactionId { get; set; } + public string? TransactionId { get; set; } [JsonPropertyName("type")] - public string FilingType { get; set; } + public string? FilingType { get; set; } [JsonPropertyName("barcode")] - public string Barcode { get; set; } + public string? Barcode { get; set; } [JsonPropertyName("date")] public DateTime? DateOfProcessing { get; set; } + [JsonPropertyName("action_date")] + public DateTime? ActionDate { get; set; } + [JsonPropertyName("description")] - public string Description { get; set; } + public string? Description { get; set; } [JsonInclude] [JsonPropertyName("description_values")] @@ -40,16 +43,16 @@ public class FilingHistoryItem : IDescriptable public bool? PaperFiled { get; set; } [JsonPropertyName("annotations")] - public FilingHistoryItemAnnotation[] Annotations { get; set; } + public FilingHistoryItemAnnotation[]? Annotations { get; set; } [JsonPropertyName("associated_filings")] - public FilingHistoryItemAssociatedFiling[] AssociatedFilings { get; set; } + public FilingHistoryItemAssociatedFiling[]? AssociatedFilings { get; set; } [JsonPropertyName("resolutions")] - public FilingHistoryItemResolution[] Resolutions { get; set; } + public FilingHistoryItemResolution[]? Resolutions { get; set; } [JsonPropertyName("links")] - public Links Links { get; set; } + public Links? Links { get; set; } public string GetDescription(string format) { diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs index 5229674..323145d 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs @@ -14,7 +14,7 @@ public class FilingHistoryItemAnnotation : IDescriptable public DateTime? DateOfAnnotation { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } + public string? Description { get; set; } [JsonInclude] [JsonPropertyName("description_values")] diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs index c99b616..896597a 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling @@ -7,15 +8,16 @@ namespace CompaniesHouse.Response.CompanyFiling public class FilingHistoryItemAssociatedFiling { [JsonPropertyName("type")] - public string FilingType { get; set; } + public string? FilingType { get; set; } [JsonPropertyName("date")] public DateTime? Date { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } + public string? Description { get; set; } + [JsonInclude] [JsonPropertyName("description_values")] - private Dictionary DescriptionValues { get; set; } + private JsonElement? DescriptionValues { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs index eeb6edd..9afbdb7 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs @@ -15,16 +15,16 @@ public class FilingHistoryItemResolution : IDescriptable public FilingSubcategory[] Subcategory { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } + public string? Description { get; set; } [JsonPropertyName("document_id")] - public string DocumentId { get; set; } + public string? DocumentId { get; set; } [JsonPropertyName("receive_date")] public DateTime? DateOfProcessing { get; set; } [JsonPropertyName("type")] - public string ResolutionType { get; set; } + public string? ResolutionType { get; set; } [JsonInclude] [JsonPropertyName("description_values")] diff --git a/src/CompaniesHouse/Response/CompanyFiling/Links.cs b/src/CompaniesHouse/Response/CompanyFiling/Links.cs index f0d86e0..2c5c426 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/Links.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/Links.cs @@ -5,9 +5,9 @@ namespace CompaniesHouse.Response.CompanyFiling public class Links { [JsonPropertyName("self")] - public string Self { get; set; } + public string? Self { get; set; } [JsonPropertyName("document_metadata")] - public string DocumentMetaData { get; set; } + public string? DocumentMetaData { get; set; } } } diff --git a/src/CompaniesHouse/Response/Document/DocumentMetadata.cs b/src/CompaniesHouse/Response/Document/DocumentMetadata.cs index c719640..7e1d187 100644 --- a/src/CompaniesHouse/Response/Document/DocumentMetadata.cs +++ b/src/CompaniesHouse/Response/Document/DocumentMetadata.cs @@ -6,24 +6,26 @@ namespace CompaniesHouse.Response.Document public class DocumentMetadata { [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string? CompanyNumber { get; set; } [JsonPropertyName("barcode")] - public string Barcode { get; set; } + public string? Barcode { get; set; } [JsonPropertyName("significant_date")] - public object SignificantDate { get; set; } + public DateTime? SignificantDate { get; set; } [JsonPropertyName("significant_date_type")] - public string SignificantDateType { get; set; } + public string? SignificantDateType { get; set; } [JsonPropertyName("category")] - public string Category { get; set; } + public string? Category { get; set; } [JsonPropertyName("pages")] public int Pages { get; set; } + [JsonPropertyName("filename")] + public string? Filename { get; set; } [JsonPropertyName("created_at")] - public string CreatedAt { get; set; } + public DateTime? CreatedAt { get; set; } [JsonPropertyName("etag")] - public string Etag { get; set; } + public string? Etag { get; set; } [JsonPropertyName("links")] - public Links Links { get; set; } + public Links? Links { get; set; } [JsonPropertyName("resources")] - public Dictionary Resources { get; set; } + public Dictionary? Resources { get; set; } } } diff --git a/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs b/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs index 29a64bc..9e2976f 100644 --- a/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs +++ b/src/CompaniesHouse/Response/Document/DocumentMetadataContentLength.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Document public class DocumentMetadataContentLength { [JsonPropertyName("content_length")] - public int ContentLength { get; set; } + public long ContentLength { get; set; } } } diff --git a/src/CompaniesHouse/Response/Document/Links.cs b/src/CompaniesHouse/Response/Document/Links.cs index be07db8..b5bf85f 100644 --- a/src/CompaniesHouse/Response/Document/Links.cs +++ b/src/CompaniesHouse/Response/Document/Links.cs @@ -5,8 +5,8 @@ namespace CompaniesHouse.Response.Document public class Links { [JsonPropertyName("self")] - public string Self { get; set; } + public string? Self { get; set; } [JsonPropertyName("document")] - public string Document { get; set; } + public string? Document { get; set; } } } diff --git a/src/CompaniesHouse/Response/FilingCategory.cs b/src/CompaniesHouse/Response/FilingCategory.cs deleted file mode 100644 index 0a00ee8..0000000 --- a/src/CompaniesHouse/Response/FilingCategory.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum FilingCategory - { - None = 0, - - [EnumMember(Value = "auditors")] - Auditors, - - [EnumMember(Value = "accounts")] - Accounts, - - [EnumMember(Value = "address")] - Address, - - [EnumMember(Value = "annual-return")] - AnnualReturn, - - [EnumMember(Value = "capital")] - Capital, - - [EnumMember(Value = "gazette")] - Gazette, - - [EnumMember(Value = "change-of-name")] - ChangeOfName, - - [EnumMember(Value = "incorporation")] - Incorporation, - - [EnumMember(Value = "liquidation")] - Liquidation, - - [EnumMember(Value = "miscellaneous")] - Miscellaneous, - - [EnumMember(Value = "mortgage")] - Mortgage, - - [EnumMember(Value = "officers")] - Officers, - - [EnumMember(Value = "resolution")] - Resolution, - - [EnumMember(Value = "change-of-constitution")] - ChangeOfConstitution, - - [EnumMember(Value = "document-replacement")] - DocumentReplacement, - - [EnumMember(Value = "insolvency")] - Insolvency, - - [EnumMember(Value = "confirmation-statement")] - ConfirmationStatement, - - [EnumMember(Value = "persons-with-significant-control")] - PersonsWithSignificantControl, - - [EnumMember(Value = "historical")] - Historical, - - [EnumMember(Value = "dissolution")] - Dissolution, - - [EnumMember(Value = "restoration")] - Restoration, - - [EnumMember(Value = "return")] - Return, - - [EnumMember(Value = "other")] - Other, - - [EnumMember(Value = "court-order")] - CourtOrder, - - [EnumMember(Value = "reregistration")] - ReRegistration, - - [EnumMember(Value = "certificate")] - Certificate, - } -} diff --git a/src/CompaniesHouse/Response/FilingHistoryStatus.cs b/src/CompaniesHouse/Response/FilingHistoryStatus.cs deleted file mode 100644 index 81fe3ae..0000000 --- a/src/CompaniesHouse/Response/FilingHistoryStatus.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum FilingHistoryStatus - { - None = 0, - - [EnumMember(Value = "filing-history-available")] - FilingHistoryAvailable, - - [EnumMember(Value = "filing-history-not-available-invalid-format")] - InvalidFormat, - - [EnumMember(Value = "filing-history-available-no-images-limited-partnership-from-1988")] - FilingHistoryAvailableNoImagesLimitedPartnershipFrom1988, - - [EnumMember(Value = "filing-history-available-assurance-company-before-2004")] - FilingHistoryAvailableAssuranceCompanyBefore2004, - - [EnumMember(Value = "filing-history-available-limited-partnership-from-2014")] - FilingHistoryAvailableLimitedPartnershipFrom2014, - - [EnumMember(Value = "filing-history-not-available-industrial-and-provident-society")] - FilingHistoryNotAvailableIndustrialAndProvidentSociety, - - [EnumMember(Value = "filing-history-not-available-limited-partnership-before-1988")] - FilingHistoryNotAvailableLimitedPartnershipBefore1988, - - [EnumMember(Value = "filing-history-not-available-royal-charter")] - FilingHistoryNotAvailableRoyalCharter, - - [EnumMember(Value = "filing-history-not-available-scottish-industrial-and-provident-society")] - FilingHistoryNotAvailableScottishIndustrialAndProvidentSociety, - - [EnumMember(Value = "filing-history-not-available-northern-ireland-industrial-and-provident-society")] - FilingHistoryNotAvailableNorthernIrelandIndustrialAndProvidentSociety, - - [EnumMember(Value = "filing-history-not-available-unknown-prefix")] - FilingHistoryNotAvailableUnknownPrefix, - } -} diff --git a/src/CompaniesHouse/Response/FilingSubcategory.cs b/src/CompaniesHouse/Response/FilingSubcategory.cs deleted file mode 100644 index 63b4532..0000000 --- a/src/CompaniesHouse/Response/FilingSubcategory.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum FilingSubcategory - { - None = 0, - - [EnumMember(Value = "annual-return")] - AnnualReturn, - - [EnumMember(Value = "resolution")] - Resolution, - - [EnumMember(Value = "change")] - Change, - - [EnumMember(Value = "create")] - Create, - - [EnumMember(Value = "certificate")] - Certificate, - - [EnumMember(Value = "appointments")] - Appointments, - - [EnumMember(Value = "satisfy")] - Satisfy, - - [EnumMember(Value = "termination")] - Termination, - - [EnumMember(Value = "release-cease")] - ReleaseCease, - - [EnumMember(Value = "voluntary")] - Voluntary, - - [EnumMember(Value = "administration")] - Administration, - - [EnumMember(Value = "compulsory")] - Compulsory, - - [EnumMember(Value = "court-order")] - CourtOrder, - - [EnumMember(Value = "other")] - Other, - - [EnumMember(Value = "notifications")] - Notifications, - - [EnumMember(Value = "officers")] - Officers, - - [EnumMember(Value = "document-replacement")] - DocumentReplacement, - - [EnumMember(Value = "statements")] - Statements, - - [EnumMember(Value = "voluntary-arrangement")] - VoluntaryArrangement, - - [EnumMember(Value = "alter")] - Alter, - - [EnumMember(Value = "register")] - Register, - - [EnumMember(Value = "receiver")] - Receiver, - - [EnumMember(Value = "voluntary-arrangement-moratoria")] - VoluntaryArrangementMoratoria, - - [EnumMember(Value = "acquire")] - Acquire, - - [EnumMember(Value = "trustee")] - Trustee, - - [EnumMember(Value = "mortgage")] - Mortgage, - - [EnumMember(Value = "transfer")] - Transfer, - - [EnumMember(Value = "debenture")] - Debenture, - } -} diff --git a/src/CompaniesHouse/Response/Insolvency/Address.cs b/src/CompaniesHouse/Response/Insolvency/Address.cs index c7ea930..8c85715 100644 --- a/src/CompaniesHouse/Response/Insolvency/Address.cs +++ b/src/CompaniesHouse/Response/Insolvency/Address.cs @@ -5,21 +5,21 @@ namespace CompaniesHouse.Response.Insolvency public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } + public string? AddressLine1 { get; set; } [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } + public string? AddressLine2 { get; set; } [JsonPropertyName("country")] - public string Country { get; set; } + public string? Country { get; set; } [JsonPropertyName("locality")] - public string Locality { get; set; } + public string? Locality { get; set; } [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } + public string? PostalCode { get; set; } [JsonPropertyName("region")] - public string Region { get; set; } + public string? Region { get; set; } } } diff --git a/src/CompaniesHouse/Response/Insolvency/Case.cs b/src/CompaniesHouse/Response/Insolvency/Case.cs index 3db1ea8..702697a 100644 --- a/src/CompaniesHouse/Response/Insolvency/Case.cs +++ b/src/CompaniesHouse/Response/Insolvency/Case.cs @@ -5,21 +5,21 @@ namespace CompaniesHouse.Response.Insolvency public class Case { [JsonPropertyName("dates")] - public CaseDate[] Dates { get; set; } + public CaseDate[]? Dates { get; set; } [JsonPropertyName("links")] - public Links Links { get; set; } + public Links? Links { get; set; } [JsonPropertyName("notes")] - public string[] Notes { get; set; } + public string[]? Notes { get; set; } [JsonPropertyName("number")] public int Number { get; set; } [JsonPropertyName("practitioners")] - public Practitioner[] Practitioners { get; set; } + public Practitioner[]? Practitioners { get; set; } [JsonPropertyName("type")] - public string Type { get; set; } + public InsolvencyCaseType Type { get; set; } } } diff --git a/src/CompaniesHouse/Response/Insolvency/CaseDate.cs b/src/CompaniesHouse/Response/Insolvency/CaseDate.cs index 0bbeaf2..23aa357 100644 --- a/src/CompaniesHouse/Response/Insolvency/CaseDate.cs +++ b/src/CompaniesHouse/Response/Insolvency/CaseDate.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse.Response.Insolvency public class CaseDate { [JsonPropertyName("date")] - public DateTime Date { get; set; } + public DateTime? Date { get; set; } [JsonPropertyName("type")] public CaseDateType Type { get; set; } diff --git a/src/CompaniesHouse/Response/Insolvency/CaseDateType.cs b/src/CompaniesHouse/Response/Insolvency/CaseDateType.cs deleted file mode 100644 index 8cd9841..0000000 --- a/src/CompaniesHouse/Response/Insolvency/CaseDateType.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.Insolvency -{ - public enum CaseDateType - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "instrumented-on")] - InstrumentedOn, - - [EnumMember(Value = "administration-started-on")] - AdministrationStartedOn, - - [EnumMember(Value = "administration-discharged-on")] - AdministrationDischargedOn, - - [EnumMember(Value = "administration-ended-on")] - AdministrationEndedOn, - - [EnumMember(Value = "concluded-winding-up-on")] - ConcludedWindingUpOn, - - [EnumMember(Value = "petitioned-on")] - PetitionedOn, - - [EnumMember(Value = "ordered-to-wind-up-on")] - OrderedToWindUpOn, - - [EnumMember(Value = "due-to-be-dissolved-on")] - DueToBeDissolvedOn, - - [EnumMember(Value = "case-end-on")] - CaseEndOn, - - [EnumMember(Value = "wound-up-on")] - WoundUpOn, - - [EnumMember(Value = "voluntary-arrangement-started-on")] - VoluntaryArrangementStartedOn, - - [EnumMember(Value = "voluntary-arrangement-ended-on")] - VoluntaryArrangementEndedOn, - - [EnumMember(Value = "moratorium-started-on")] - MoratoriumStartedOn, - - [EnumMember(Value = "moratorium-ended-on")] - MoratoriumEndedOn, - - [EnumMember(Value = "declaration-solvent-on")] - DeclarationSolventOn, - - [EnumMember(Value = "dissolved-on")] - DissolvedOn, - } -} diff --git a/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs b/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs index 3f289a6..4a59571 100644 --- a/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs +++ b/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Response.Insolvency public class CompanyInsolvencyInformation { [JsonPropertyName("cases")] - public Case[] Cases { get; set; } + public Case[]? Cases { get; set; } [JsonPropertyName("etag")] - public string Etag { get; set; } + public string? Etag { get; set; } [JsonPropertyName("status")] - public InsolvencyStatus[] Status { get; set; } + public InsolvencyStatus[]? Status { get; set; } } } diff --git a/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs b/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs deleted file mode 100644 index cec9473..0000000 --- a/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.Insolvency -{ - public enum InsolvencyStatus - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "live-propopsed-transfer-from-gb")] - LivepropopsedTransferFromGb, - - [EnumMember(Value = "voluntary-arrangement")] - VoluntaryArrangement, - - [EnumMember(Value = "voluntary-arrangement-receivership")] - VoluntaryArrangementReceivership, - - [EnumMember(Value = "administration-order")] - AdministrationOrder, - - [EnumMember(Value = "live-receiver-manager-on-at-least-one-charge")] - LiveReceiverManagerOnAtLeastOneCharge, - - [EnumMember(Value = "administrative-receiver")] - AdministrativeReceiver, - - [EnumMember(Value = "receiver-manager-or-administrative-receiver")] - ReceiverManagerOrAdministrativeReceiver, - - [EnumMember(Value = "receiver-manager")] - ReceiverManager, - - [EnumMember(Value = "receivership")] - Receivership, - - [EnumMember(Value = "in-administration")] - InAdministration, - - [EnumMember(Value = "liquidation")] - Liquidation, - } -} diff --git a/src/CompaniesHouse/Response/Insolvency/Links.cs b/src/CompaniesHouse/Response/Insolvency/Links.cs index 3d38762..5acb0c8 100644 --- a/src/CompaniesHouse/Response/Insolvency/Links.cs +++ b/src/CompaniesHouse/Response/Insolvency/Links.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Insolvency public class Links { [JsonPropertyName("charge")] - public string Charge { get; set; } + public string? Charge { get; set; } } } diff --git a/src/CompaniesHouse/Response/Insolvency/Practitioner.cs b/src/CompaniesHouse/Response/Insolvency/Practitioner.cs index 9d2eea7..dbba3a1 100644 --- a/src/CompaniesHouse/Response/Insolvency/Practitioner.cs +++ b/src/CompaniesHouse/Response/Insolvency/Practitioner.cs @@ -6,18 +6,18 @@ namespace CompaniesHouse.Response.Insolvency public class Practitioner { [JsonPropertyName("address")] - public Address Address { get; set; } + public Address? Address { get; set; } [JsonPropertyName("appointed_on")] - public DateTime AppointedOn { get; set; } + public DateTime? AppointedOn { get; set; } [JsonPropertyName("ceased_to_act_on")] - public DateTime CeasedToActOn { get; set; } + public DateTime? CeasedToActOn { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("role")] - public string Role { get; set; } + public string? Role { get; set; } } } diff --git a/src/CompaniesHouse/Response/ParticularType.cs b/src/CompaniesHouse/Response/ParticularType.cs deleted file mode 100644 index e33491f..0000000 --- a/src/CompaniesHouse/Response/ParticularType.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum ParticularType - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "short-particulars")] - ShortParticulars, - - [EnumMember(Value = "charged-property-description")] - ChargedPropertyDescription, - - [EnumMember(Value = "charged-property-or-undertaking-description")] - ChargedPropertyOrUndertakingDescription, - - [EnumMember(Value = "brief-description")] - BriefDescription - } -} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs index 38ee7d0..61d5356 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs @@ -7,42 +7,45 @@ namespace CompaniesHouse.Response.PersonsWithSignificantControl public class PersonWithSignificantControl { [JsonPropertyName("address")] - public Address Address { get; set; } + public Address? Address { get; set; } + + [JsonPropertyName("ceased")] + public bool? Ceased { get; set; } [JsonPropertyName("ceased_on")] - public DateTime CeasedOn { get; set; } + public DateTime? CeasedOn { get; set; } [JsonPropertyName("country_of_residence")] - public string CountryOfResidence { get; set; } + public string? CountryOfResidence { get; set; } [JsonPropertyName("date_of_birth")] - public DateOfBirth DateOfBirth { get; set; } + public DateOfBirth? DateOfBirth { get; set; } [JsonPropertyName("etag")] - public string ETag { get; set; } + public string? ETag { get; set; } [JsonPropertyName("kind")] public PersonWithSignificantControlKind Kind { get; set; } [JsonPropertyName("links")] - public PersonWithSignificantControlLinks Links { get; set; } + public PersonWithSignificantControlLinks? Links { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("name_elements")] - public NameElements NameElements { get; set; } + public NameElements? NameElements { get; set; } [JsonPropertyName("nationality")] - public string Nationality { get; set; } + public string? Nationality { get; set; } [JsonPropertyName("natures_of_control")] - public PersonWithSignificantControlNatureOfControl[] NaturesOfControl { get; set; } + public PersonWithSignificantControlNatureOfControl[]? NaturesOfControl { get; set; } [JsonPropertyName("notified_on")] - public DateTime NotifiedOn { get; set; } + public DateTime? NotifiedOn { get; set; } [JsonPropertyName("identification")] - public PersonWithSignificantControlIdentification Identification { get; set; } + public PersonWithSignificantControlIdentification? Identification { get; set; } } } diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs index 94dbac3..6718bf4 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs @@ -5,17 +5,18 @@ namespace CompaniesHouse.Response.PersonsWithSignificantControl public class PersonWithSignificantControlIdentification { [JsonPropertyName("legal_authority")] - public string LegalAuthority { get; set; } + public string? LegalAuthority { get; set; } [JsonPropertyName("legal_form")] - public string LegalForm { get; set; } + public string? LegalForm { get; set; } [JsonPropertyName("place_registered")] - public string PlaceRegistered { get; set; } + public string? PlaceRegistered { get; set; } [JsonPropertyName("registration_number")] - public string RegistrationNumber { get; set; } + public string? RegistrationNumber { get; set; } - [JsonPropertyName("country_registered")] public string CountryRegistered { get; set; } + [JsonPropertyName("country_registered")] + public string? CountryRegistered { get; set; } } } diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs deleted file mode 100644 index c572bbe..0000000 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlKind.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.PersonsWithSignificantControl -{ - public enum PersonWithSignificantControlKind - { - [EnumMember(Value = "corporate-entity-person-with-significant-control")] - CorporateEntityPersonWithSignificantControl, - - [EnumMember(Value = "corporate-entity-beneficial-owner")] - CorporateEntityBeneficialOwner, - - [EnumMember(Value = "individual-person-with-significant-control")] - IndividualPersonWithSignificantControl, - - [EnumMember(Value = "individual-beneficial-owner")] - IndividualBeneficialOwner, - - [EnumMember(Value = "super-secure-person-with-significant-control")] - SuperSecurePersonWithSignificantControl, - - [EnumMember(Value = "super-secure-beneficial-owner")] - SuperSecureBeneficialOwner, - - [EnumMember(Value = "legal-person-person-with-significant-control")] - LegalPersonPersonWithSignificantControl, - - [EnumMember(Value = "legal-person-beneficial-owner")] - LegalPersonBeneficialOwner, - } -} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs index 0557596..ee43bb4 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs @@ -5,9 +5,9 @@ namespace CompaniesHouse.Response.PersonsWithSignificantControl public class PersonWithSignificantControlLinks { [JsonPropertyName("self")] - public string Self { get; set; } + public string? Self { get; set; } [JsonPropertyName("statement")] - public string Statement { get; set; } + public string? Statement { get; set; } } } diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs deleted file mode 100644 index 7225ce0..0000000 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlNatureOfControl.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.PersonsWithSignificantControl -{ - public enum PersonWithSignificantControlNatureOfControl - { - [EnumMember(Value = "ownership-of-shares-25-to-50-percent")] - OwnershipOfShares25To50Percent, - [EnumMember(Value = "ownership-of-shares-50-to-75-percent")] - OwnershipOfShares50To75Percent, - [EnumMember(Value = "ownership-of-shares-75-to-100-percent")] - OwnershipOfShares75To100Percent, - [EnumMember(Value = "ownership-of-shares-25-to-50-percent-as-trust")] - OwnershipOfShares25To50PercentAsTrust, - [EnumMember(Value = "ownership-of-shares-50-to-75-percent-as-trust")] - OwnershipOfShares50To75PercentAsTrust, - [EnumMember(Value = "ownership-of-shares-75-to-100-percent-as-trust")] - OwnershipOfShares75To100PercentAsTrust, - [EnumMember(Value = "ownership-of-shares-25-to-50-percent-as-firm")] - OwnershipOfShares25To50PercentAsFirm, - [EnumMember(Value = "ownership-of-shares-50-to-75-percent-as-firm")] - OwnershipOfShares50To75PercentAsFirm, - [EnumMember(Value = "ownership-of-shares-75-to-100-percent-as-firm")] - OwnershipOfShares75To100PercentAsFirm, - [EnumMember(Value = "ownership-of-shares-more-than-25-percent-registered-overseas-entity")] - OwnershipOfSharesMoreThan25PercentRegisteredOverseasEntity, - [EnumMember(Value = "ownership-of-shares-more-than-25-percent-as-trust-registered-overseas-entity")] - OwnershipOfSharesMoreThan25PercentAsTrustRegisteredOverseasEntity, - [EnumMember(Value = "ownership-of-shares-more-than-25-percent-as-firm-registered-overseas-entity")] - OwnershipOfSharesMoreThan25PercentAsFirmRegisteredOverseasEntity, - [EnumMember(Value = "voting-rights-25-to-50-percent")] - VotingRights25To50Percent, - [EnumMember(Value = "voting-rights-50-to-75-percent")] - VotingRights50To75Percent, - [EnumMember(Value = "voting-rights-75-to-100-percent")] - VotingRights75To100Percent, - [EnumMember(Value = "voting-rights-25-to-50-percent-as-trust")] - VotingRights25To50PercentAsTrust, - [EnumMember(Value = "voting-rights-50-to-75-percent-as-trust")] - VotingRights50To75PercentAsTrust, - [EnumMember(Value = "voting-rights-75-to-100-percent-as-trust")] - VotingRights75To100PercentAsTrust, - [EnumMember(Value = "voting-rights-25-to-50-percent-as-firm")] - VotingRights25To50PercentAsFirm, - [EnumMember(Value = "voting-rights-50-to-75-percent-as-firm")] - VotingRights50To75PercentAsFirm, - [EnumMember(Value = "voting-rights-75-to-100-percent-as-firm")] - VotingRights75To100PercentAsFirm, - [EnumMember(Value = "voting-rights-more-than-25-percent-registered-overseas-entity")] - VotingRightsMoreThan25PercentRegisteredOverseasEntity, - [EnumMember(Value = "voting-rights-more-than-25-percent-as-trust-registered-overseas-entity")] - VotingRightsMoreThan25PercentAsTrustRegisteredOverseasEntity, - [EnumMember(Value = "voting-rights-more-than-25-percent-as-firm-registered-overseas-entity")] - VotingRightsMoreThan25PercentAsFirmRegisteredOverseasEntity, - [EnumMember(Value = "right-to-appoint-and-remove-directors")] - RightToAppointAndRemoveDirectors, - [EnumMember(Value = "right-to-appoint-and-remove-directors-as-trust")] - RightToAppointAndRemoveDirectorsAsTrust, - [EnumMember(Value = "right-to-appoint-and-remove-directors-as-firm")] - RightToAppointAndRemoveDirectorsAsFirm, - [EnumMember(Value = "significant-influence-or-control")] - SignificantInfluenceOrControl, - [EnumMember(Value = "significant-influence-or-control-as-trust")] - SignificantInfluenceOrControlAsTrust, - [EnumMember(Value = "significant-influence-or-control-as-firm")] - SignificantInfluenceOrControlAsFirm, - [EnumMember(Value = "right-to-share-surplus-assets-25-to-50-percent-limited-liability-partnership")] - RightToShareSurplusAssets25To50PercentLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-50-to-75-percent-limited-liability-partnership")] - RightToShareSurplusAssets50To75PercentLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-75-to-100-percent-limited-liability-partnership")] - RightToShareSurplusAssets75To100PercentLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-25-to-50-percent-as-trust-limited-liability-partnership")] - RightToShareSurplusAssets25To50PercentAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-50-to-75-percent-as-trust-limited-liability-partnership")] - RightToShareSurplusAssets50To75PercentAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-75-to-100-percent-as-trust-limited-liability-partnership")] - RightToShareSurplusAssets75To100PercentAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-25-to-50-percent-as-firm-limited-liability-partnership")] - RightToShareSurplusAssets25To50PercentAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-50-to-75-percent-as-firm-limited-liability-partnership")] - RightToShareSurplusAssets50To75PercentAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-share-surplus-assets-75-to-100-percent-as-firm-limited-liability-partnership")] - RightToShareSurplusAssets75To100PercentAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-25-to-50-percent-limited-liability-partnership")] - VotingRights25To50PercentLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-50-to-75-percent-limited-liability-partnership")] - VotingRights50To75PercentLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-75-to-100-percent-limited-liability-partnership")] - VotingRights75To100PercentLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-25-to-50-percent-as-trust-limited-liability-partnership")] - VotingRights25To50PercentAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-50-to-75-percent-as-trust-limited-liability-partnership")] - VotingRights50To75PercentAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-75-to-100-percent-as-trust-limited-liability-partnership")] - VotingRights75To100PercentAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-25-to-50-percent-as-firm-limited-liability-partnership")] - VotingRights25To50PercentAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-50-to-75-percent-as-firm-limited-liability-partnership")] - VotingRights50To75PercentAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "voting-rights-75-to-100-percent-as-firm-limited-liability-partnership")] - VotingRights75To100PercentAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-appoint-and-remove-members-limited-liability-partnership")] - RightToAppointAndRemoveMembersLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-appoint-and-remove-members-as-trust-limited-liability-partnership")] - RightToAppointAndRemoveMembersAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "right-to-appoint-and-remove-members-as-firm-limited-liability-partnership")] - RightToAppointAndRemoveMembersAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "significant-influence-or-control-limited-liability-partnership")] - SignificantInfluenceOrControlLimitedLiabilityPartnership, - [EnumMember(Value = "significant-influence-or-control-as-trust-limited-liability-partnership")] - SignificantInfluenceOrControlAsTrustLimitedLiabilityPartnership, - [EnumMember(Value = "significant-influence-or-control-as-firm-limited-liability-partnership")] - SignificantInfluenceOrControlAsFirmLimitedLiabilityPartnership, - [EnumMember(Value = "significant-influence-or-control-registered-overseas-entity")] - SignificantInfluenceOrControlRegisteredOverseasEntity, - [EnumMember(Value = "significant-influence-or-control-as-trust-registered-overseas-entity")] - SignificantInfluenceOrControlAsTrustRegisteredOverseasEntity, - [EnumMember(Value = "significant-influence-or-control-as-firm-registered-overseas-entity")] - SignificantInfluenceOrControlAsFirmRegisteredOverseasEntity, - [EnumMember(Value = "part-right-to-share-surplus-assets-25-to-50-percent")] - PartRightToShareSurplusAssets25To50Percent, - [EnumMember(Value = "part-right-to-share-surplus-assets-50-to-75-percent")] - PartRightToShareSurplusAssets50To75Percent, - [EnumMember(Value = "part-right-to-share-surplus-assets-75-to-100-percent")] - PartRightToShareSurplusAssets75To100Percent, - [EnumMember(Value = "part-right-to-share-surplus-assets-25-to-50-percent-as-trust")] - PartRightToShareSurplusAssets25To50PercentAsTrust, - [EnumMember(Value = "part-right-to-share-surplus-assets-50-to-75-percent-as-trust")] - PartRightToShareSurplusAssets50To75PercentAsTrust, - [EnumMember(Value = "part-right-to-share-surplus-assets-75-to-100-percent-as-trust")] - PartRightToShareSurplusAssets75To100PercentAsTrust, - [EnumMember(Value = "part-right-to-share-surplus-assets-25-to-50-percent-as-firm")] - PartRightToShareSurplusAssets25To50PercentAsFirm, - [EnumMember(Value = "part-right-to-share-surplus-assets-50-to-75-percent-as-firm")] - PartRightToShareSurplusAssets50To75PercentAsFirm, - [EnumMember(Value = "part-right-to-share-surplus-assets-75-to-100-percent-as-firm")] - PartRightToShareSurplusAssets75To100PercentAsFirm, - [EnumMember(Value = "right-to-appoint-and-remove-person")] - RightToAppointAndRemovePerson, - [EnumMember(Value = "right-to-appoint-and-remove-person-as-firm")] - RightToAppointAndRemovePersonAsFirm, - [EnumMember(Value = "right-to-appoint-and-remove-person-as-trust")] - RightToAppointAndRemovePersonAsTrust, - [EnumMember(Value = "right-to-appoint-and-remove-directors-registered-overseas-entity")] - RightToAppointAndRemoveDirectorsRegisteredOverseasEntity, - [EnumMember(Value = "right-to-appoint-and-remove-directors-as-trust-registered-overseas-entity")] - RightToAppointAndRemoveDirectorsAsTrustRegisteredOverseasEntity, - [EnumMember(Value = "right-to-appoint-and-remove-directors-as-firm-registered-overseas-entity")] - RightToAppointAndRemoveDirectorsAsFirmRegisteredOverseasEntity, - } -} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs index bfe9c2d..d18cee1 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs @@ -8,9 +8,21 @@ public class PersonsWithSignificantControl public int? ActiveCount { get; set; } [JsonPropertyName("items")] - public PersonWithSignificantControl[] Items { get; set; } + public PersonWithSignificantControl[]? Items { get; set; } [JsonPropertyName("ceased_count")] public int? CeasedCount { get; set; } + + [JsonPropertyName("items_per_page")] + public int? ItemsPerPage { get; set; } + + [JsonPropertyName("links")] + public PersonWithSignificantControlLinks? Links { get; set; } + + [JsonPropertyName("start_index")] + public int? StartIndex { get; set; } + + [JsonPropertyName("total_results")] + public int? TotalResults { get; set; } } } diff --git a/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs b/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs index b0290f4..be0db9a 100644 --- a/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs +++ b/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.RegisteredOfficeAddress public class Links { [JsonPropertyName("self")] - public string Self { get; set; } + public string? Self { get; set; } } } diff --git a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs b/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs index 1073c3c..9f4d107 100644 --- a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs +++ b/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs @@ -5,36 +5,36 @@ namespace CompaniesHouse.Response.RegisteredOfficeAddress public class OfficeAddress { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } - + public string? AddressLine1 { get; set; } + [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } - + public string? AddressLine2 { get; set; } + [JsonPropertyName("country")] - public OfficeAddressCountry Country { get; set; } - + public string? Country { get; set; } + [JsonPropertyName("etag")] - public string Etag { get; set; } - + public string? Etag { get; set; } + [JsonPropertyName("kind")] - public string Kind { get; set; } - + public string? Kind { get; set; } + [JsonPropertyName("links")] - public Links Links { get; set; } - + public Links? Links { get; set; } + [JsonPropertyName("locality")] - public string Locality { get; set; } + public string? Locality { get; set; } [JsonPropertyName("po_box")] - public string PoBox { get; set; } - + public string? PoBox { get; set; } + [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } + public string? PostalCode { get; set; } [JsonPropertyName("premises")] - public string Premises { get; set; } + public string? Premises { get; set; } [JsonPropertyName("region")] - public string Region { get; set; } + public string? Region { get; set; } } } diff --git a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs b/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs deleted file mode 100644 index c93e08a..0000000 --- a/src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddressCountry.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.RegisteredOfficeAddress -{ - public enum OfficeAddressCountry - { - [EnumMember(Value = "Not specified")] - NotSpecified = 0, - - [EnumMember(Value = "England")] - England, - - [EnumMember(Value = "Wales")] - Wales, - - [EnumMember(Value = "Scotland")] - Scotland, - - [EnumMember(Value = "Northern Ireland")] - NorthernIreland, - - [EnumMember(Value = "Great Britain")] - GreatBritain, - - [EnumMember(Value = "United Kingdom")] - UnitedKingdom - } -} diff --git a/src/CompaniesHouse/Response/ResolutionCategory.cs b/src/CompaniesHouse/Response/ResolutionCategory.cs deleted file mode 100644 index 26e53b0..0000000 --- a/src/CompaniesHouse/Response/ResolutionCategory.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum ResolutionCategory - { - None = 0, - - [EnumMember(Value = "capital")] - Capital, - - [EnumMember(Value = "incorporation")] - Incorporation, - - [EnumMember(Value = "miscellaneous")] - Miscellaneous, - - [EnumMember(Value = "resolution")] - Resolution, - - [EnumMember(Value = "change-of-name")] - ChangeOfName, - - [EnumMember(Value = "liquidation")] - Liquidation, - - [EnumMember(Value = "auditors")] - Auditors, - - [EnumMember(Value = "insolvency")] - Insolvency, - } -} diff --git a/src/CompaniesHouse/Response/SecuredDetailType.cs b/src/CompaniesHouse/Response/SecuredDetailType.cs deleted file mode 100644 index c0ba614..0000000 --- a/src/CompaniesHouse/Response/SecuredDetailType.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum SecuredDetailType - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "amount-secured")] - AmountSecured, - - [EnumMember(Value = "obligations-secured")] - ObligationsSecured - } -} diff --git a/src/CompaniesHouse/UriBuilders/AppointmentsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/AppointmentsUriBuilder.cs new file mode 100644 index 0000000..28c9154 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/AppointmentsUriBuilder.cs @@ -0,0 +1,14 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public class AppointmentsUriBuilder : IAppointmentsUriBuilder + { + public Uri Build(string officerId, int startIndex, int pageSize) + { + var path = $"officers/{Uri.EscapeDataString(officerId)}/appointments?items_per_page={pageSize}&start_index={startIndex}"; + + return new Uri(path, UriKind.Relative); + } + } +} diff --git a/src/CompaniesHouse/UriBuilders/CompanyInsolvencyInformationUriBuilder.cs b/src/CompaniesHouse/UriBuilders/CompanyInsolvencyInformationUriBuilder.cs new file mode 100644 index 0000000..edfa073 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/CompanyInsolvencyInformationUriBuilder.cs @@ -0,0 +1,14 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public class CompanyInsolvencyInformationUriBuilder : ICompanyInsolvencyInformationUriBuilder + { + public Uri Build(string companyNumber) + { + var path = $"company/{Uri.EscapeDataString(companyNumber)}/insolvency"; + + return new Uri(path, UriKind.Relative); + } + } +} diff --git a/src/CompaniesHouse/UriBuilders/IAppointmentsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/IAppointmentsUriBuilder.cs new file mode 100644 index 0000000..53d7eef --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/IAppointmentsUriBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public interface IAppointmentsUriBuilder + { + Uri Build(string officerId, int startIndex, int pageSize); + } +} diff --git a/src/CompaniesHouse/UriBuilders/ICompanyInsolvencyInformationUriBuilder.cs b/src/CompaniesHouse/UriBuilders/ICompanyInsolvencyInformationUriBuilder.cs new file mode 100644 index 0000000..976b91a --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/ICompanyInsolvencyInformationUriBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public interface ICompanyInsolvencyInformationUriBuilder + { + Uri Build(string companyNumber); + } +} diff --git a/src/CompaniesHouse/enum-map.txt b/src/CompaniesHouse/enum-map.txt index d2857d9..99d26d0 100644 --- a/src/CompaniesHouse/enum-map.txt +++ b/src/CompaniesHouse/enum-map.txt @@ -16,3 +16,17 @@ foreign_account_type|CompaniesHouse.Response.CompanyProfile|ForeignAccountType|t terms_of_account_publication|CompaniesHouse.Response.CompanyProfile|TermsOfAccountPublication|true officer_role|CompaniesHouse.Response.Officers|OfficerRole|true identification_type|CompaniesHouse.Response.Officers|IdentificationType|true +filing_history_status|CompaniesHouse.Response|FilingHistoryStatus|false +filing_category|CompaniesHouse.Response|FilingCategory|false +filing_subcategory|CompaniesHouse.Response|FilingSubcategory|false +resolution_category|CompaniesHouse.Response|ResolutionCategory|false +charge_status|CompaniesHouse.Response|ChargeStatus|false +classification_charge_type|CompaniesHouse.Response|ClassificationChargeType|false +particular_type|CompaniesHouse.Response|ParticularType|false +secured_detail_type|CompaniesHouse.Response|SecuredDetailType|false +assets_ceased_released|CompaniesHouse.Response|AssetsCeasedReleased|false +insolvency_status|CompaniesHouse.Response.Insolvency|InsolvencyStatus|false +insolvency_case_date_type|CompaniesHouse.Response.Insolvency|CaseDateType|false +insolvency_case_type|CompaniesHouse.Response.Insolvency|InsolvencyCaseType|true +person_with_significant_control_kind|CompaniesHouse.Response.PersonsWithSignificantControl|PersonWithSignificantControlKind|false +person_with_significant_control_nature_of_control|CompaniesHouse.Response.PersonsWithSignificantControl|PersonWithSignificantControlNatureOfControl|false diff --git a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs index f900403..cc29238 100644 --- a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs @@ -35,6 +35,7 @@ public void CanResolveCompaniesHouseClients() scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); scope.ServiceProvider.GetService().ShouldNotBeNull(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); } [Fact] @@ -92,6 +93,7 @@ public void AddCompaniesHouseClient_Named_ResolvesKeyedServices() scope.ServiceProvider.GetRequiredKeyedService("first").ShouldNotBeNull(); scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); + scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); } [Fact] diff --git a/tests/CompaniesHouse.IntegrationTests/CollectionBehavior.cs b/tests/CompaniesHouse.IntegrationTests/CollectionBehavior.cs new file mode 100644 index 0000000..2171200 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/CollectionBehavior.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs index d22e231..4746e31 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs @@ -4,13 +4,13 @@ namespace CompaniesHouse.IntegrationTests.Tests.AppointmentsTests { - + public class AppointmentsTestsValid : AppointmentsTestBase { // Sergey Brin's officer id private const string ValidOfficerId = "uQNQ-blSo-8PiOaehWClTPmbZNI"; - + protected override async Task When() { await WhenRetrievingAppointmentsForAValidOfficer() @@ -23,6 +23,13 @@ public void ThenTheDataItemsAreNotEmpty() Result.Data.Items.ShouldNotBeEmpty(); } + [Fact] + public void ThenObservedEnvelopeFieldsAreReturned() + { + Result.Data.Kind.ShouldBe("personal-appointment"); + Result.Data.Links?.Self.ShouldBe($"/officers/{ValidOfficerId}/appointments"); + } + private async Task WhenRetrievingAppointmentsForAValidOfficer() { Result = await Client.GetAppointmentsAsync(ValidOfficerId); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs index dd78c22..077d599 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs @@ -5,15 +5,23 @@ namespace CompaniesHouse.IntegrationTests.Tests.ChargesTests { - + public class ChargeByIdTestsValid : ChargesTestBase { - private const string CompanyNumber = "00445790"; - private const string ChargeId = "5QU1lSudRI2jTIfUv_AOVxfLxVE"; + private const string CompanyNumber = "03977902"; + private const string ChargeId = "4VMbVfCBWdzCW2fXOF5QTezbJ9g"; protected override async Task When() => Result = await Client.GetChargeByIdAsync(CompanyNumber, ChargeId); [Fact] public void ThenChargesListIsNull() => Result.Data.ShouldNotBeNull(); + + [Fact] + public void ThenKnownObservedFieldsAreReturned() + { + Result.Data.Status.Value.ShouldNotBeNullOrWhiteSpace(); + Result.Data.Classification?.Type.Value.ShouldNotBeNullOrWhiteSpace(); + Result.Data.Links?.Self.ShouldBe($"/company/{CompanyNumber}/charges/{ChargeId}"); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs index bdc4933..9776840 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs @@ -24,5 +24,16 @@ public async Task ThenChargesListIsNotEmpty(string companyNumber) result.Data.Items.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenKnownChargeListIncludesObservedGeneratedValues() + { + var result = await _client.GetChargesListAsync("03977902"); + + result.Data.UnfilteredCount.ShouldNotBeNull(); + result.Data.UnfilteredCount.Value.ShouldBeGreaterThan(0); + result.Data.Items[0].Status.Value.ShouldNotBeNullOrWhiteSpace(); + result.Data.Items[0].Links?.Self.ShouldNotBeNullOrWhiteSpace(); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs index c2da1df..9243d12 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs @@ -5,7 +5,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.CompanyFilingHistoryTests { - + public class CompanyFilingHistoryTestsInvalid : CompanyFilingHistoryTestBase { private const string InvalidCompanyNumber = "ABC00000"; @@ -21,7 +21,8 @@ await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() [Fact] public void ThenTheDataItemsAreNull() { - _result.Data.Items.ShouldBeNull(); + _result.Data.ShouldNotBeNull(); + _result.Data.Items.ShouldBeEmpty(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs index 45ceb3d..6d1fce6 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs @@ -40,5 +40,16 @@ public async Task ThenTheDataItemsAreNotEmpty(string companyNumber) results.ShouldNotBeEmpty(); } + + [Fact] + public async Task ThenKnownFilingHistoryIncludesObservedPaginationFields() + { + var result = await _client.GetCompanyFilingHistoryAsync("00445790"); + + result.Data.TotalCount.ShouldBeGreaterThan(0); + result.Data.ItemsPerPage.ShouldBeGreaterThan(0); + result.Data.Items.ShouldNotBeEmpty(); + result.Data.Items[0].Category.Value.ShouldNotBeNullOrWhiteSpace(); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs index 5cac2c8..06ae8d9 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs @@ -5,11 +5,11 @@ namespace CompaniesHouse.IntegrationTests.Tests.CompanyFilingHistoryTests { - + public class FilingHistoryByTransactionIdTestsValid : CompanyFilingHistoryTestBase { - private const string InvalidCompanyNumber = "00445790"; - private const string InvalidTransactionId = "QUE3UDBHVU9hZGlxemtjeA"; + private const string ValidCompanyNumber = "00445790"; + private const string ValidTransactionId = "MzUyNDY1MTExNmFkaXF6a2N4"; private CompaniesHouseClientResponse _result = null!; @@ -25,9 +25,16 @@ public void ThenTheDataItemsAreNull() _result.Data.ShouldNotBeNull(); } + [Fact] + public void ThenObservedFieldsAreReturned() + { + _result.Data.Links?.DocumentMetaData.ShouldNotBeNullOrWhiteSpace(); + _result.Data.Category.Value.ShouldNotBeNullOrWhiteSpace(); + } + private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() { - _result = await _client.GetFilingHistoryByTransactionAsync(InvalidCompanyNumber, InvalidTransactionId) + _result = await _client.GetFilingHistoryByTransactionAsync(ValidCompanyNumber, ValidTransactionId) ; } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs index 96546e3..4ba7aa5 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs @@ -4,7 +4,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.CompanyInsolvencyInformationTests { - + public class CompanyInsolvencyInformationTestsValid : CompanyInsolvencyInformationTestBase { private const string ValidCompanyNumber = "08749409"; @@ -14,5 +14,13 @@ protected override async Task When() => [Fact] public void ThenTheItemsAreReturned() => Result.Data.ShouldNotBeNull(); + + [Fact] + public void ThenObservedStatusesAndCaseTypesAreReturned() + { + Result.Data.Cases.ShouldNotBeNull(); + Result.Data.Cases.ShouldNotBeEmpty(); + Result.Data.Cases[0].Type.Value.ShouldNotBeNullOrWhiteSpace(); + } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs index 7a85cb3..e0419f8 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.IntegrationTests.Tests.DocumentTests { - + public class DocumentTestsValid : DocumentTestBase { - private const string DocumentId = "FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4"; + private const string DocumentId = "IHFGB_pcm7rSIRefsfuXK1MDkLFxrSoHbKKAgY7OTxk"; + - protected override async Task When() => await RetrievingDocumentMetadata(); private async Task RetrievingDocumentMetadata() => Result = await Client.GetDocumentMetadataAsync(DocumentId); @@ -22,5 +22,12 @@ public void ThenDocumentMetadataAreNotEmpty() Result.Data.Resources.ShouldNotBeNull(); Result.Data.Resources.ShouldNotBeEmpty(); } + + [Fact] + public void ThenObservedFilenameAndDocumentLinkAreReturned() + { + Result.Data.Filename.ShouldNotBeNullOrWhiteSpace(); + Result.Data.Links?.Document.ShouldNotBeNullOrWhiteSpace(); + } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs index 6450521..6e9c787 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs @@ -4,12 +4,12 @@ namespace CompaniesHouse.IntegrationTests.Tests.PersonsWithSignificantControlTests { - + public class PersonsWithSignificantControlTestsInValid : PersonsWithSignificantControlTestBase { private const string InvalidCompanyNumber = "ABC00000"; - + protected override async Task When() { await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany(); @@ -18,7 +18,8 @@ protected override async Task When() [Fact] public void ThenTheDataItemsAreNull() { - _result.Data.ShouldBeNull(); + _result.Data.ShouldNotBeNull(); + _result.Data.Items.ShouldBeEmpty(); } private async Task WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany() diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs index 07f3c47..9160e55 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs @@ -4,13 +4,13 @@ namespace CompaniesHouse.IntegrationTests.Tests.PersonsWithSignificantControlTests { - + public class PersonsWithSignificantControlTestsValid : PersonsWithSignificantControlTestBase { // Google UK company number, unlikely to go away soon private const string ValidCompanyNumber = "03977902"; - + protected override async Task When() { await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnValidCompany(); @@ -22,6 +22,14 @@ public void ThenTheDataItemsAreNotEmpty() _result.Data.Items.ShouldNotBeEmpty(); } + [Fact] + public void ThenObservedCountsAndKindsAreReturned() + { + _result.Data.TotalResults.ShouldNotBeNull(); + _result.Data.TotalResults.Value.ShouldBeGreaterThan(0); + _result.Data.Items[0].Kind.Value.ShouldNotBeNullOrWhiteSpace(); + } + private async Task WhenRetrievingAnCompanyPersonsWithSignificantControlForAnValidCompany() { _result = await _client.GetPersonsWithSignificantControlAsync(ValidCompanyNumber); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs index 9ba2a98..865f602 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs @@ -4,14 +4,21 @@ namespace CompaniesHouse.IntegrationTests.Tests.RegisteredOfficeAddress { - + public class RegisteredOfficeAddressesTestsValid : RegisteredOfficeAddressTestBase { private const string CompanyNumber = "03977902"; protected override async Task When() => Result = await Client.GetRegisteredOfficeAddress(CompanyNumber); - + [Fact] public void ThenRegisteredOfficeAddressIsNotNull() => Result.Data.ShouldNotBeNull(); + + [Fact] + public void ThenObservedFieldsAreReturned() + { + Result.Data.Country.ShouldBe("United Kingdom"); + Result.Data.Links?.Self.ShouldBe("/company/03977902/registered-office-address"); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs index 10853fa..bfc0cb6 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs @@ -33,12 +33,16 @@ public async Task ThenCompaniesAreReturned(string query) [IntegrationFact] public async Task ThenPreviousNameSearchReturnsMatchedPreviousCompanyName() { - var result = await _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest + CompaniesHouseClientResponse result; + + try { - Query = "radio rentals", - SearchType = "previous-name-dissolved", - Size = 10, - }); + result = await SearchPreviousNamesAsync(); + } + catch (CompaniesHouseApiException exception) when (exception.StatusCode == 500) + { + result = await SearchPreviousNamesAsync(); + } result.Data.Kind.ShouldBe("search#previous-name-dissolved"); result.Data.TopHit.MatchedPreviousCompanyName.ShouldNotBeNull(); @@ -58,5 +62,13 @@ public async Task ThenAlphabeticalSearchReturnsOrderedAlphaKeys() result.Data.Kind.ShouldBe("search#alphabetical-dissolved"); result.Data.Items.ShouldContain(x => !string.IsNullOrWhiteSpace(x.OrderedAlphaKeyWithId)); } + + private Task> SearchPreviousNamesAsync() => + _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest + { + Query = "radio rentals", + SearchType = "previous-name-dissolved", + Size = 10, + }); } } diff --git a/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs b/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs new file mode 100644 index 0000000..cc1890c --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs @@ -0,0 +1,81 @@ +using System.Text.Json; +using CompaniesHouse.Response.Appointments; +using CompaniesHouse.Response.PersonsWithSignificantControl; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class AppointmentsAndPscScenarios + { + [Fact] + public void Appointments_DeserializesCorporateAndEnvelopeFields() + { + const string json = """ + { + "active_count":90, + "etag":"8a276751f22df1b08704b544645a41b00fc0fec1", + "inactive_count":19, + "is_corporate_officer":true, + "items":[ + { + "appointed_on":"2025-10-21", + "appointed_to":{"company_name":"INFORMA PRESTIGE HOLDINGS LIMITED","company_number":"16718313","company_status":"active"}, + "name":"INFORMA COSEC LIMITED", + "identification":{"identification_type":"uk-limited-company","registration_number":"3849195"}, + "is_pre_1992_appointment":false, + "links":{"company":"/company/16718313"}, + "officer_role":"corporate-secretary" + } + ], + "items_per_page":5, + "kind":"personal-appointment", + "links":{"self":"/officers/YwIOmduyS6PW5axJgQQrsTGyRD0/appointments"}, + "name":"INFORMA COSEC LIMITED", + "resigned_count":12, + "start_index":0, + "total_results":121 + } + """; + + var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.IsCorporateOfficer.ShouldBeTrue(); + value.Items[0].Identification?.RegistrationNumber.ShouldBe("3849195"); + } + + [Fact] + public void PersonsWithSignificantControl_DeserializesCorporateEntityList() + { + const string json = """ + { + "items_per_page":10, + "items":[ + { + "notified_on":"2016-04-06", + "name":"Alphabet, Inc.", + "links":{"self":"/company/03977902/persons-with-significant-control/corporate-entity/cdqMtbUIfvMc4RgPpHEhBM8trCs"}, + "identification":{"legal_form":"Corporate","legal_authority":"Delaware Secretary Of State","country_registered":"Delaware","place_registered":"Delaware","registration_number":"5786925"}, + "ceased":false, + "kind":"corporate-entity-person-with-significant-control", + "natures_of_control":["ownership-of-shares-75-to-100-percent","voting-rights-75-to-100-percent","right-to-appoint-and-remove-directors"] + } + ], + "start_index":0, + "total_results":1, + "active_count":1, + "ceased_count":0, + "links":{"self":"/company/03977902/persons-with-significant-control"} + } + """; + + var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.TotalResults.ShouldBe(1); + value.Items[0].Kind.ShouldBe(new PersonWithSignificantControlKind("corporate-entity-person-with-significant-control")); + value.Items[0].NaturesOfControl.ShouldContain(new PersonWithSignificantControlNatureOfControl("right-to-appoint-and-remove-directors")); + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs b/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs new file mode 100644 index 0000000..9fefc84 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs @@ -0,0 +1,74 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Charges; +using CompaniesHouse.Response.CompanyFiling; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class FilingAndChargesScenarios + { + [Fact] + public void FilingHistoryItem_DeserializesSingleSubcategoryAndDocumentLink() + { + const string json = """ + { + "transaction_id":"MzUyMDQ1NzU4MWFkaXF6a2N4", + "barcode":"XF1MYMJM", + "type":"MR01", + "date":"2026-05-08", + "category":"mortgage", + "subcategory":"create", + "description":"mortgage-create-with-deed-with-charge-number-charge-creation-date", + "description_values":{"charge_number":"000020650090","charge_creation_date":"2026-05-06"}, + "pages":16, + "action_date":"2026-05-06", + "links":{"self":"/company/00002065/filing-history/MzUyMDQ1NzU4MWFkaXF6a2N4","document_metadata":"https://document-api.company-information.service.gov.uk/document/yiC6UOsmY5UnJERjCxHDRMUIKbFEY_R5zcSTVyVLT-A"} + } + """; + + var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Category.ShouldBe(new FilingCategory("mortgage")); + value.Subcategory.ShouldBe([new FilingSubcategory("create")]); + value.ActionDate.ShouldBe(new DateTime(2026, 05, 06)); + } + + [Fact] + public void CompanyCharges_DeserializesUnfilteredCountAndGeneratedValueTypes() + { + const string json = """ + { + "etag":"96a8b4fffcc72586b0b003550132128341cdc4f5", + "total_count":1, + "unfiltered_count":1, + "satisfied_count":0, + "part_satisfied_count":0, + "items":[ + { + "etag":"43a456b9b17fc077d7ef8a9861b9842a20e7eba5", + "classification":{"type":"charge-description","description":"Rent deposit deed"}, + "charge_number":1, + "status":"outstanding", + "delivered_on":"2012-10-02", + "created_on":"2012-09-25", + "particulars":{"type":"short-particulars","description":"£18,930.87"}, + "secured_details":{"type":"amount-secured","description":"£18,930.87 due"}, + "links":{"self":"/company/03977902/charges/4VMbVfCBWdzCW2fXOF5QTezbJ9g"} + } + ] + } + """; + + var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.UnfilteredCount.ShouldBe(1); + value.Items[0].Status.ShouldBe(new ChargeStatus("outstanding")); + value.Items[0].Classification?.Type.ShouldBe(new ClassificationChargeType("charge-description")); + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs b/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs new file mode 100644 index 0000000..73973d8 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs @@ -0,0 +1,41 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response.Insolvency; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class InsolvencyScenarios + { + [Fact] + public void CompanyInsolvencyInformation_DeserializesStatusesAndCaseDates() + { + const string json = """ + { + "cases":[ + { + "type":"creditors-voluntary-liquidation", + "dates":[ + {"type":"voluntary-arrangement-ceased-to-have-effect","date":"2013-05-29"}, + {"type":"liquidation-started-on","date":"2013-05-29"} + ], + "practitioners":[ + {"name":"Richard David Hill","address":{"address_line_1":"Grant Thornton Uk Llp","address_line_2":"4 Hardman Square","locality":"Spinningfields","region":"Manchester","postal_code":"M3 3EB"},"appointed_on":"2013-05-29","role":"liquidator"} + ], + "number":"1" + } + ], + "status":["liquidation"] + } + """; + + var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Status.ShouldBe([new InsolvencyStatus("liquidation")]); + value.Cases[0].Type.ShouldBe(InsolvencyCaseType.CreditorsVoluntaryLiquidation); + value.Cases[0].Dates.ShouldContain(x => x.Type == new CaseDateType("liquidation-started-on") && x.Date == new DateTime(2013, 05, 29)); + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs b/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs new file mode 100644 index 0000000..1b7bb4d --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs @@ -0,0 +1,63 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response.Document; +using CompaniesHouse.Response.RegisteredOfficeAddress; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class RegisteredOfficeAndDocumentsScenarios + { + [Fact] + public void RegisteredOfficeAddress_DeserializesForeignCountryWithoutEnumFailure() + { + const string json = """ + { + "etag":"185a52c646d2f03c05127df15915f784e41acf60", + "kind":"registered-office-address", + "links":{"self":"/company/FC040879/registered-office-address"}, + "address_line_1":"Absa Towers West", + "address_line_2":"15 Troye Street", + "country":"South Africa", + "locality":"Johannesburg", + "region":"Gauteng 2000" + } + """; + + var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Country.ShouldBe("South Africa"); + value.Region.ShouldBe("Gauteng 2000"); + } + + [Fact] + public void DocumentMetadata_DeserializesObservedFields() + { + const string json = """ + { + "company_number":"00445790", + "barcode":"XF5EZFHE", + "significant_date":null, + "significant_date_type":"", + "category":"annual-returns", + "pages":3, + "filename":"00445790_cs01_2026-07-01", + "created_at":"2026-07-01T08:28:44.698561376Z", + "links":{"self":"https://document-api.company-information.service.gov.uk/document/IHFGB_pcm7rSIRefsfuXK1MDkLFxrSoHbKKAgY7OTxk","document":"https://document-api.company-information.service.gov.uk/document/IHFGB_pcm7rSIRefsfuXK1MDkLFxrSoHbKKAgY7OTxk/content"}, + "resources":{"application/pdf":{"content_length":82803}} + } + """; + + var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.CreatedAt.ShouldNotBeNull(); + value.CreatedAt.Value.Year.ShouldBe(2026); + value.CreatedAt.Value.Month.ShouldBe(7); + value.CreatedAt.Value.Day.ShouldBe(1); + value.Resources["application/pdf"].ContentLength.ShouldBe(82803); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseAppointmentsClientTests/CompaniesHouseAppointmentsClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseAppointmentsClientTests/CompaniesHouseAppointmentsClientTests.cs new file mode 100644 index 0000000..7bdd443 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseAppointmentsClientTests/CompaniesHouseAppointmentsClientTests.cs @@ -0,0 +1,113 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Appointments; +using CompaniesHouse.UriBuilders; +using Moq; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseAppointmentsClientTests +{ + public class CompaniesHouseAppointmentsClientTests + { + [Fact] + public async Task GivenARealCapturedAppointmentList_WhenGettingAppointments_ThenEnvelopeAndItemsDeserialize() + { + const string json = """ + { + "active_count":0, + "date_of_birth":{"month":8,"year":1973}, + "etag":"bdc01ed926045d56901268825b5c58ec21b8754b", + "inactive_count":0, + "is_corporate_officer":false, + "items":[ + { + "address":{"address_line_1":"555 Bryant Street 163","address_line_2":"Palo Alto","locality":"California","postal_code":"94301"}, + "appointed_on":"2000-09-27", + "appointed_to":{"company_name":"GOOGLE UK LIMITED","company_number":"03977902","company_status":"active"}, + "name":"Sergey BRIN", + "is_pre_1992_appointment":false, + "links":{"company":"/company/03977902"}, + "name_elements":{"forename":"Sergey","surname":"BRIN"}, + "nationality":"Usa", + "officer_role":"director", + "resigned_on":"2004-08-04" + } + ], + "items_per_page":5, + "kind":"personal-appointment", + "links":{"self":"/officers/uQNQ-blSo-8PiOaehWClTPmbZNI/appointments"}, + "name":"Sergey BRIN", + "resigned_count":1, + "start_index":0, + "total_results":1 + } + """; + + var uri = new Uri("https://wibble.com/officers/officer-id/appointments"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, json); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())).Returns(uri); + + var client = new CompaniesHouseAppointmentsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetAppointmentsAsync("officer-id", 0, 5, default); + + result.Data.ShouldNotBeNull(); + result.Data.Kind.ShouldBe("personal-appointment"); + result.Data.Links?.Self.ShouldBe("/officers/uQNQ-blSo-8PiOaehWClTPmbZNI/appointments"); + result.Data.DateOfBirth?.Year.ShouldBe(1973); + result.Data.Items.ShouldNotBeNull(); + result.Data.Items[0].Appointed?.CompanyStatus.ShouldBe(CompanyStatus.Active); + result.Data.Items[0].Links?.Company.ShouldBe("/company/03977902"); + result.Data.Items[0].OfficerRole.ShouldBe(CompaniesHouse.Response.Officers.OfficerRole.Director); + } + + [Fact] + public async Task GivenARealCapturedCorporateAppointmentList_WhenGettingAppointments_ThenCorporateIdentificationDeserializes() + { + const string json = """ + { + "active_count":90, + "etag":"8a276751f22df1b08704b544645a41b00fc0fec1", + "inactive_count":19, + "is_corporate_officer":true, + "items":[ + { + "address":{"address_line_1":"Howick Place","country":"United Kingdom","locality":"London","postal_code":"SW1P 1WG","premises":"5"}, + "appointed_on":"2025-10-21", + "appointed_to":{"company_name":"INFORMA PRESTIGE HOLDINGS LIMITED","company_number":"16718313","company_status":"active"}, + "name":"INFORMA COSEC LIMITED", + "identification":{"identification_type":"uk-limited-company","registration_number":"3849195"}, + "is_pre_1992_appointment":false, + "links":{"company":"/company/16718313"}, + "officer_role":"corporate-secretary" + } + ], + "items_per_page":5, + "kind":"personal-appointment", + "links":{"self":"/officers/YwIOmduyS6PW5axJgQQrsTGyRD0/appointments"}, + "name":"INFORMA COSEC LIMITED", + "resigned_count":12, + "start_index":0, + "total_results":121 + } + """; + + var uri = new Uri("https://wibble.com/officers/corporate-id/appointments"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, json); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())).Returns(uri); + + var client = new CompaniesHouseAppointmentsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetAppointmentsAsync("corporate-id", 0, 5, default); + + result.Data.ShouldNotBeNull(); + result.Data.IsCorporateOfficer.ShouldBeTrue(); + result.Data.Items.ShouldNotBeNull(); + result.Data.Items[0].Identification?.RegistrationNumber.ShouldBe("3849195"); + result.Data.Items[0].OfficerRole.ShouldBe(CompaniesHouse.Response.Officers.OfficerRole.CorporateSecretary); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs index 1b3f56e..1df276c 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using CompaniesHouse.Response; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; using Moq; @@ -26,7 +27,7 @@ public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyCharges(Co var result = await client.GetChargesListAsync("1", 0, 25); - EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, charges, "TransactionId"); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, charges, "TransactionId", "UnfilteredCount"); foreach (var (actual, expected) in result.Data.Items.Zip(charges.Items)) { actual.InsolvencyCases.Select(x => x.TransactionId).ShouldBe(expected.InsolvencyCases.Select(x => (long?)x.TransactionId)); @@ -50,7 +51,7 @@ public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyChargeById EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, charge, "TransactionId"); result.Data.InsolvencyCases.Select(x => x.TransactionId).ShouldBe(charge.InsolvencyCases.Select(x => (long?)x.TransactionId)); } - + public static IEnumerable TestCases() { var allAssetsCeasedReleased = EnumerationMappings.PossibleAssetsCeasedReleased.Keys.Select(x => new CompaniesHouseChargesClientTestCase @@ -88,7 +89,7 @@ public static IEnumerable TestCases() ClassificationChargeType = x, Status = EnumerationMappings.PossibleChargeStatuses.Keys.First() }); - + var allChargeStatuses = EnumerationMappings.PossibleChargeStatuses.Keys.Select(x => new CompaniesHouseChargesClientTestCase { AssetsCeasedReleased = EnumerationMappings.PossibleAssetsCeasedReleased.Keys.First(), @@ -105,5 +106,50 @@ public static IEnumerable TestCases() .Concat(allChargeStatuses) .Select(testCase => new object[] { testCase }); } + + [Fact] + public async Task GivenARealCapturedChargeList_WhenGettingCompanyCharges_ThenUnfilteredCountAndChargeFieldsDeserialize() + { + const string json = """ + { + "etag":"96a8b4fffcc72586b0b003550132128341cdc4f5", + "total_count":1, + "unfiltered_count":1, + "satisfied_count":0, + "part_satisfied_count":0, + "items":[ + { + "etag":"43a456b9b17fc077d7ef8a9861b9842a20e7eba5", + "classification":{"type":"charge-description","description":"Rent deposit deed"}, + "charge_number":1, + "status":"outstanding", + "delivered_on":"2012-10-02", + "created_on":"2012-09-25", + "particulars":{"type":"short-particulars","description":"£18,930.87 together with all interest accrued thereto."}, + "secured_details":{"type":"amount-secured","description":"£18,930.87 due or to become due from the company to the chargee under the terms of the aforementioned instrument creating or evidencing the charge"}, + "persons_entitled":[{"name":"Lazari Investments Limited"}], + "transactions":[{"filing_type":"create-charge-pre-april-2013","delivered_on":"2012-10-02","links":{"filing":"/company/03977902/filing-history/MzA2NTI5MDU2N2FkaXF6a2N4"}}], + "links":{"self":"/company/03977902/charges/4VMbVfCBWdzCW2fXOF5QTezbJ9g"} + } + ] + } + """; + + var uri = new Uri("https://wibble.com/company/03977902/charges"); + var handler = new StubHttpMessageHandler(uri, json); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())).Returns(uri); + + var client = new CompaniesHouseChargesClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetChargesListAsync("03977902", 0, 25); + + result.Data.ShouldNotBeNull(); + result.Data.UnfilteredCount.ShouldBe(1); + result.Data.Items.ShouldNotBeNull(); + result.Data.Items[0].Status.ShouldBe(new ChargeStatus("outstanding")); + result.Data.Items[0].Classification?.Type.ShouldBe(new ClassificationChargeType("charge-description")); + result.Data.Items[0].Particular?.Type.ShouldBe(new ParticularType("short-particulars")); + result.Data.Items[0].SecuredDetail?.Type.ShouldBe(new SecuredDetailType("amount-secured")); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs index 36d6e32..da62270 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using CompaniesHouse.Response; +using CompaniesHouse.Response.CompanyFiling; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; using Moq; @@ -101,5 +103,39 @@ public static IEnumerable TestCases() .Concat(allFilingResolutionCategories) .Select(testCase => new object[] { testCase }); } + + [Fact] + public async Task GivenARealCapturedMortgageFiling_WhenGettingAFilingHistoryItem_ThenSingleSubcategoryAndActionDateDeserialize() + { + const string json = """ + { + "transaction_id":"MzUyMDQ1NzU4MWFkaXF6a2N4", + "barcode":"XF1MYMJM", + "type":"MR01", + "date":"2026-05-08", + "category":"mortgage", + "subcategory":"create", + "description":"mortgage-create-with-deed-with-charge-number-charge-creation-date", + "description_values":{"charge_number":"000020650090","charge_creation_date":"2026-05-06"}, + "pages":16, + "action_date":"2026-05-06", + "links":{"self":"/company/00002065/filing-history/MzUyMDQ1NzU4MWFkaXF6a2N4","document_metadata":"https://document-api.company-information.service.gov.uk/document/yiC6UOsmY5UnJERjCxHDRMUIKbFEY_R5zcSTVyVLT-A"} + } + """; + + var uri = new Uri("https://wibble.com/company/00002065/filing-history/id"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, json); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny())).Returns(uri); + + var client = new CompaniesHouseCompanyFilingHistoryClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetFilingHistoryByTransactionAsync("00002065", "id"); + + result.Data.ShouldNotBeNull(); + result.Data.Category.ShouldBe(new FilingCategory("mortgage")); + result.Data.Subcategory.ShouldBe([new FilingSubcategory("create")]); + result.Data.ActionDate.ShouldBe(new DateTime(2026, 05, 06)); + result.Data.Links?.DocumentMetaData.ShouldBe("https://document-api.company-information.service.gov.uk/document/yiC6UOsmY5UnJERjCxHDRMUIKbFEY_R5zcSTVyVLT-A"); + } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs new file mode 100644 index 0000000..ce1b9d3 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Response.Insolvency; +using CompaniesHouse.UriBuilders; +using Moq; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseCompanyInsolvencyInformationClientTests +{ + public class CompaniesHouseCompanyInsolvencyInformationClientTests + { + [Fact] + public async Task GivenARealCapturedInsolvencyPayload_WhenGettingCompanyInsolvencyInformation_ThenStatusesAndCaseTypesDeserialize() + { + const string json = """ + { + "cases":[ + { + "type":"in-administration", + "dates":[ + {"type":"administration-started-on","date":"2012-01-30"}, + {"type":"administration-ended-on","date":"2013-01-22"} + ], + "practitioners":[ + {"name":"Ian Christopher Schofield","address":{"address_line_1":"Pkf (Uk) Llp","address_line_2":"Pannell House","locality":"6 Queen Street","region":"Leeds","postal_code":"LS1 2TW"},"role":"practitioner"}, + {"name":"Charles William Anthony Escott","address":{"address_line_1":"Pannell House 6 Queen Street","locality":"Leeds","region":"West Yorkshire","postal_code":"LS1 2TW"},"ceased_to_act_on":"2012-06-01","role":"practitioner"} + ], + "number":"1" + } + ], + "status":["in-administration","administrative-receiver"] + } + """; + + var uri = new Uri("https://wibble.com/company/08749409/insolvency"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, json); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny())).Returns(uri); + + var client = new CompaniesHouseCompanyInsolvencyInformationClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetCompanyInsolvencyInformationAsync("08749409"); + + result.Data.ShouldNotBeNull(); + result.Data.Status.ShouldBe([new InsolvencyStatus("in-administration"), new InsolvencyStatus("administrative-receiver")]); + result.Data.Cases.ShouldNotBeNull(); + result.Data.Cases[0].Type.ShouldBe(InsolvencyCaseType.InAdministration); + result.Data.Cases[0].Dates.ShouldContain(x => x.Type == new CaseDateType("administration-started-on")); + result.Data.Cases[0].Practitioners.ShouldContain(x => x.Name == "Charles William Anthony Escott" && x.CeasedToActOn == new DateTime(2012, 06, 01)); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs index 380ea5a..99f7cd2 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs @@ -30,7 +30,41 @@ public CompaniesHouseDocumentMetadataClientTests() public void ThenDocumentMetadataIsCorrect() { EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _expected, nameof(DocumentMetadata.CreatedAt)); - _result.Data.CreatedAt.ShouldBe(_expected.CreatedAt.ToString("O")); + _result.Data.CreatedAt.ShouldBe(_expected.CreatedAt); + } + + [Fact] + public async Task GivenARealCapturedDocumentMetadata_WhenGettingDocumentMetadata_ThenFilenameAndContentLengthDeserialize() + { + const string documentId = "IHFGB_pcm7rSIRefsfuXK1MDkLFxrSoHbKKAgY7OTxk"; + const string json = """ + { + "company_number":"00445790", + "barcode":"XF5EZFHE", + "significant_date":null, + "significant_date_type":"", + "category":"annual-returns", + "pages":3, + "filename":"00445790_cs01_2026-07-01", + "created_at":"2026-07-01T08:28:44.698561376Z", + "etag":"", + "links":{"self":"https://document-api.company-information.service.gov.uk/document/IHFGB_pcm7rSIRefsfuXK1MDkLFxrSoHbKKAgY7OTxk","document":"https://document-api.company-information.service.gov.uk/document/IHFGB_pcm7rSIRefsfuXK1MDkLFxrSoHbKKAgY7OTxk/content"}, + "resources":{"application/pdf":{"content_length":82803}} + } + """; + + var requestUri = new Uri($"https://document-api.company-information.service.gov.uk/document/{documentId}"); + var stubHttpMessageHandler = new StubHttpMessageHandler(requestUri, json); + var mockUriBuilder = new Mock(); + mockUriBuilder.Setup(x => x.Build(documentId)).Returns(requestUri); + + var result = await new CompaniesHouseDocumentMetadataClient(new HttpClient(stubHttpMessageHandler), mockUriBuilder.Object) + .GetDocumentMetadataAsync(documentId); + + result.Data.ShouldNotBeNull(); + result.Data.Filename.ShouldBe("00445790_cs01_2026-07-01"); + result.Data.Resources.ShouldContainKey("application/pdf"); + result.Data.Resources["application/pdf"].ContentLength.ShouldBe(82803); } private static Mock SetupRequestUri(Uri catchUri) diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs index c461984..85c9301 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs @@ -19,7 +19,7 @@ public class DocumentMetadataTestCase public class ResourceContentLength { - public int ContentLength { get; set; } + public long ContentLength { get; set; } } public class Links diff --git a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs index 068d5f2..349d982 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs @@ -1,6 +1,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using CompaniesHouse.Response.PersonsWithSignificantControl; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; using Moq; @@ -37,5 +38,48 @@ public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingPersonsWit EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)_result.Data, _personsWithSignificantControl); } + + [Fact] + public async Task GivenARealCapturedCorporatePscList_WhenGettingPersonsWithSignificantControl_ThenTotalsAndIdentificationDeserialize() + { + const string json = """ + { + "items_per_page":10, + "items":[ + { + "etag":"d42a48004c06ea9a50976a58f16d2a70ac6ed820", + "notified_on":"2016-04-06", + "name":"Alphabet, Inc.", + "links":{"self":"/company/03977902/persons-with-significant-control/corporate-entity/cdqMtbUIfvMc4RgPpHEhBM8trCs"}, + "identification":{"legal_form":"Corporate","legal_authority":"Delaware Secretary Of State","country_registered":"Delaware","place_registered":"Delaware","registration_number":"5786925"}, + "ceased":false, + "kind":"corporate-entity-person-with-significant-control", + "address":{"address_line_1":"251 Little Falls Drive","country":"United States","locality":"Wilmington","postal_code":"19808","premises":"Corporation Service Company","region":"Delaware"}, + "natures_of_control":["ownership-of-shares-75-to-100-percent","voting-rights-75-to-100-percent","right-to-appoint-and-remove-directors"] + } + ], + "start_index":0, + "total_results":1, + "active_count":1, + "ceased_count":0, + "links":{"self":"/company/03977902/persons-with-significant-control"} + } + """; + + var uri = new Uri("https://wibble.com/company/03977902/persons-with-significant-control"); + var handler = new StubHttpMessageHandler(uri, json); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny(), It.IsAny(), It.IsAny())).Returns(uri); + + var client = new CompaniesHousePersonsWithSignificantControlClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetPersonsWithSignificantControlAsync("03977902", 0, 10); + + result.Data.ShouldNotBeNull(); + result.Data.TotalResults.ShouldBe(1); + result.Data.Items.ShouldNotBeNull(); + result.Data.Items[0].Kind.ShouldBe(new PersonWithSignificantControlKind("corporate-entity-person-with-significant-control")); + result.Data.Items[0].Identification?.RegistrationNumber.ShouldBe("5786925"); + result.Data.Items[0].NaturesOfControl.ShouldContain(new PersonWithSignificantControlNatureOfControl("right-to-appoint-and-remove-directors")); + } } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs index d80a1b7..0bb08f9 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Runtime.Serialization; using System.Threading.Tasks; +using CompaniesHouse.Response.RegisteredOfficeAddress; using CompaniesHouse.Tests.ResourceBuilders; using CompaniesHouse.UriBuilders; using Moq; @@ -32,9 +32,7 @@ public async Task GivenACompaniesHouseRegistereOfficeAddressClient_WhenGettingAR var result = await client.GetRegisteredOfficeAddress("abc"); - EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, registeredOfficeAddress, nameof(RegisteredOfficeAddress.Country)); - - result.Data.Country.GetEnumMemberValue().ShouldBe(registeredOfficeAddress.Country); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, registeredOfficeAddress); } public static IEnumerable TestCases() => @@ -44,17 +42,38 @@ public static IEnumerable TestCases() => Country = x }) .Select(testCase => new object[] { testCase }); - } - internal static class EnumExtensions - { - public static string GetEnumMemberValue(this Enum enumValue) + [Fact] + public async Task GivenARealCapturedPayload_WhenGettingARegisteredOfficeAddress_ThenAllObservedFieldsDeserialize() { - var type = enumValue.GetType(); - var info = type.GetField(enumValue.ToString()); - var enumMember = (EnumMemberAttribute[])info.GetCustomAttributes(typeof(EnumMemberAttribute), false); + const string json = """ + { + "etag":"185a52c646d2f03c05127df15915f784e41acf60", + "kind":"registered-office-address", + "links":{"self":"/company/FC040879/registered-office-address"}, + "address_line_1":"Absa Towers West", + "address_line_2":"15 Troye Street", + "country":"South Africa", + "locality":"Johannesburg", + "region":"Gauteng 2000" + } + """; + + var uri = new Uri("https://wibble.com/company/FC040879/registered-office-address"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, json); + + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny())).Returns(uri); + + var client = new CompaniesHouseRegisteredOfficeAddressClient(new HttpClient(handler), uriBuilder.Object); + + var result = await client.GetRegisteredOfficeAddress("FC040879"); - return enumMember.Length > 0 ? enumMember[0].Value : string.Empty; + result.Data.ShouldNotBeNull(); + result.Data.Country.ShouldBe("South Africa"); + result.Data.Kind.ShouldBe("registered-office-address"); + result.Data.Region.ShouldBe("Gauteng 2000"); + result.Data.Links?.Self.ShouldBe("/company/FC040879/registered-office-address"); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/EnumerationMappings.cs b/tests/CompaniesHouse.Tests/EnumerationMappings.cs index a175614..0dd1200 100644 --- a/tests/CompaniesHouse.Tests/EnumerationMappings.cs +++ b/tests/CompaniesHouse.Tests/EnumerationMappings.cs @@ -150,128 +150,128 @@ public static class EnumerationMappings public static readonly IReadOnlyDictionary PossibleResolutionCategories = new Dictionary () { - {"miscellaneous", ResolutionCategory.Miscellaneous} + {"miscellaneous", new ResolutionCategory("miscellaneous")} }; public static readonly IReadOnlyDictionary PossibleFilingHistoryStatus = new Dictionary () { - {"filing-history-available", FilingHistoryStatus.FilingHistoryAvailable} + {"filing-history-available", new FilingHistoryStatus("filing-history-available")} }; public static readonly IReadOnlyDictionary PossibleFilingSubcategories = new Dictionary () { - {"annual-return", FilingSubcategory.AnnualReturn}, - {"resolution", FilingSubcategory.Resolution}, - {"change", FilingSubcategory.Change}, - {"create", FilingSubcategory.Create}, - {"certificate", FilingSubcategory.Certificate}, - {"appointments", FilingSubcategory.Appointments}, - {"satisfy", FilingSubcategory.Satisfy}, - {"termination", FilingSubcategory.Termination}, - {"release-cease", FilingSubcategory.ReleaseCease}, - {"voluntary", FilingSubcategory.Voluntary}, - {"administration", FilingSubcategory.Administration}, - {"compulsory", FilingSubcategory.Compulsory}, - {"court-order", FilingSubcategory.CourtOrder}, - {"other", FilingSubcategory.Other}, - {"notifications", FilingSubcategory.Notifications}, - {"officers", FilingSubcategory.Officers}, - {"document-replacement", FilingSubcategory.DocumentReplacement}, - {"statements", FilingSubcategory.Statements}, - {"voluntary-arrangement", FilingSubcategory.VoluntaryArrangement}, - {"alter", FilingSubcategory.Alter}, - {"register", FilingSubcategory.Register}, - {"receiver", FilingSubcategory.Receiver}, - {"voluntary-arrangement-moratoria", FilingSubcategory.VoluntaryArrangementMoratoria}, - {"acquire", FilingSubcategory.Acquire}, - {"trustee", FilingSubcategory.Trustee}, - {"mortgage", FilingSubcategory.Mortgage}, - {"transfer", FilingSubcategory.Transfer}, - {"debenture", FilingSubcategory.Debenture}, + {"annual-return", new FilingSubcategory("annual-return")}, + {"resolution", new FilingSubcategory("resolution")}, + {"change", new FilingSubcategory("change")}, + {"create", new FilingSubcategory("create")}, + {"certificate", new FilingSubcategory("certificate")}, + {"appointments", new FilingSubcategory("appointments")}, + {"satisfy", new FilingSubcategory("satisfy")}, + {"termination", new FilingSubcategory("termination")}, + {"release-cease", new FilingSubcategory("release-cease")}, + {"voluntary", new FilingSubcategory("voluntary")}, + {"administration", new FilingSubcategory("administration")}, + {"compulsory", new FilingSubcategory("compulsory")}, + {"court-order", new FilingSubcategory("court-order")}, + {"other", new FilingSubcategory("other")}, + {"notifications", new FilingSubcategory("notifications")}, + {"officers", new FilingSubcategory("officers")}, + {"document-replacement", new FilingSubcategory("document-replacement")}, + {"statements", new FilingSubcategory("statements")}, + {"voluntary-arrangement", new FilingSubcategory("voluntary-arrangement")}, + {"alter", new FilingSubcategory("alter")}, + {"register", new FilingSubcategory("register")}, + {"receiver", new FilingSubcategory("receiver")}, + {"voluntary-arrangement-moratoria", new FilingSubcategory("voluntary-arrangement-moratoria")}, + {"acquire", new FilingSubcategory("acquire")}, + {"trustee", new FilingSubcategory("trustee")}, + {"mortgage", new FilingSubcategory("mortgage")}, + {"transfer", new FilingSubcategory("transfer")}, + {"debenture", new FilingSubcategory("debenture")}, }; public static readonly IReadOnlyDictionary PossibleFilingCategories = new Dictionary () { - {"accounts", FilingCategory.Accounts}, - {"address", FilingCategory.Address}, - {"annual-return", FilingCategory.AnnualReturn}, - {"capital", FilingCategory.Capital}, - {"change-of-name", FilingCategory.ChangeOfName}, - {"incorporation", FilingCategory.Incorporation}, - {"liquidation", FilingCategory.Liquidation}, - {"miscellaneous", FilingCategory.Miscellaneous}, - {"mortgage", FilingCategory.Mortgage}, - {"officers", FilingCategory.Officers}, - {"resolution", FilingCategory.Resolution}, - {"confirmation-statement", FilingCategory.ConfirmationStatement}, - {"persons-with-significant-control", FilingCategory.PersonsWithSignificantControl}, - {"restoration", FilingCategory.Restoration}, - {"return", FilingCategory.Return}, - {"other", FilingCategory.Other}, - {"reregistration", FilingCategory.ReRegistration}, - {"certificate", FilingCategory.Certificate}, + {"accounts", new FilingCategory("accounts")}, + {"address", new FilingCategory("address")}, + {"annual-return", new FilingCategory("annual-return")}, + {"capital", new FilingCategory("capital")}, + {"change-of-name", new FilingCategory("change-of-name")}, + {"incorporation", new FilingCategory("incorporation")}, + {"liquidation", new FilingCategory("liquidation")}, + {"miscellaneous", new FilingCategory("miscellaneous")}, + {"mortgage", new FilingCategory("mortgage")}, + {"officers", new FilingCategory("officers")}, + {"resolution", new FilingCategory("resolution")}, + {"confirmation-statement", new FilingCategory("confirmation-statement")}, + {"persons-with-significant-control", new FilingCategory("persons-with-significant-control")}, + {"restoration", new FilingCategory("restoration")}, + {"return", new FilingCategory("return")}, + {"other", new FilingCategory("other")}, + {"reregistration", new FilingCategory("reregistration")}, + {"certificate", new FilingCategory("certificate")}, }; public static readonly IReadOnlyDictionary PossiblePersonWithSignificantControlKinds = new Dictionary () { - {"corporate-entity-person-with-significant-control", PersonWithSignificantControlKind.CorporateEntityPersonWithSignificantControl}, - {"individual-person-with-significant-control", PersonWithSignificantControlKind.IndividualPersonWithSignificantControl}, - {"super-secure-person-with-significant-control", PersonWithSignificantControlKind.IndividualPersonWithSignificantControl}, - {"legal-person-person-with-significant-control", PersonWithSignificantControlKind.IndividualPersonWithSignificantControl}, + {"corporate-entity-person-with-significant-control", new PersonWithSignificantControlKind("corporate-entity-person-with-significant-control")}, + {"individual-person-with-significant-control", new PersonWithSignificantControlKind("individual-person-with-significant-control")}, + {"super-secure-person-with-significant-control", new PersonWithSignificantControlKind("super-secure-person-with-significant-control")}, + {"legal-person-person-with-significant-control", new PersonWithSignificantControlKind("legal-person-person-with-significant-control")}, }; public static readonly IReadOnlyDictionary PossibleAssetsCeasedReleased = new Dictionary { - {"property-ceased-to-belong", AssetsCeasedReleased.PropertyCeasedToBelong}, - {"part-property-release-and-ceased-to-belong", AssetsCeasedReleased.PartPropertyReleaseAndCeasedToBelong}, - {"part-property-released", AssetsCeasedReleased.PartPropertyReleased}, - {"part-property-ceased-to-belong", AssetsCeasedReleased.PartPropertyCeasedToBelong}, - {"whole-property-released", AssetsCeasedReleased.WholePropertyReleased}, - {"multiple-filings", AssetsCeasedReleased.MultipleFilings}, - {"whole-property-released-and-ceased-to-belong", AssetsCeasedReleased.WholePropertyReleasedAndCeasedToBelong} + {"property-ceased-to-belong", new AssetsCeasedReleased("property-ceased-to-belong")}, + {"part-property-release-and-ceased-to-belong", new AssetsCeasedReleased("part-property-release-and-ceased-to-belong")}, + {"part-property-released", new AssetsCeasedReleased("part-property-released")}, + {"part-property-ceased-to-belong", new AssetsCeasedReleased("part-property-ceased-to-belong")}, + {"whole-property-released", new AssetsCeasedReleased("whole-property-released")}, + {"multiple-filings", new AssetsCeasedReleased("multiple-filings")}, + {"whole-property-released-and-ceased-to-belong", new AssetsCeasedReleased("whole-property-released-and-ceased-to-belong")} }; public static readonly IReadOnlyDictionary PossibleParticularTypes = new Dictionary { - {"short-particulars", ParticularType.ShortParticulars}, - {"charged-property-description", ParticularType.ChargedPropertyDescription}, - {"charged-property-or-undertaking-description", ParticularType.ChargedPropertyOrUndertakingDescription}, - {"brief-description", ParticularType.BriefDescription} + {"short-particulars", new ParticularType("short-particulars")}, + {"charged-property-description", new ParticularType("charged-property-description")}, + {"charged-property-or-undertaking-description", new ParticularType("charged-property-or-undertaking-description")}, + {"brief-description", new ParticularType("brief-description")} }; public static readonly IReadOnlyDictionary PossibleClassificationChargeTypes = new Dictionary { - {"charge-description", ClassificationChargeType.ChargeDescription}, - {"nature-of-charge", ClassificationChargeType.NatureOfCharge} + {"charge-description", new ClassificationChargeType("charge-description")}, + {"nature-of-charge", new ClassificationChargeType("nature-of-charge")} }; public static readonly IReadOnlyDictionary PossibleSecuredDetailTypes = new Dictionary { - {"amount-secured", SecuredDetailType.AmountSecured}, - {"obligations-secured", SecuredDetailType.ObligationsSecured}, + {"amount-secured", new SecuredDetailType("amount-secured")}, + {"obligations-secured", new SecuredDetailType("obligations-secured")}, }; public static readonly IReadOnlyDictionary PossibleChargeStatuses = new Dictionary { - {"outstanding", ChargeStatus.Outstanding}, - {"fully-satisfied", ChargeStatus.FullySatisfied}, - {"part-satisfied", ChargeStatus.PartSatisfied}, - {"satisfied", ChargeStatus.Satisfied} + {"outstanding", new ChargeStatus("outstanding")}, + {"fully-satisfied", new ChargeStatus("fully-satisfied")}, + {"part-satisfied", new ChargeStatus("part-satisfied")}, + {"satisfied", new ChargeStatus("satisfied")} }; - public static readonly IReadOnlyDictionary PossibleRegisteredOfficeAddressCountry = new Dictionary + public static readonly IReadOnlyDictionary PossibleRegisteredOfficeAddressCountry = new Dictionary { - {"England", OfficeAddressCountry.England}, - {"Scotland", OfficeAddressCountry.Scotland}, - {"Wales", OfficeAddressCountry.Wales}, - {"Great Britain", OfficeAddressCountry.GreatBritain}, - {"Northern Ireland", OfficeAddressCountry.NorthernIreland}, - {"Not specified", OfficeAddressCountry.NotSpecified}, - {"United Kingdom", OfficeAddressCountry.UnitedKingdom} + {"England", "England"}, + {"Scotland", "Scotland"}, + {"Wales", "Wales"}, + {"Great Britain", "Great Britain"}, + {"Northern Ireland", "Northern Ireland"}, + {"Not specified", "Not specified"}, + {"United Kingdom", "United Kingdom"} }; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs index 8e99359..c864619 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs @@ -14,7 +14,7 @@ protected override string GetJson() [Fact] public void ThenMultipleItemsAreReturned() { - Result.ShouldBe(new[] { FilingSubcategory.Compulsory, FilingSubcategory.CourtOrder }); + Result.ShouldBe(new[] { new FilingSubcategory("compulsory"), new FilingSubcategory("court-order") }); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs index c97020c..59b6766 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForSingleValue.cs @@ -14,7 +14,7 @@ protected override string GetJson() [Fact] public void ThenSingleItemInAnArrayIsReturned() { - Result.ShouldBe(new[] { FilingSubcategory.Change }); + Result.ShouldBe(new[] { new FilingSubcategory("change") }); } } diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/ChargeValueTypeTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/ChargeValueTypeTests.cs new file mode 100644 index 0000000..42a7ec5 --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/ChargeValueTypeTests.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class ChargeValueTypeTests + { + [Theory] + [InlineData(typeof(ChargeStatus), "outstanding")] + [InlineData(typeof(ClassificationChargeType), "charge-description")] + [InlineData(typeof(ParticularType), "brief-description")] + [InlineData(typeof(SecuredDetailType), "amount-secured")] + [InlineData(typeof(AssetsCeasedReleased), "whole-property-released")] + public void KnownValues_RoundTrip(Type type, string wireValue) + { + var json = $"\"{wireValue}\""; + var value = JsonSerializer.Deserialize(json, type, CompaniesHouseJsonSerializerOptions.Default); + var serialized = JsonSerializer.Serialize(value, type, CompaniesHouseJsonSerializerOptions.Default); + + serialized.ShouldBe(json); + type.GetProperty("Value")!.GetValue(value).ShouldBe(wireValue); + } + } +} diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/FilingValueTypeTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/FilingValueTypeTests.cs new file mode 100644 index 0000000..becb1f7 --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/FilingValueTypeTests.cs @@ -0,0 +1,34 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class FilingValueTypeTests + { + [Theory] + [InlineData(typeof(FilingCategory), "mortgage")] + [InlineData(typeof(FilingSubcategory), "create")] + [InlineData(typeof(FilingHistoryStatus), "filing-history-available")] + [InlineData(typeof(ResolutionCategory), "miscellaneous")] + public void KnownValues_RoundTrip(Type type, string wireValue) + { + var json = $"\"{wireValue}\""; + var value = JsonSerializer.Deserialize(json, type, CompaniesHouseJsonSerializerOptions.Default); + var serialized = JsonSerializer.Serialize(value, type, CompaniesHouseJsonSerializerOptions.Default); + + serialized.ShouldBe(json); + type.GetProperty("Value")!.GetValue(value).ShouldBe(wireValue); + } + + [Fact] + public void UnknownFilingSubcategory_DoesNotThrow() + { + var value = JsonSerializer.Deserialize("\"brand-new-subcategory\"", CompaniesHouseJsonSerializerOptions.Default); + + value.Value.ShouldBe("brand-new-subcategory"); + value.IsKnown.ShouldBeFalse(); + } + } +} diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/InsolvencyValueTypeTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/InsolvencyValueTypeTests.cs new file mode 100644 index 0000000..c0b3308 --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/InsolvencyValueTypeTests.cs @@ -0,0 +1,30 @@ +using System.Text.Json; +using CompaniesHouse.Response.Insolvency; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class InsolvencyValueTypeTests + { + [Theory] + [InlineData(typeof(InsolvencyStatus), "in-administration")] + [InlineData(typeof(CaseDateType), "administration-started-on")] + [InlineData(typeof(InsolvencyCaseType), "creditors-voluntary-liquidation")] + public void KnownValues_RoundTrip(Type type, string wireValue) + { + var json = $"\"{wireValue}\""; + var value = JsonSerializer.Deserialize(json, type, CompaniesHouseJsonSerializerOptions.Default); + var serialized = JsonSerializer.Serialize(value, type, CompaniesHouseJsonSerializerOptions.Default); + + serialized.ShouldBe(json); + type.GetProperty("Value")!.GetValue(value).ShouldBe(wireValue); + } + + [Fact] + public void InsolvencyCaseType_ExposesDescriptions() + { + InsolvencyCaseType.CreditorsVoluntaryLiquidation.Description.ShouldBe("Creditors voluntary liquidation"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/ResponseValueTypes/PscValueTypeTests.cs b/tests/CompaniesHouse.Tests/ResponseValueTypes/PscValueTypeTests.cs new file mode 100644 index 0000000..9d6a651 --- /dev/null +++ b/tests/CompaniesHouse.Tests/ResponseValueTypes/PscValueTypeTests.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using CompaniesHouse.Response.PersonsWithSignificantControl; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.ResponseValueTypes +{ + public class PscValueTypeTests + { + [Theory] + [InlineData(typeof(PersonWithSignificantControlKind), "corporate-entity-person-with-significant-control")] + [InlineData(typeof(PersonWithSignificantControlNatureOfControl), "right-to-appoint-and-remove-directors")] + public void KnownValues_RoundTrip(Type type, string wireValue) + { + var json = $"\"{wireValue}\""; + var value = JsonSerializer.Deserialize(json, type, CompaniesHouseJsonSerializerOptions.Default); + var serialized = JsonSerializer.Serialize(value, type, CompaniesHouseJsonSerializerOptions.Default); + + serialized.ShouldBe(json); + type.GetProperty("Value")!.GetValue(value).ShouldBe(wireValue); + } + + [Fact] + public void UnknownPscNatureOfControl_DoesNotThrow() + { + var value = JsonSerializer.Deserialize("\"brand-new-psc-nature\"", CompaniesHouseJsonSerializerOptions.Default); + + value.Value.ShouldBe("brand-new-psc-nature"); + value.IsKnown.ShouldBeFalse(); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AppointmentsUriBuilderTests/AppointmentsUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/AppointmentsUriBuilderTests/AppointmentsUriBuilderTests.cs new file mode 100644 index 0000000..a067dc5 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/AppointmentsUriBuilderTests/AppointmentsUriBuilderTests.cs @@ -0,0 +1,18 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.AppointmentsUriBuilderTests +{ + public class AppointmentsUriBuilderTests + { + [Fact] + public void Build_EncodesOfficerIdAndPaging() + { + var uri = new AppointmentsUriBuilder().Build("abc/123", 10, 50); + + uri.ShouldBe(new Uri("officers/abc%2F123/appointments?items_per_page=50&start_index=10", UriKind.Relative)); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/ChargesUriBuilderTests/ChargesUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/ChargesUriBuilderTests/ChargesUriBuilderTests.cs new file mode 100644 index 0000000..46e4251 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/ChargesUriBuilderTests/ChargesUriBuilderTests.cs @@ -0,0 +1,26 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.ChargesUriBuilderTests +{ + public class ChargesUriBuilderTests + { + [Fact] + public void Build_List_EncodesCompanyNumberAndPaging() + { + var uri = new ChargesUriBuilder().Build("00/123", 25, 35); + + uri.ShouldBe(new Uri("company/00%2F123/charges?items_per_page=35&start_index=25", UriKind.Relative)); + } + + [Fact] + public void Build_Single_EncodesCompanyNumber() + { + var uri = new ChargesUriBuilder().Build("00/123", "charge-id"); + + uri.ShouldBe(new Uri("company/00%2F123/charges/charge-id", UriKind.Relative)); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/CompanyInsolvencyInformationUriBuilderTests/CompanyInsolvencyInformationUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/CompanyInsolvencyInformationUriBuilderTests/CompanyInsolvencyInformationUriBuilderTests.cs new file mode 100644 index 0000000..5ca642a --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/CompanyInsolvencyInformationUriBuilderTests/CompanyInsolvencyInformationUriBuilderTests.cs @@ -0,0 +1,18 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.CompanyInsolvencyInformationUriBuilderTests +{ + public class CompanyInsolvencyInformationUriBuilderTests + { + [Fact] + public void Build_EncodesCompanyNumber() + { + var uri = new CompanyInsolvencyInformationUriBuilder().Build("SC/171417"); + + uri.ShouldBe(new Uri("company/SC%2F171417/insolvency", UriKind.Relative)); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/DocumentUriBuilderTests/DocumentUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/DocumentUriBuilderTests/DocumentUriBuilderTests.cs new file mode 100644 index 0000000..1672d8d --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/DocumentUriBuilderTests/DocumentUriBuilderTests.cs @@ -0,0 +1,26 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.DocumentUriBuilderTests +{ + public class DocumentUriBuilderTests + { + [Fact] + public void MetadataBuilder_EncodesDocumentId() + { + var uri = new DocumentMetadataUriBuilder().Build("abc/123"); + + uri.ShouldBe(new Uri("/document/abc%2F123", UriKind.Relative)); + } + + [Fact] + public void ContentBuilder_EncodesDocumentId() + { + var uri = new DocumentContentUriBuilder().Build("abc/123"); + + uri.ShouldBe(new Uri("/document/abc%2F123/content", UriKind.Relative)); + } + } +} From 92822acd30a78baf4ec12b0e6e63b0543b9dc968 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 2 Jul 2026 00:13:18 +0100 Subject: [PATCH 16/38] Extend integration-test skip mechanism to remaining endpoint suites (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. --- .../Tests/AppointmentsTests/OfficersTestsValid.cs | 4 ++-- .../Tests/ChargesTests/ChargeByIdTestsInValid.cs | 2 +- .../Tests/ChargesTests/ChargeByIdTestsValid.cs | 4 ++-- .../Tests/ChargesTests/ChargesListTestsInValid.cs | 2 +- .../Tests/ChargesTests/ChargesListTestsValid.cs | 4 ++-- .../CompanyFilingHistoryTestsInvalid.cs | 2 +- .../CompanyFilingHistoryTestsValid.cs | 4 ++-- .../FilingHistoryByTransactionIdTestsInvalid.cs | 2 +- .../FilingHistoryByTransactionIdTestsValid.cs | 4 ++-- .../CompanyInsolvencyInformationTestsInvalid.cs | 2 +- .../CompanyInsolvencyInformationTestsValid.cs | 4 ++-- .../Tests/DocumentTests/DocumentDownloadTests.cs | 4 ++-- .../Tests/DocumentTests/DocumentMetadataTestsInvalid.cs | 2 +- .../Tests/DocumentTests/DocumentMetadataTestsValid.cs | 4 ++-- .../PersonsWithSignificantControlTestsInValid.cs | 2 +- .../PersonsWithSignificantControlTestsValid.cs | 4 ++-- .../RegisteredOfficeAddressesTestsInValid.cs | 2 +- .../RegisteredOfficeAddressesTestsValid.cs | 4 ++-- 18 files changed, 28 insertions(+), 28 deletions(-) diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs index 4746e31..857a374 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs @@ -17,13 +17,13 @@ await WhenRetrievingAppointmentsForAValidOfficer() ; } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreNotEmpty() { Result.Data.Items.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public void ThenObservedEnvelopeFieldsAreReturned() { Result.Data.Kind.ShouldBe("personal-appointment"); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs index baf2a10..29af9cb 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs @@ -13,7 +13,7 @@ public class ChargeByIdTestsInValid : ChargesTestBase protected override async Task When() => Result = await Client.GetChargeByIdAsync(CompanyNumber, ChargeId); - [Fact] + [IntegrationFact] public void ThenChargesListIsNull() => Result.Data.ShouldBeNull(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs index 077d599..c79de55 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs @@ -13,10 +13,10 @@ public class ChargeByIdTestsValid : ChargesTestBase protected override async Task When() => Result = await Client.GetChargeByIdAsync(CompanyNumber, ChargeId); - [Fact] + [IntegrationFact] public void ThenChargesListIsNull() => Result.Data.ShouldNotBeNull(); - [Fact] + [IntegrationFact] public void ThenKnownObservedFieldsAreReturned() { Result.Data.Status.Value.ShouldNotBeNullOrWhiteSpace(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs index 671a8b7..5dced7a 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs @@ -12,7 +12,7 @@ public class ChargesListTestsInValid : ChargesTestBase protected override async Task When() => Result = await Client.GetChargesListAsync(CompanyNumber); - [Fact] + [IntegrationFact] public void ThenChargesListIsNull() => Result.Data.Items.ShouldBeEmpty(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs index 9776840..d28156a 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs @@ -13,7 +13,7 @@ public ChargesListTestsValid() _client = new CompaniesHouseClient(new CompaniesHouseSettings(CompaniesHouseUris.Default, Keys.ApiKey)); } - [Theory] + [IntegrationTheory] [InlineData("03977902")] [InlineData("00445790")] [InlineData("00002065")] @@ -25,7 +25,7 @@ public async Task ThenChargesListIsNotEmpty(string companyNumber) result.Data.Items.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenKnownChargeListIncludesObservedGeneratedValues() { var result = await _client.GetChargesListAsync("03977902"); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs index 9243d12..e2f0be7 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs @@ -18,7 +18,7 @@ await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() ; } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreNull() { _result.Data.ShouldNotBeNull(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs index 6d1fce6..5eff803 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs @@ -16,7 +16,7 @@ public CompanyFilingHistoryTestsValid() _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [Theory] + [IntegrationTheory] [InlineData("03977902")] [InlineData("00445790")] [InlineData("00002065")] @@ -41,7 +41,7 @@ public async Task ThenTheDataItemsAreNotEmpty(string companyNumber) results.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public async Task ThenKnownFilingHistoryIncludesObservedPaginationFields() { var result = await _client.GetCompanyFilingHistoryAsync("00445790"); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs index 9aa3295..468a804 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs @@ -19,7 +19,7 @@ await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() ; } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreNull() { _result.Data.ShouldBeNull(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs index 06ae8d9..8ba7138 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs @@ -19,13 +19,13 @@ await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() ; } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreNull() { _result.Data.ShouldNotBeNull(); } - [Fact] + [IntegrationFact] public void ThenObservedFieldsAreReturned() { _result.Data.Links?.DocumentMetaData.ShouldNotBeNullOrWhiteSpace(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs index 6b611fe..15ecd40 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs @@ -12,7 +12,7 @@ public class CompanyInsolvencyInformationTestsInvalid : CompanyInsolvencyInforma protected override async Task When() => Result = await Client.GetCompanyInsolvencyInformationAsync(InvalidCompanyNumber); - [Fact] + [IntegrationFact] public void ThenTheItemsAreNull() => Result.Data.ShouldBeNull(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs index 4ba7aa5..685330f 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs @@ -12,10 +12,10 @@ public class CompanyInsolvencyInformationTestsValid : CompanyInsolvencyInformati protected override async Task When() => Result = await Client.GetCompanyInsolvencyInformationAsync(ValidCompanyNumber); - [Fact] + [IntegrationFact] public void ThenTheItemsAreReturned() => Result.Data.ShouldNotBeNull(); - [Fact] + [IntegrationFact] public void ThenObservedStatusesAndCaseTypesAreReturned() { Result.Data.Cases.ShouldNotBeNull(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs index 3bf926c..058ef4b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs @@ -17,7 +17,7 @@ public class DocumentDownloadTests : DocumentTestBase private async Task DownloadingDocument() => _result = await Client.DownloadDocumentAsync(DocumentId); - [Fact] + [IntegrationFact] public async Task ThenDocumentContentIsNotEmpty() { using var memoryStream = new MemoryStream(); @@ -40,7 +40,7 @@ public class DocumentDownloadTestsInvalid : DocumentTestBase private async Task DownloadingDocument() => _result = await Client.DownloadDocumentAsync(DocumentId); - [Fact] + [IntegrationFact] public void ThenDocumentDataIsNull() => _result.Data.ShouldBeNull(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs index d6e5dff..5324d4c 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs @@ -16,7 +16,7 @@ public class DocumentTestsInvalid : DocumentTestBase private async Task RetrievingDocumentMetadata() => Result = await Client.GetDocumentMetadataAsync(DocumentId); - [Fact] + [IntegrationFact] public void ThenDocumentMetadataIsNull() => Result.Data.ShouldBeNull(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs index e0419f8..6d0e2bf 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs @@ -15,7 +15,7 @@ public class DocumentTestsValid : DocumentTestBase private async Task RetrievingDocumentMetadata() => Result = await Client.GetDocumentMetadataAsync(DocumentId); - [Fact] + [IntegrationFact] public void ThenDocumentMetadataAreNotEmpty() { Result.Data.CompanyNumber.ShouldNotBeNullOrEmpty(); @@ -23,7 +23,7 @@ public void ThenDocumentMetadataAreNotEmpty() Result.Data.Resources.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public void ThenObservedFilenameAndDocumentLinkAreReturned() { Result.Data.Filename.ShouldNotBeNullOrWhiteSpace(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs index 6e9c787..701c5dc 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs @@ -15,7 +15,7 @@ protected override async Task When() await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany(); } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreNull() { _result.Data.ShouldNotBeNull(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs index 9160e55..66bc617 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs @@ -16,13 +16,13 @@ protected override async Task When() await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnValidCompany(); } - [Fact] + [IntegrationFact] public void ThenTheDataItemsAreNotEmpty() { _result.Data.Items.ShouldNotBeEmpty(); } - [Fact] + [IntegrationFact] public void ThenObservedCountsAndKindsAreReturned() { _result.Data.TotalResults.ShouldNotBeNull(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs index c742d66..25b9955 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs @@ -11,7 +11,7 @@ public class RegisteredOfficeAddressesTestsInValid : RegisteredOfficeAddressTest protected override async Task When() => Result = await Client.GetRegisteredOfficeAddress(InvalidCompanyNumber); - [Fact] + [IntegrationFact] public void ThenRegisteredOfficeAddressIsNull() => Result.Data.ShouldBeNull(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs index 865f602..929dc2a 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs @@ -11,10 +11,10 @@ public class RegisteredOfficeAddressesTestsValid : RegisteredOfficeAddressTestBa protected override async Task When() => Result = await Client.GetRegisteredOfficeAddress(CompanyNumber); - [Fact] + [IntegrationFact] public void ThenRegisteredOfficeAddressIsNotNull() => Result.Data.ShouldNotBeNull(); - [Fact] + [IntegrationFact] public void ThenObservedFieldsAreReturned() { Result.Data.Country.ShouldBe("United Kingdom"); From 1d58f791284dadd853f1b47687dbbc8ed68afa2a Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 2 Jul 2026 08:53:32 +0100 Subject: [PATCH 17/38] Reference static members from KnownValues/Descriptions in generated enum 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. --- .../ValueTypeEmitter.cs | 18 +++++++++++++++--- .../EnumValueTypeGeneratorTests.cs | 4 ++-- .../ValueTypeEmitterSnapshotTests.cs | 8 ++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs b/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs index 2959b43..9492077 100644 --- a/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs +++ b/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs @@ -62,20 +62,32 @@ public static string EmitValueType(EnumMapEntry entry, MergedGroup group) sb.AppendLine(); sb.AppendLine(" private static readonly HashSet KnownValues = new(StringComparer.Ordinal)"); sb.AppendLine(" {"); - foreach (var (_, wireValue) in members) + foreach (var (memberName, _) in members) { - sb.AppendLine($" \"{Escape(wireValue)}\","); + sb.AppendLine($" {memberName}.Value,"); } sb.AppendLine(" };"); if (entry.IncludeDescriptions) { + var memberNamesByWireValue = new Dictionary(StringComparer.Ordinal); + foreach (var (memberName, wireValue) in members) + { + memberNamesByWireValue[wireValue] = memberName; + } + sb.AppendLine(); sb.AppendLine(" private static readonly IReadOnlyDictionary Descriptions = new Dictionary(StringComparer.Ordinal)"); sb.AppendLine(" {"); foreach (var wireValue in group.WireValues) { - sb.AppendLine($" [\"{Escape(wireValue)}\"] = \"{Escape(group.GetDescription(wireValue))}\","); + // The empty-string wire value has no static member (it represents the + // type's absent/default state - see BuildMembers), so it falls back to + // the literal key rather than a member reference. + var key = memberNamesByWireValue.TryGetValue(wireValue, out var memberName) + ? $"{memberName}.Value" + : $"\"{Escape(wireValue)}\""; + sb.AppendLine($" [{key}] = \"{Escape(group.GetDescription(wireValue))}\","); } sb.AppendLine(" };"); } diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs index 24a0471..cec1970 100644 --- a/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs +++ b/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs @@ -36,7 +36,7 @@ public void GeneratesAValueTypeAndConverterForAConfiguredGroup() generated["CompanyStatus.g.cs"].ShouldContain("public readonly record struct CompanyStatus"); generated["CompanyStatus.g.cs"].ShouldContain("public static CompanyStatus Active => new(\"active\");"); generated["CompanyStatus.g.cs"].ShouldContain("public static CompanyStatus Dissolved => new(\"dissolved\");"); - generated["CompanyStatus.g.cs"].ShouldContain("[\"active\"] = \"Active\""); + generated["CompanyStatus.g.cs"].ShouldContain("[Active.Value] = \"Active\""); generated.ShouldContainKey("CompanyStatusJsonConverter.g.cs"); generated["CompanyStatusJsonConverter.g.cs"].ShouldContain("public sealed class CompanyStatusJsonConverter : JsonConverter"); @@ -61,7 +61,7 @@ public void ExtrasOverlayOverridesAndAppendsToSubmoduleData() var generated = RunGenerator(additionalFiles); - generated["CompanyStatus.g.cs"].ShouldContain("[\"active\"] = \"Overridden\""); + generated["CompanyStatus.g.cs"].ShouldContain("[Active.Value] = \"Overridden\""); generated["CompanyStatus.g.cs"].ShouldContain("public static CompanyStatus ClosedOn => new(\"closed-on\");"); } diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs index e6c5abf..f2f6535 100644 --- a/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs +++ b/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs @@ -74,14 +74,14 @@ public WidgetState(string? value) private static readonly HashSet KnownValues = new(StringComparer.Ordinal) { - "on", - "off", + On.Value, + Off.Value, }; private static readonly IReadOnlyDictionary Descriptions = new Dictionary(StringComparer.Ordinal) { - ["on"] = "On", - ["off"] = "Off", + [On.Value] = "On", + [Off.Value] = "Off", }; } } From 5ef37c5c900178d8c77ef87dcfd646d631ecea30 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Thu, 2 Jul 2026 09:02:37 +0100 Subject: [PATCH 18/38] Rewrite README, add MIGRATION.md, and update sample project for v-next --- MIGRATION.md | 188 ++++++++++++++ README.md | 278 +++++++++++++++------ samples/SampleProject/Program.cs | 172 +++++++++---- samples/SampleProject/SampleProject.csproj | 5 + 4 files changed, 517 insertions(+), 126 deletions(-) create mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..5a3bdcd --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,188 @@ +# Migration guide: v-next (breaking changes) + +This is a new major version of `CompaniesHouse.NET`. It is a deliberate, +breaking rewrite - it does not attempt to be a drop-in replacement. This guide +covers every breaking change with a before/after snippet so you can migrate +call sites methodically. + +## Target frameworks + +**Before:** `net45`, `netstandard2.0` (or similar). +**After:** `net8.0`, `net9.0`, `net10.0` only. + +If you're on .NET Framework or an older .NET/`netstandard` target, you'll need +to stay on the previous major version, or upgrade your app to a supported TFM. + +## JSON: `Newtonsoft.Json` → `System.Text.Json` + +The client no longer depends on `Newtonsoft.Json` at all (see issue #188). All +(de)serialization uses `System.Text.Json`. + +This mostly only matters if you had custom `JsonConverter`s or relied on +`Newtonsoft`-specific behaviour (e.g. `JObject`/`JToken` on the response +models, or `[JsonProperty]` attributes): + +```diff +- using Newtonsoft.Json; +- var json = JsonConvert.SerializeObject(profile); ++ using System.Text.Json; ++ using CompaniesHouse; ++ var json = JsonSerializer.Serialize(profile, CompaniesHouseJsonSerializerOptions.Default); +``` + +If you were deserializing raw API responses yourself, use +`CompaniesHouseJsonSerializerOptions.Default` so enum value types and casing +are handled consistently with the client. + +## Enums → string-backed value types + +This is the biggest behavioural change. Every API "enum" (company status, +officer role, charge type, jurisdiction, ...) used to be a plain C# `enum`. +It's now a **string-backed, readonly `record struct`** that never throws for +an unrecognised value. + +**Before:** + +```csharp +public enum CompanyStatus +{ + Active, + Dissolved, + // ... a fixed, hand-maintained list +} + +switch (profile.CompanyStatus) +{ + case CompanyStatus.Active: + Console.WriteLine("is active"); + break; + case CompanyStatus.Dissolved: + Console.WriteLine("is dissolved"); + break; + default: + // Companies House adding a new status value could throw a + // JsonSerializationException during deserialization, or silently + // map to an unrelated member, depending on the old converter. + break; +} +``` + +**After:** + +```csharp +var description = profile.CompanyStatus switch +{ + var s when s == CompanyStatus.Active => "is active", + var s when s == CompanyStatus.Dissolved => "is dissolved", + var s when s.IsKnown => s.Description, // any other value this library recognises + var s => $"unrecognised status: {s.Value}", // never throws, even for brand-new values +}; +``` + +Key API differences to update at each call site: + +- Replace `EnumType.Member` usages with the equivalent static member on the + value type (e.g. `CompanyStatus.Active` still works, but it's a value not an + `enum` member - `==`/`!=` work as expected via `record struct` equality). +- Replace `switch` statements on the type itself with pattern matching against + equality (`s == CompanyStatus.Active`), since the value type isn't a closed + set of cases. +- Anywhere you called `.ToString()` expecting the C# member name (e.g. + `"Active"`), note that `ToString()` now returns the **raw wire value** + (e.g. `"active"`); use `.Description` for a friendly name. +- Anywhere you relied on `Enum.Parse`/`Enum.TryParse`, construct the value type + directly from the wire string instead: `new CompanyStatus("active")`. + +See the [README's enum section](README.md#enumvalue-type-handling) and the +[design rationale](https://kevsoft.net/2026/06/28/enums-in-api-contracts.html) +for more detail. + +## `CompaniesHouseClientResponse` shape change + +**Before:** the response wrapper exposed only the deserialized body (shape +varied by version; some versions returned `T` directly). + +**After:** every client method returns `CompaniesHouseClientResponse`, +which always carries transport metadata: + +```diff +- var profile = await client.GetCompanyProfileAsync(companyNumber); +- // profile was the data itself, or null ++ var result = await client.GetCompanyProfileAsync(companyNumber); ++ var profile = result.Data; // null for non-success responses ++ result.StatusCode; // now available ++ result.RetryAfter; // now available - see #181/#182 ++ result.IsSuccess; // convenience check for 2xx +``` + +Update any code that used the return value directly as the model to go +through `.Data` instead, and consider using `.StatusCode`/`.IsSuccess` for +error handling instead of catching exceptions or checking for `null` alone. + +## Default base URI change + +**Before:** `https://api.companieshouse.gov.uk/` +(or a similar legacy host, depending on version). + +**After:** `https://api.company-information.service.gov.uk/` +(`CompaniesHouseUris.Default`). + +If you previously passed a base URI explicitly, no change is needed. If you +relied on the implicit default, verify it now resolves to the new host - your +existing API key works against both. + +## DI package changes + +**Before:** + +```csharp +services.AddCompaniesHouseClient("Your API Key"); +``` + +**After:** the same call still works, plus new overloads built on +`IOptions`: + +```csharp +// Still works +services.AddCompaniesHouseClient(apiKey); + +// New: configure via a delegate +services.AddCompaniesHouseClient(options => +{ + options.ApiKey = apiKey; + options.BaseUri = CompaniesHouseUris.Default; +}); + +// New: bind from IConfiguration +services.AddCompaniesHouseClient(configuration); // reads the "CompaniesHouse" section + +// New: customise the underlying IHttpClientBuilder +services.AddCompaniesHouseClient(apiKey, builder => builder.AddStandardResilienceHandler()); +``` + +Document-endpoint registration follows the same pattern via +`AddCompaniesHouseDocumentClient`, reading from the `CompaniesHouseDocument` +configuration section by default. + +## Test stack (if you forked/contributed tests) + +Tests moved from NUnit + FluentAssertions to **xUnit + Shouldly** (Fluent +Assertions' license changed to a paid tier from v8): + +```diff +- [Test] +- public void Should_return_active_status() +- { +- result.CompanyStatus.Should().Be(CompanyStatus.Active); +- } ++ [Fact] ++ public void Should_return_active_status() ++ { ++ result.CompanyStatus.ShouldBe(CompanyStatus.Active); ++ } +``` + +## Getting help + +If you hit a migration issue not covered here, please open an issue at +. diff --git a/README.md b/README.md index 3fea402..2658ac6 100644 --- a/README.md +++ b/README.md @@ -1,175 +1,301 @@ # CompaniesHouse.NET -A simple .NET client wrapper for CompaniesHouse API. +A .NET client for the [Companies House Public Data API](https://developer-specs.company-information.service.gov.uk/companies-house-public-data-api/reference). [![install from nuget](http://img.shields.io/nuget/v/CompaniesHouse.svg?style=flat-square)](https://www.nuget.org/packages/CompaniesHouse) [![downloads](http://img.shields.io/nuget/dt/CompaniesHouse.svg?style=flat-square)](https://www.nuget.org/packages/CompaniesHouse) -[![Build status](https://ci.appveyor.com/api/projects/status/6uv0pemfr07nf4bs/branch/master?svg=true)](https://ci.appveyor.com/project/kevbite/companieshouse-net/branch/master) +[![Build status](https://github.com/kevbite/CompaniesHouse.NET/actions/workflows/continuous-integration-workflow.yml/badge.svg)](https://github.com/kevbite/CompaniesHouse.NET/actions/workflows/continuous-integration-workflow.yml) -## Getting Started +> **Upgrading from an earlier version?** This is a major, deliberately breaking +> rewrite. See [MIGRATION.md](MIGRATION.md) for the full list of changes and +> before/after snippets. -CompaniesHouse.NET can be installed via the package manager console by executing the following commandlet: +## Installation + +Two NuGet packages are published: ```powershell -PM> Install-Package CompaniesHouse +# The core client and all request/response models +dotnet add package CompaniesHouse + +# Optional: DI helpers for ASP.NET Core / generic-host apps +dotnet add package CompaniesHouse.Extensions.Microsoft.DependencyInjection ``` -Once we have the package installed, we can then create a `CompaniesHouseSettings` with a ApiKey which can be created via the [CompaniesHouse API website](https://developer.companieshouse.gov.uk/developer/applications) +Both packages multi-target `net8.0`, `net9.0` and `net10.0`. + +## Getting an API key + +Register an application on the +[Companies House developer hub](https://developer.company-information.service.gov.uk/) +to get an API key for the public data API. + +## Getting started + +### Constructing the client directly ```csharp +using CompaniesHouse; + var settings = new CompaniesHouseSettings(apiKey); + +using var client = new CompaniesHouseClient(settings); ``` -We need to now create a `CompaniesHouseClient` - passing in the settings that we've just created. +`CompaniesHouseClient` implements `IDisposable` - always dispose it (or wrap it +in a `using` block) once you're done, since it owns an underlying `HttpClient`. + +You can also construct the client from your own `HttpClient` (useful in tests, +or when you want full control over handlers/base address): ```csharp -using(var client = new CompaniesHouseClient(settings)) -{ - // Do some work... -} +using var httpClient = new HttpClient { BaseAddress = CompaniesHouseUris.Default }; +httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{apiKey}:"))); + +using var client = new CompaniesHouseClient(httpClient); ``` -This is the object we'll use going forward for any interaction to the CompaniesHouse API, but don't forget to call `Dispose` after you've finish (or wrap in a `using` block). +### Dependency injection -### ASP.NET Core +Install `CompaniesHouse.Extensions.Microsoft.DependencyInjection` and register +the client on your `IServiceCollection`. Several overloads are available, +built on `IOptions`: -If you're using [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-5.0) you can configure the IoC container with one simple extention method call. But first you'll need to install the [CompaniesHouse.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/CompaniesHouse.Extensions.Microsoft.DependencyInjection/) NuGet package. +```csharp +// Simplest - just an API key +services.AddCompaniesHouseClient(apiKey); -```powershell -PM> Install-Package CompaniesHouse.Extensions.Microsoft.DependencyInjection +// A custom base URI (e.g. against a sandbox/test host) +services.AddCompaniesHouseClient(new Uri("https://api.company-information.service.gov.uk/"), apiKey); + +// Full control via a delegate +services.AddCompaniesHouseClient(options => +{ + options.ApiKey = apiKey; + options.BaseUri = CompaniesHouseUris.Default; +}); + +// Bind from IConfiguration (defaults to the "CompaniesHouse" section) +services.AddCompaniesHouseClient(configuration); +``` + +Every overload also accepts an optional `configureHttpClientBuilder` delegate, +letting you customise the underlying `IHttpClientBuilder` (e.g. to add Polly +resilience handlers): + +```csharp +services.AddCompaniesHouseClient(apiKey, builder => builder.AddStandardResilienceHandler()); ``` -Once installed, in your [Startup class](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-5.0) in the `ConfigureServices` method, call the `AddCompaniesHouseClient` method on the `services` object. +Once registered, inject `ICompaniesHouseClient` - the main facade interface - +into your dependencies: ```csharp -public void ConfigureServices(IServiceCollection services) +public class MyPageModel(ICompaniesHouseClient client) : PageModel { // ... - services.AddCompaniesHouseClient("Your API Key"); } ``` -This will then register a range of interfaces in to the IoC container that can be injected in to any of your dependancies. A list of these can be found in the [ServiceCollectionExtensionsTests](https://github.com/kevbite/CompaniesHouse.NET/blob/master/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs#L17). +Document endpoints (`GetDocumentMetadataAsync`/`DownloadDocumentAsync`) talk to +a separate host and are registered independently via +`AddCompaniesHouseDocumentClient`, with the same set of overloads. + +### `IConfiguration` example + +```json +{ + "CompaniesHouse": { + "ApiKey": "your-api-key", + "BaseUri": "https://api.company-information.service.gov.uk/" + } +} +``` + +```csharp +services.AddCompaniesHouseClient(builder.Configuration); +``` + +## Enum/value-type handling -For example if we wanted to use the `ICompaniesHouseClient` which is the main facade interface, we could inject this in to our page model. +Every "enum" in the Companies House API (company status, officer role, charge +type, etc.) is modelled as a **string-backed, readonly `record struct`** +rather than a plain C# `enum`. This is deliberate: Companies House regularly +adds new wire values, and a plain `enum` throws (or silently defaults) the +moment it sees one it doesn't recognise. See the +[design rationale](https://kevsoft.net/2026/06/28/enums-in-api-contracts.html) +for the full background. ```csharp -public class MyPageModel : PageModel +CompanyStatus status = companyProfile.CompanyStatus; + +status.Value; // the raw wire value, e.g. "active" +status.HasValue; // false only for the default/absent value +status.IsKnown; // true if this library recognises the value +status.Description; // a friendly description for known values, e.g. "Active" +``` + +Compare against the generated static members (`CompanyStatus.Active`, +`CompanyStatus.Dissolved`, ...) rather than raw strings, and always keep a +fallback arm for values you don't recognise yet: + +```csharp +var description = status switch { - private readonly ICompaniesHouseClient _client; + _ when status == CompanyStatus.Active => "is active", + _ when status == CompanyStatus.Dissolved => "is dissolved", + _ when status.IsKnown => status.Description, + _ => $"unrecognised status: {status.Value}", // never throws +}; +``` - public Index2Model(ICompaniesHouseClient client) - { - _client = client; - } -} +New values ship as a new minor version of the `CompaniesHouse` package (the +value types are generated from the official +[`api-enumerations`](https://github.com/companieshouse/api-enumerations) data) +- you never need to hand-edit or wait on a code change to keep deserializing. + +## Reading responses + +Every client method returns a `CompaniesHouseClientResponse`, which carries +transport metadata alongside the deserialized body: + +```csharp +var result = await client.GetCompanyProfileAsync(companyNumber); + +result.StatusCode; // the HTTP status code, e.g. 200 or 404 +result.IsSuccess; // true for 2xx responses +result.ReasonPhrase; // the HTTP reason phrase, if any +result.RetryAfter; // the Retry-After header value, if present (e.g. on 429s) +result.Headers; // the raw response headers +result.Data; // the deserialized body, or default for non-success responses ``` -Under the hood this is using [typed clients](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0#typed-clients) for the `HttpClient` used by CompaniesHouse.NET and it's also possible to use this package with any dependency injection framework that implements `Microsoft.Extensions.DependencyInjection.Abstractions`. +`Data` is `null`/`default` (rather than throwing) when the API returns a +non-success response, so check it (or `IsSuccess`) before using it: + +```csharp +var result = await client.GetCompanyProfileAsync(companyNumber); +if (result.Data is null) +{ + // no match for that company number (404), or another non-success response + return; +} +``` ## Usage ### Searching for resources -To search for a resource, we first need to create a `SearchRequest` with details of the search we require. - ```csharp -var request = new SearchRequest() +var request = new SearchAllRequest { Query = "Jay2Base", - StartIndex = 10, + StartIndex = 0, ItemsPerPage = 10 }; -``` - -We can then pass the `SearchRequest` object in to the `SearchAllAsync` method and await on the task, this will then return all related resources. -```csharp var result = await client.SearchAllAsync(request); -foreach (var item in _result.Data.Items) +foreach (var item in result.Data.Items) { // Do something... } ``` -If we need to be more precise on the resources we require, we can then pass the request object in to the required search method, either `SearchCompanyAsync` or `SearchOfficerAsync` or `SearchDisqualifiedOfficerAsync` and await on the task. +For a specific resource type, use `SearchCompanyAsync`, `SearchOfficerAsync`, +`SearchDisqualifiedOfficerAsync`, `SearchCompaniesAlphabeticallyAsync`, +`SearchDissolvedCompaniesAsync` or `AdvancedCompanySearchAsync` with the +matching request type. ```csharp -var result1 = await client.SearchCompanyAsync(request); - -var result2 = await client.SearchOfficerAsync(request); - -var result3 = await client.SearchDisqualifiedOfficerAsync(request); +var companies = await client.SearchCompanyAsync(new SearchCompanyRequest { Query = "Jay2Base" }); +var officers = await client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Jay2Base" }); +var disqualified = await client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "Jay2Base" }); ``` ### Getting a company profile -To get a company profile, we pass a company number in to the `GetCompanyProfileAsync` method and await on the task. - ```csharp var result = await client.GetCompanyProfileAsync("10440441"); ``` -If there was no match for that company number then `null` will be returned. +`result.Data` is `null` if there was no match for that company number. -### Getting company officer list - -To get a list of officers for a company, we pass a company number in to the `GetOfficersAsync` method and await on the task. +### Getting the company officer list ```csharp var result = await client.GetOfficersAsync("03977902"); + +// Optionally page the results +var page = await client.GetOfficersAsync("03977902", startIndex: 10, pageSize: 10); ``` -We can also pass in some optional parameters of `startIndex` and `pageSize` which will allow us to page the results. +A single officer appointment can be fetched directly: ```csharp -var result = await client.GetOfficersAsync("03977902", 10, 10); +var officer = await client.GetOfficerByAppointmentIdAsync("03977902", appointmentId); ``` -### Getting company filing history list - -To get a list of the filing history for a company, we can pass a company number to the `GetCompanyFilingHistoryAsync` method and await on the task. +### Getting officer appointments ```csharp -var result = await client.GetCompanyFilingHistoryAsync("10440441"); +var result = await client.GetAppointmentsAsync(officerId, startIndex: 0, pageSize: 25); ``` -We can also pass in some optional parameters of `startIndex` and `pageSize` which will allow us to page the results. +### Getting the company filing history ```csharp -var result = await client.GetCompanyFilingHistoryAsync("10440441", 10, 10); +var result = await client.GetCompanyFilingHistoryAsync("10440441", startIndex: 0, pageSize: 25); + +var item = await client.GetFilingHistoryByTransactionAsync("10440441", transactionId); ``` ### Getting company insolvency information -To get the insolvency information for a company, we can pass a company number to the `GetCompanyInsolvencyInformationAsync` method and await on the task. - ```csharp var result = await client.GetCompanyInsolvencyInformationAsync("10440441"); ``` -If there was no insolvency information for the given company number then `null` will be returned. +`result.Data` is `null` if there is no insolvency information for the company. -### Getting document metadata information +### Getting persons with significant control -To get the metadata for a document, we can pass document id to the `GetDocumentMetadataAsync` method and await on the task. +```csharp +var result = await client.GetPersonsWithSignificantControlAsync("10440441", startIndex: 0, pageSize: 25); +``` + +### Getting charges ```csharp -var result = await client.GetDocumentMetadataAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4"); +var charges = await client.GetChargesListAsync("10440441", startIndex: 0, pageSize: 25); + +var charge = await client.GetChargeByIdAsync("10440441", chargeId); ``` -If there was no document metadata for the given document id then `null` will be returned. +### Getting the registered office address -### Downloading a document +```csharp +var result = await client.GetRegisteredOfficeAddress("10440441"); +``` -To download a document, we can pass document id to the `DownloadDocumentAsync` method and await on the task. +### Getting document metadata and downloading a document ```csharp -var result = await client.DownloadDocumentAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4"); +var metadata = await client.GetDocumentMetadataAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4"); + +var document = await client.DownloadDocumentAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4"); ``` -If there was no document for the given document id then `null` will be returned. +`result.Data` is `null` if there was no metadata/document for the given id. + +More endpoints land progressively - see `.plans/` for what's in flight. + +## Sample project + +A runnable end-to-end example, covering direct construction, DI registration, +search, company profile, officers, and gracefully handling an unrecognised +enum value, lives in [`samples/SampleProject`](samples/SampleProject). ## Contributing @@ -177,9 +303,17 @@ If there was no document for the given document id then `null` will be returned. 1. Hack! 1. Pull Request +See [AGENTS.md](AGENTS.md) for repository conventions, build/test commands and +the design decisions behind the v-next rewrite. -## Running Unit tests +## Running tests -In order for the integration tests to run, you need an API Key from [CompaniesHouse API website](https://developer.companieshouse.gov.uk/developer/applications) -Setup API Key in an environment variable called "api_key", and then run the tests. +```powershell +dotnet restore +dotnet build -c Release +dotnet test -c Release +``` +Integration tests hit the real Companies House API and need an API key in the +`COMPANIES_HOUSE_API_KEY` environment variable - they're skipped/fail without +one, which is expected when working offline. diff --git a/samples/SampleProject/Program.cs b/samples/SampleProject/Program.cs index b2c56e7..0c77cf4 100644 --- a/samples/SampleProject/Program.cs +++ b/samples/SampleProject/Program.cs @@ -1,71 +1,135 @@ -using CompaniesHouse; +using CompaniesHouse; using CompaniesHouse.Request; -using CompaniesHouse.Response.Search.OfficerSearch; +using CompaniesHouse.Response; +using CompaniesHouse.Response.Search.AllSearch; using CompaniesHouse.Response.Search.CompanySearch; using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch; +using CompaniesHouse.Response.Search.OfficerSearch; +using Microsoft.Extensions.DependencyInjection; using System; using System.Linq; using System.Threading.Tasks; -using CompaniesHouse.Response.Search.AllSearch; -namespace SampleProject +namespace SampleProject; + +class Program { - class Program + // Add your API key from https://developer.company-information.service.gov.uk/ + private const string ApiKey = ""; + + static async Task Main() { - static async Task Main( string[] args ) + if (string.IsNullOrEmpty(ApiKey)) { - string api_key = ""; //Add your api key from companies house api here https://developer.companieshouse.gov.uk/developer/applications - if (!api_key.Any()) - { - Console.WriteLine( $"No API Key found. Please edit Program.cs to add it in." ); - return; - } - - Console.WriteLine( $"Starting up - Found this api key: {api_key}" ); - CompaniesHouseClientResponse result = null; - string nameToSearchFor = "Bigman"; - var settings = new CompaniesHouseSettings( api_key ); - using (var client = new CompaniesHouseClient( settings )) - { - var request = new SearchAllRequest() - { - Query = nameToSearchFor, - StartIndex = 0, - ItemsPerPage = 10 - }; - - result = await client.SearchAllAsync( request ); - } - - DisplayResults( result, nameToSearchFor ); + Console.WriteLine("No API Key found. Please edit Program.cs to add it in."); + return; } - private static void DisplayResults( CompaniesHouseClientResponse result, string nameSearchedFor ) + const string companyNumber = "10440441"; + const string nameToSearchFor = "Bigman"; + + await RunWithDirectClientAsync(nameToSearchFor, companyNumber); + + await RunWithDependencyInjectionAsync(companyNumber); + } + + /// + /// The simplest way to use the client: construct and + /// directly. Prefer the DI path below in ASP.NET Core apps. + /// + private static async Task RunWithDirectClientAsync(string nameToSearchFor, string companyNumber) + { + var settings = new CompaniesHouseSettings(ApiKey); + using var client = new CompaniesHouseClient(settings); + + var request = new SearchAllRequest { + Query = nameToSearchFor, + StartIndex = 0, + ItemsPerPage = 10 + }; + + var searchResult = await client.SearchAllAsync(request); + DisplaySearchResults(searchResult, nameToSearchFor); + + var officers = await client.GetOfficersAsync(companyNumber); + DisplayOfficers(officers); + } + + /// + /// The recommended way to use the client from an app with an + /// (ASP.NET Core, worker services, etc.) - see the "CompaniesHouse.Extensions.Microsoft.DependencyInjection" package. + /// + private static async Task RunWithDependencyInjectionAsync(string companyNumber) + { + var services = new ServiceCollection(); + services.AddCompaniesHouseClient(ApiKey); + await using var provider = services.BuildServiceProvider(); + + var client = provider.GetRequiredService(); - //Show all companies found - Console.WriteLine( $"{Environment.NewLine}----------------------------------------------" ); - Console.WriteLine( $"Companies found when searching for '{nameSearchedFor}' :" ); - foreach (Company item in result.Data.Items.Where( t => t as Company != null )) - { - Console.WriteLine( $"* {item.Title} - {item.Description} - {item.CompanyStatus}" ); - } - - //Show all Officers found - Console.WriteLine( $"{Environment.NewLine}----------------------------------------------" ); - Console.WriteLine( $"Officers found when searching for '{nameSearchedFor}' :" ); - foreach (Officer item in result.Data.Items.Where( t => t as Officer != null )) - { - Console.WriteLine( $"* {item.Title} - {item.Description}" ); - } - - //Show all Disqualified Officers found - Console.WriteLine( $"{Environment.NewLine}----------------------------------------------" ); - Console.WriteLine( $"Disqualified Officers found when searching for '{nameSearchedFor}' :" ); - foreach (DisqualifiedOfficer item in result.Data.Items.Where( t => t as DisqualifiedOfficer != null )) - { - Console.WriteLine( $"* {item.Title}" ); - } + var profileResult = await client.GetCompanyProfileAsync(companyNumber); + DisplayCompanyProfile(profileResult); + } + + private static void DisplaySearchResults(CompaniesHouseClientResponse result, string nameSearchedFor) + { + Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); + Console.WriteLine($"Companies found when searching for '{nameSearchedFor}' :"); + foreach (var item in result.Data!.Items.OfType()) + { + // CompanyStatus is a string-backed value type - it never throws on an unrecognised + // wire value, so we can always describe it, even for values added after this release. + Console.WriteLine($"* {item.Title} - {item.Description} - {DescribeCompanyStatus(item.CompanyStatus)}"); + } + + Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); + Console.WriteLine($"Officers found when searching for '{nameSearchedFor}' :"); + foreach (var item in result.Data.Items.OfType()) + { + Console.WriteLine($"* {item.Title} - {item.Description}"); + } + + Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); + Console.WriteLine($"Disqualified Officers found when searching for '{nameSearchedFor}' :"); + foreach (var item in result.Data.Items.OfType()) + { + Console.WriteLine($"* {item.Title}"); + } + } + + private static void DisplayOfficers(CompaniesHouseClientResponse result) + { + Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); + Console.WriteLine("Officers:"); + foreach (var officer in result.Data?.Items ?? []) + { + Console.WriteLine($"* {officer.Name}"); + } + } + + private static void DisplayCompanyProfile(CompaniesHouseClientResponse result) + { + Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); + if (result.Data is null) + { + Console.WriteLine($"No company profile found (HTTP {result.StatusCode})."); + return; } + + Console.WriteLine($"Company profile: {result.Data.CompanyName} - {DescribeCompanyStatus(result.Data.CompanyStatus)}"); } + + /// + /// Demonstrates handling an unknown enum value gracefully: string-backed value types never + /// throw for a value Companies House hasn't announced yet, so unrecognised values fall back + /// to instead of crashing. + /// + private static string DescribeCompanyStatus(CompanyStatus status) => status switch + { + _ when status == CompanyStatus.Active => "active", + _ when status == CompanyStatus.Dissolved => "dissolved", + _ when status.IsKnown => status.Description ?? status.Value, + _ => $"unknown status ({status.Value})", + }; } diff --git a/samples/SampleProject/SampleProject.csproj b/samples/SampleProject/SampleProject.csproj index 1016f92..bc3e02d 100644 --- a/samples/SampleProject/SampleProject.csproj +++ b/samples/SampleProject/SampleProject.csproj @@ -5,8 +5,13 @@ false + + + + + From db0e0201c68d3389b0475faea21e2e9f960cf80f Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 14:57:12 +0100 Subject: [PATCH 19/38] Fix Docker restore project copies --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 65f0f0f..eab1115 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,9 +15,11 @@ COPY ./LICENSE . COPY ./src/CompaniesHouse/*.csproj ./src/CompaniesHouse/ COPY ./src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/*.csproj ./src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/ +COPY ./src/CompaniesHouse.SourceGenerator/*.csproj ./src/CompaniesHouse.SourceGenerator/ COPY ./tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/*.csproj ./tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ COPY ./tests/CompaniesHouse.IntegrationTests/*.csproj ./tests/CompaniesHouse.IntegrationTests/ COPY ./tests/CompaniesHouse.ScenarioTests/*.csproj ./tests/CompaniesHouse.ScenarioTests/ +COPY ./tests/CompaniesHouse.SourceGenerator.Tests/*.csproj ./tests/CompaniesHouse.SourceGenerator.Tests/ COPY ./tests/CompaniesHouse.Tests/*.csproj ./tests/CompaniesHouse.Tests/ COPY ./samples/SampleProject/*.csproj ./samples/SampleProject/ RUN dotnet restore From ffa5883a470c941d75ce72fe927a2163ecb45a5c Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 15:00:46 +0100 Subject: [PATCH 20/38] Fix Dockerfile lint warnings --- .../workflows/continuous-integration-workflow.yml | 4 +++- Dockerfile | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 3f90b54..a7c815b 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -19,8 +19,10 @@ jobs: with: global-json-file: global.json - name: Docker Build NuGet packages + env: + COMPANIES_HOUSE_API_KEY: ${{ secrets.COMPANIES_HOUSE_API_KEY }} run: | - docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.VERSION }} --build-arg COMPANIES_HOUSE_API_KEY=${{ secrets.COMPANIES_HOUSE_API_KEY }} -f ./Dockerfile --output ./ . + docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.VERSION }} --secret id=companies_house_api_key,env=COMPANIES_HOUSE_API_KEY -f ./Dockerfile --output ./ . - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: always() diff --git a/Dockerfile b/Dockerfile index eab1115..0318532 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ +# syntax=docker/dockerfile:1 ARG CONFIGURATION="Release" ARG NUGET_PACKAGE_VERSION="1.0.0" -ARG COMPANIES_HOUSE_API_KEY FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore ARG CONFIGURATION @@ -24,7 +24,7 @@ COPY ./tests/CompaniesHouse.Tests/*.csproj ./tests/CompaniesHouse.Tests/ COPY ./samples/SampleProject/*.csproj ./samples/SampleProject/ RUN dotnet restore -FROM restore as build +FROM restore AS build ARG CONFIGURATION ARG NUGET_PACKAGE_VERSION @@ -35,11 +35,12 @@ COPY ./external/ ./external/ COPY ./enumerations/ ./enumerations/ RUN dotnet build --configuration $CONFIGURATION --no-restore -FROM build as test -ARG COMPANIES_HOUSE_API_KEY -RUN dotnet test --logger trx --configuration $CONFIGURATION --no-build +FROM build AS test +RUN --mount=type=secret,id=companies_house_api_key,required=false \ + export COMPANIES_HOUSE_API_KEY="$(cat /run/secrets/companies_house_api_key 2>/dev/null || true)" && \ + dotnet test --logger trx --configuration $CONFIGURATION --no-build -FROM build as pack +FROM build AS pack RUN mkdir -p artifacts RUN dotnet pack --configuration Release -p:Version=${NUGET_PACKAGE_VERSION} --no-build --output ./artifacts From 9e3844b52a7ab519cc870cbee6d41e975270f30a Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 15:59:27 +0100 Subject: [PATCH 21/38] Add NuGet package README metadata and CI validation --- .../continuous-integration-workflow.yml | 25 +++++++++++++++++++ README.md | 7 ++++++ ...sions.Microsoft.DependencyInjection.csproj | 2 ++ src/CompaniesHouse/CompaniesHouse.csproj | 2 ++ 4 files changed, 36 insertions(+) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index a7c815b..8fee69b 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -23,6 +23,31 @@ jobs: COMPANIES_HOUSE_API_KEY: ${{ secrets.COMPANIES_HOUSE_API_KEY }} run: | docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.VERSION }} --secret id=companies_house_api_key,env=COMPANIES_HOUSE_API_KEY -f ./Dockerfile --output ./ . + - name: Validate NuGet package metadata + run: | + mapfile -t packages < <(find ./artifacts -maxdepth 1 -type f -name '*.nupkg' ! -name '*.snupkg' | sort) + if [ "${#packages[@]}" -eq 0 ]; then + echo "No .nupkg files were produced." + exit 1 + fi + + for package in "${packages[@]}"; do + if ! unzip -Z1 "$package" | grep -qx "README.md"; then + echo "README.md missing from package: $package" + exit 1 + fi + + nuspec_path="$(unzip -Z1 "$package" | grep -E '\.nuspec$' | head -n 1)" + if [ -z "$nuspec_path" ]; then + echo "No .nuspec file found in package: $package" + exit 1 + fi + + if ! unzip -p "$package" "$nuspec_path" | grep -q 'README.md'; then + echo "NuSpec readme metadata missing from package: $package" + exit 1 + fi + done - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: always() diff --git a/README.md b/README.md index 2658ac6..71be0f8 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,13 @@ enum value, lives in [`samples/SampleProject`](samples/SampleProject). See [AGENTS.md](AGENTS.md) for repository conventions, build/test commands and the design decisions behind the v-next rewrite. +## Maintainer release notes + +NuGet publishing is driven by the CI Docker build, which produces the final +`.nupkg` artifacts in `./artifacts`. Before push-to-NuGet, CI validates that +each package contains `README.md` and has nuspec `README.md` +metadata so NuGet.org renders the project README correctly. + ## Running tests ```powershell diff --git a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj index 61929a7..b60ae5b 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj @@ -8,6 +8,7 @@ true snupkg true + README.md @@ -19,6 +20,7 @@ + diff --git a/src/CompaniesHouse/CompaniesHouse.csproj b/src/CompaniesHouse/CompaniesHouse.csproj index e187d60..4a179d4 100644 --- a/src/CompaniesHouse/CompaniesHouse.csproj +++ b/src/CompaniesHouse/CompaniesHouse.csproj @@ -8,6 +8,7 @@ true snupkg true + README.md @@ -21,6 +22,7 @@ + From 0c2ef6c009c06406d6b3c2fbf67170437fb84094 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 16:42:35 +0100 Subject: [PATCH 22/38] Add NuGet package links and assets to GitHub releases --- .../continuous-integration-workflow.yml | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 8fee69b..c4c0300 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -57,6 +57,27 @@ jobs: if: ${{ env.PUBLISH_PACKAGE }} run: | dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }} + - name: Generate release notes with NuGet links + if: ${{ env.PUBLISH_PACKAGE }} + id: release_notes + run: | + cat > release_notes.md << 'EOF' + ## NuGet Packages + + This release includes the following NuGet packages: + + - [CompaniesHouse](https://www.nuget.org/packages/CompaniesHouse/${{ env.VERSION }}) - Core .NET client for Companies House API + - [CompaniesHouse.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/CompaniesHouse.Extensions.Microsoft.DependencyInjection/${{ env.VERSION }}) - DI helpers for ASP.NET Core / generic-host apps + + Install via: + ``` + dotnet add package CompaniesHouse + dotnet add package CompaniesHouse.Extensions.Microsoft.DependencyInjection + ``` + + See the [README](https://github.com/kevbite/CompaniesHouse.NET#readme) for usage instructions. + EOF + echo "release_body=$(cat release_notes.md)" >> $GITHUB_OUTPUT - name: Create Release id: create_release if: ${{ env.PUBLISH_PACKAGE }} @@ -66,7 +87,9 @@ jobs: with: tag_name: ${{ env.VERSION }} name: Release ${{ env.VERSION }} - body: | - Release ${{ env.VERSION }} + body_path: release_notes.md + files: | + ./artifacts/*.nupkg + ./artifacts/*.snupkg draft: false prerelease: false From d9f63f5ea8479fd876a8babbda5fe30e067d191f Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 16:42:55 +0100 Subject: [PATCH 23/38] Document automated GitHub release with NuGet links --- AGENTS.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 8ff83c2..8604806 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -137,5 +137,18 @@ regression when working offline. - Enum design rationale: - Issue tracker: +## Release process + +On push to `master` or `prerelease`, the CI workflow automatically: +1. Builds and packs both NuGet packages. +2. Validates package metadata (README presence, nuspec readme tag). +3. Pushes packages to NuGet.org. +4. **Creates a GitHub release** with: + - Direct links to each package on NuGet.org (`nuget.org/packages/{PackageId}/{Version}`) + - Copy-paste `dotnet add package` commands for both packages + - Downloadable `.nupkg` and `.snupkg` files as release assets + +**Maintainers:** you do not need to manually write release notes or link to NuGet. The workflow handles it automatically. + ## Commits Commit in small amounts with a summary of what work we're building and not include the co-authorized by, however, do not push! Don't commit the .plans folder or the AGENTS.md \ No newline at end of file From fc4c4f63e0f24da1ace141f628f3000786753228 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 17:04:06 +0100 Subject: [PATCH 24/38] Fix release notes heredoc indentation and remove invalid GITHUB_OUTPUT line --- .../continuous-integration-workflow.yml | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index c4c0300..2a3e4cf 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -59,25 +59,23 @@ jobs: dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }} - name: Generate release notes with NuGet links if: ${{ env.PUBLISH_PACKAGE }} - id: release_notes run: | - cat > release_notes.md << 'EOF' - ## NuGet Packages - - This release includes the following NuGet packages: - - - [CompaniesHouse](https://www.nuget.org/packages/CompaniesHouse/${{ env.VERSION }}) - Core .NET client for Companies House API - - [CompaniesHouse.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/CompaniesHouse.Extensions.Microsoft.DependencyInjection/${{ env.VERSION }}) - DI helpers for ASP.NET Core / generic-host apps - - Install via: - ``` - dotnet add package CompaniesHouse - dotnet add package CompaniesHouse.Extensions.Microsoft.DependencyInjection - ``` - - See the [README](https://github.com/kevbite/CompaniesHouse.NET#readme) for usage instructions. - EOF - echo "release_body=$(cat release_notes.md)" >> $GITHUB_OUTPUT + cat > release_notes.md < Date: Fri, 3 Jul 2026 18:12:44 +0100 Subject: [PATCH 25/38] Replace CompaniesHouseClientResponse with discriminated union CompaniesHouseResponse - Add CompaniesHouseResponse abstract class with Success, NotFound, RateLimited, Unauthorized, ClientError, and ServerError sealed subtypes - Delete CompaniesHouseClientResponse 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 --- samples/SampleProject/Program.cs | 19 +- .../CompaniesHouseApiException.cs | 29 --- .../CompaniesHouseAppointmentsClient.cs | 6 +- .../CompaniesHouseChargesClient.cs | 8 +- src/CompaniesHouse/CompaniesHouseClient.cs | 38 ++-- .../CompaniesHouseClientResponse.cs | 38 ---- ...ompaniesHouseCompanyFilingHistoryClient.cs | 8 +- ...HouseCompanyInsolvencyInformationClient.cs | 4 +- .../CompaniesHouseCompanyProfileClient.cs | 4 +- .../CompaniesHouseDocumentClient.cs | 4 +- .../CompaniesHouseDocumentDownloadClient.cs | 57 +++--- .../CompaniesHouseDocumentMetadataClient.cs | 6 +- ...paniesHouseOfficerByByAppointmentClient.cs | 4 +- .../CompaniesHouseOfficersClient.cs | 4 +- ...ousePersonsWithSignificantControlClient.cs | 6 +- ...aniesHouseRegisteredOfficeAddressClient.cs | 4 +- src/CompaniesHouse/CompaniesHouseResponse.cs | 107 +++++++++++ .../CompaniesHouseSearchClient.cs | 4 +- .../HttpResponseMessageExtensions.cs | 64 +++---- ...mpaniesHouseAdvancedCompanySearchClient.cs | 2 +- .../ICompaniesHouseAppointmentsClient.cs | 4 +- .../ICompaniesHouseChargesClient.cs | 4 +- ...ompaniesHouseCompanyFilingHistoryClient.cs | 4 +- ...HouseCompanyInsolvencyInformationClient.cs | 2 +- .../ICompaniesHouseCompanyProfileClient.cs | 2 +- .../ICompaniesHouseDocumentDownloadClient.cs | 4 +- .../ICompaniesHouseDocumentMetadataClient.cs | 4 +- ...ompaniesHouseOfficerByAppointmentClient.cs | 2 +- .../ICompaniesHouseOfficersClient.cs | 2 +- ...ousePersonsWithSignificantControlClient.cs | 4 +- ...aniesHouseRegisteredOfficeAddressClient.cs | 2 +- .../ICompaniesHouseSearchAllClient.cs | 2 +- .../ICompaniesHouseSearchClient.cs | 2 +- ...ouseSearchCompaniesAlphabeticallyClient.cs | 2 +- .../ICompaniesHouseSearchCompanyClient.cs | 2 +- ...iesHouseSearchDisqualifiedOfficerClient.cs | 2 +- ...niesHouseSearchDissolvedCompaniesClient.cs | 2 +- .../ICompaniesHouseSearchOfficerClient.cs | 2 +- .../AppointmentsTests/AppointmentsTestBase.cs | 2 +- .../Tests/ChargesTests/ChargesTestBase.cs | 2 +- .../CompanyFilingHistoryTestsInvalid.cs | 2 +- .../CompanyFilingHistoryTestsValid.cs | 2 +- ...ilingHistoryByTransactionIdTestsInvalid.cs | 2 +- .../FilingHistoryByTransactionIdTestsValid.cs | 2 +- .../CompanyInsolvencyInformationTestBase.cs | 2 +- .../CompanyProfileTestsBase.cs | 2 +- .../DocumentTests/DocumentDownloadTests.cs | 4 +- .../Tests/DocumentTests/DocumentTestBase.cs | 2 +- .../Tests/OfficerTests/OfficersTestBase.cs | 2 +- .../PersonsWithSignificantControlTestBase.cs | 2 +- .../RegisteredOfficeAddressTestBase.cs | 2 +- .../DissolvedCompaniesSearchTests.cs | 11 +- ...CompaniesHouseCompanyProfileClientTests.cs | 7 +- .../CompaniesHouseDocumentClientTests.cs | 4 +- ...mpaniesHouseDocumentMetadataClientTests.cs | 4 +- ...ompaniesHouseCompanyOfficersClientTests.cs | 4 +- ...HousePersonsWithSignificantControlTests.cs | 4 +- ...sHouseSearchClientTestsForCompanySearch.cs | 4 +- ...estsForCompanySearchWithTooManyRequests.cs | 7 +- ...sHouseSearchClientTestsForOfficerSearch.cs | 2 +- .../HttpResponseMessageExtensionsTests.cs | 180 +++++++++++------- 61 files changed, 399 insertions(+), 320 deletions(-) delete mode 100644 src/CompaniesHouse/CompaniesHouseApiException.cs delete mode 100644 src/CompaniesHouse/CompaniesHouseClientResponse.cs create mode 100644 src/CompaniesHouse/CompaniesHouseResponse.cs diff --git a/samples/SampleProject/Program.cs b/samples/SampleProject/Program.cs index 0c77cf4..f275a30 100644 --- a/samples/SampleProject/Program.cs +++ b/samples/SampleProject/Program.cs @@ -72,11 +72,11 @@ private static async Task RunWithDependencyInjectionAsync(string companyNumber) DisplayCompanyProfile(profileResult); } - private static void DisplaySearchResults(CompaniesHouseClientResponse result, string nameSearchedFor) + private static void DisplaySearchResults(CompaniesHouseResponse result, string nameSearchedFor) { Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); Console.WriteLine($"Companies found when searching for '{nameSearchedFor}' :"); - foreach (var item in result.Data!.Items.OfType()) + foreach (var item in result.Data.Items.OfType()) { // CompanyStatus is a string-backed value type - it never throws on an unrecognised // wire value, so we can always describe it, even for values added after this release. @@ -98,26 +98,27 @@ private static void DisplaySearchResults(CompaniesHouseClientResponse } } - private static void DisplayOfficers(CompaniesHouseClientResponse result) + private static void DisplayOfficers(CompaniesHouseResponse result) { Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); Console.WriteLine("Officers:"); - foreach (var officer in result.Data?.Items ?? []) + foreach (var officer in result.Data.Items ?? []) { Console.WriteLine($"* {officer.Name}"); } } - private static void DisplayCompanyProfile(CompaniesHouseClientResponse result) + private static void DisplayCompanyProfile(CompaniesHouseResponse result) { Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); - if (result.Data is null) + if (result is CompaniesHouseResponse.Success success) + { + Console.WriteLine($"Company profile: {success.Data.CompanyName} - {DescribeCompanyStatus(success.Data.CompanyStatus)}"); + } + else { Console.WriteLine($"No company profile found (HTTP {result.StatusCode})."); - return; } - - Console.WriteLine($"Company profile: {result.Data.CompanyName} - {DescribeCompanyStatus(result.Data.CompanyStatus)}"); } /// diff --git a/src/CompaniesHouse/CompaniesHouseApiException.cs b/src/CompaniesHouse/CompaniesHouseApiException.cs deleted file mode 100644 index c31148b..0000000 --- a/src/CompaniesHouse/CompaniesHouseApiException.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; - -namespace CompaniesHouse -{ - /// - /// Thrown for genuine transport/server failures (5xx) so callers can rely on a - /// being returned for every other outcome - /// (including expected 4xx responses such as 404). - /// - public sealed class CompaniesHouseApiException : Exception - { - public CompaniesHouseApiException(int statusCode, string? reasonPhrase, TimeSpan? retryAfter) - : base($"Companies House API request failed with status code {statusCode} ({reasonPhrase}).") - { - StatusCode = statusCode; - ReasonPhrase = reasonPhrase; - RetryAfter = retryAfter; - } - - /// The HTTP status code returned by the API. - public int StatusCode { get; } - - /// The HTTP reason phrase returned by the API, if any. - public string? ReasonPhrase { get; } - - /// The value of the Retry-After header, if present. - public TimeSpan? RetryAfter { get; } - } -} diff --git a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs index b2c41e2..fe64f29 100644 --- a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs +++ b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs @@ -1,4 +1,4 @@ -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Appointments; @@ -19,13 +19,13 @@ public CompaniesHouseAppointmentsClient(HttpClient httpClient, IAppointmentsUriB _appointmentsUriBuilder = appointmentsUriBuilder; } - public async Task> GetAppointmentsAsync(string officerId, int startIndex, int pageSize, CancellationToken cancellationToken) + public async Task> GetAppointmentsAsync(string officerId, int startIndex, int pageSize, CancellationToken cancellationToken) { var requestUri = _appointmentsUriBuilder.Build(officerId, startIndex, pageSize); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseChargesClient.cs b/src/CompaniesHouse/CompaniesHouseChargesClient.cs index a384328..af25537 100644 --- a/src/CompaniesHouse/CompaniesHouseChargesClient.cs +++ b/src/CompaniesHouse/CompaniesHouseChargesClient.cs @@ -19,20 +19,20 @@ public CompaniesHouseChargesClient(HttpClient httpClient, IChargesUriBuilder cha _chargesUriBuilder = chargesUriBuilder; } - public async Task> GetChargesListAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default) + public async Task> GetChargesListAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default) { var requestUri = _chargesUriBuilder.Build(companyNumber, startIndex, pageSize); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } - public async Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken = default) + public async Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken = default) { var requestUri = _chargesUriBuilder.Build(companyNumber, chargeId); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index 9270ace..2fa102e 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -57,58 +57,58 @@ public CompaniesHouseClient(ICompaniesHouseSettings settings) { } - public Task> SearchCompanyAsync(SearchCompanyRequest request, CancellationToken cancellationToken = default(CancellationToken)) + public Task> SearchCompanyAsync(SearchCompanyRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } - public Task> SearchOfficerAsync(SearchOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)) + public Task> SearchOfficerAsync(SearchOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } - public Task> SearchDisqualifiedOfficerAsync(SearchDisqualifiedOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)) + public Task> SearchDisqualifiedOfficerAsync(SearchDisqualifiedOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } - public Task> SearchAllAsync(SearchAllRequest request, CancellationToken cancellationToken = default(CancellationToken)) + public Task> SearchAllAsync(SearchAllRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } - public Task> SearchCompaniesAlphabeticallyAsync(SearchCompaniesAlphabeticallyRequest request, CancellationToken cancellationToken = default(CancellationToken)) + public Task> SearchCompaniesAlphabeticallyAsync(SearchCompaniesAlphabeticallyRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } - public Task> SearchDissolvedCompaniesAsync(SearchDissolvedCompaniesRequest request, CancellationToken cancellationToken = default(CancellationToken)) + public Task> SearchDissolvedCompaniesAsync(SearchDissolvedCompaniesRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } - public Task> AdvancedCompanySearchAsync(AdvancedCompanySearchRequest request, CancellationToken cancellationToken = default(CancellationToken)) + public Task> AdvancedCompanySearchAsync(AdvancedCompanySearchRequest request, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseSearchClient.SearchAsync(request, cancellationToken); } - public Task> GetCompanyProfileAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) + public Task> GetCompanyProfileAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseCompanyProfileClient.GetCompanyProfileAsync(companyNumber, cancellationToken); } - public Task> GetCompanyFilingHistoryAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)) + public Task> GetCompanyFilingHistoryAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseCompanyFilingHistoryClient.GetCompanyFilingHistoryAsync(companyNumber, startIndex, pageSize, cancellationToken); } - public Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default) + public Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default) { return _companiesHouseCompanyFilingHistoryClient.GetFilingHistoryByTransactionAsync(companyNumber, transactionId, cancellationToken); } // Companies House defaults officer lists to 35 items, unlike several other paged endpoints. - public Task> GetOfficersAsync( + public Task> GetOfficersAsync( string companyNumber, int startIndex = 0, int pageSize = 35, @@ -120,38 +120,38 @@ public Task> GetOfficersAsync( return _companiesHouseOfficersClient.GetOfficersAsync(companyNumber, startIndex, pageSize, registerType, registerView, orderBy, cancellationToken); } - public Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) + public Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseCompanyInsolvencyInformationClient.GetCompanyInsolvencyInformationAsync(companyNumber, cancellationToken); } - public Task> GetAppointmentsAsync(string officerId, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)) + public Task> GetAppointmentsAsync(string officerId, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHouseCompanyAppointmentsClient.GetAppointmentsAsync(officerId, startIndex, pageSize, cancellationToken); } - public Task> GetPersonsWithSignificantControlAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)) + public Task> GetPersonsWithSignificantControlAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)) { return _companiesHousePersonsWithSignificantControlClient.GetPersonsWithSignificantControlAsync(companyNumber, startIndex, pageSize, cancellationToken); } - public Task> GetChargesListAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default) + public Task> GetChargesListAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default) { return _companiesHouseChargesClient.GetChargesListAsync(companyNumber, startIndex, pageSize, cancellationToken); } - public Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken = default) + public Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken = default) { return _companiesHouseChargesClient.GetChargeByIdAsync(companyNumber, chargeId, cancellationToken); } - public Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken = default) + public Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken = default) { return _companiesHouseRegisteredOfficeAddressClient.GetRegisteredOfficeAddress(companyNumber, cancellationToken); } - public Task> GetOfficerByAppointmentIdAsync(string companyNumber, string appointmentId, CancellationToken cancellationToken = default) + public Task> GetOfficerByAppointmentIdAsync(string companyNumber, string appointmentId, CancellationToken cancellationToken = default) { return _companiesHouseOfficerByAppointmentClient.GetOfficerByAppointmentIdAsync(companyNumber, appointmentId, cancellationToken); } diff --git a/src/CompaniesHouse/CompaniesHouseClientResponse.cs b/src/CompaniesHouse/CompaniesHouseClientResponse.cs deleted file mode 100644 index af06913..0000000 --- a/src/CompaniesHouse/CompaniesHouseClientResponse.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Net.Http.Headers; - -namespace CompaniesHouse -{ - /// - /// Wraps the result of a Companies House API call, exposing transport metadata (status - /// code, reason phrase, retry-after, headers) alongside the deserialized . - /// - public class CompaniesHouseClientResponse - { - public CompaniesHouseClientResponse(T? data, int statusCode, string? reasonPhrase, System.TimeSpan? retryAfter, HttpResponseHeaders? headers) - { - Data = data; - StatusCode = statusCode; - ReasonPhrase = reasonPhrase; - RetryAfter = retryAfter; - Headers = headers; - } - - /// The deserialized response body, or default for non-success responses. - public T? Data { get; } - - /// The HTTP status code of the response. - public int StatusCode { get; } - - /// The HTTP reason phrase of the response, if any. - public string? ReasonPhrase { get; } - - /// The value of the Retry-After header, if present (see #181/#182). - public System.TimeSpan? RetryAfter { get; } - - /// Whether the response status code was in the 2xx range. - public bool IsSuccess => StatusCode is >= 200 and < 300; - - /// The response headers, exposed read-only. - public HttpResponseHeaders? Headers { get; } - } -} diff --git a/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs index 9ae94ac..18689a4 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs @@ -19,22 +19,22 @@ public CompaniesHouseCompanyFilingHistoryClient(HttpClient httpClient, ICompanyF _companyFilingHistoryUriBuilder = companyFilingHistoryUriBuilder; } - public async Task> GetCompanyFilingHistoryAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetCompanyFilingHistoryAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) { var requestUri = _companyFilingHistoryUriBuilder.Build(companyNumber, startIndex, pageSize); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } - public async Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default) + public async Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default) { var requestUri = _companyFilingHistoryUriBuilder.Build(companyNumber, transactionId); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs index 464e697..cec78b5 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs @@ -19,13 +19,13 @@ public CompaniesHouseCompanyInsolvencyInformationClient(HttpClient httpClient, I _uriBuilder = uriBuilder; } - public async Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) { var requestUri = _uriBuilder.Build(companyNumber); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs index 15c5464..3dc951a 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs @@ -19,13 +19,13 @@ public CompaniesHouseCompanyProfileClient(HttpClient httpClient, ICompanyProfile _companyProfileUriBuilder = companyProfileUriBuilder; } - public async Task> GetCompanyProfileAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetCompanyProfileAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)) { var requestUri = _companyProfileUriBuilder.Build(companyNumber); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseDocumentClient.cs b/src/CompaniesHouse/CompaniesHouseDocumentClient.cs index 1d5f255..fa5d413 100644 --- a/src/CompaniesHouse/CompaniesHouseDocumentClient.cs +++ b/src/CompaniesHouse/CompaniesHouseDocumentClient.cs @@ -26,12 +26,12 @@ public CompaniesHouseDocumentClient(ICompaniesHouseSettings settings) } - public Task> GetDocumentMetadataAsync(string documentId, CancellationToken caneCancellationToken = default) + public Task> GetDocumentMetadataAsync(string documentId, CancellationToken caneCancellationToken = default) { return _companiesHouseDocumentMetadataClient.GetDocumentMetadataAsync(documentId, caneCancellationToken); } - public Task> DownloadDocumentAsync(string documentId, CancellationToken cancellationToken = default) + public Task> DownloadDocumentAsync(string documentId, CancellationToken cancellationToken = default) { return _companiesHouseDocumentDownloadClient.DownloadDocumentAsync(documentId, cancellationToken); } diff --git a/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs b/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs index 4453e81..baae0b0 100644 --- a/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs +++ b/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs @@ -1,4 +1,4 @@ -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Document; @@ -6,8 +6,6 @@ namespace CompaniesHouse { - using CompaniesHouse.Extensions; - public class CompaniesHouseDocumentDownloadClient : ICompaniesHouseDocumentDownloadClient { private readonly HttpClient _httpClient; @@ -19,28 +17,43 @@ public CompaniesHouseDocumentDownloadClient(HttpClient httpClient, IDocumentUriB _documentUriBuilder = documentUriBuilder; } - public async Task> DownloadDocumentAsync(string documentId, CancellationToken cancellationToken = default) + public async Task> DownloadDocumentAsync(string documentId, CancellationToken cancellationToken = default) { var requestUri = _documentUriBuilder.Build(documentId); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - await response.EnsureNotServerErrorAsync().ConfigureAwait(false); - - var data = response.IsSuccessStatusCode - ? new DocumentDownload - { - Content = await response.Content.ReadAsStreamAsync(cancellationToken), - ContentLength = response.Content.Headers.ContentLength, - ContentType = response.Content.Headers.ContentType?.MediaType - } - : null; - - return new CompaniesHouseClientResponse( - data, - (int)response.StatusCode, - response.ReasonPhrase, - response.Headers.RetryAfter?.Delta, - response.Headers); + var statusCode = (int)response.StatusCode; + var reasonPhrase = response.ReasonPhrase; + + return statusCode switch + { + >= 200 and < 300 => new CompaniesHouseResponse.Success( + new DocumentDownload + { + Content = await response.Content.ReadAsStreamAsync(cancellationToken), + ContentLength = response.Content.Headers.ContentLength, + ContentType = response.Content.Headers.ContentType?.MediaType + }, + statusCode, + reasonPhrase, + response.Headers), + + 404 => new CompaniesHouseResponse.NotFound(statusCode, reasonPhrase), + + 429 => new CompaniesHouseResponse.RateLimited( + response.Headers.RetryAfter?.Delta, + statusCode, + reasonPhrase), + + 401 or 403 => new CompaniesHouseResponse.Unauthorized(statusCode, reasonPhrase), + + >= 500 => new CompaniesHouseResponse.ServerError( + response.Headers.RetryAfter?.Delta, + statusCode, + reasonPhrase), + + _ => new CompaniesHouseResponse.ClientError(statusCode, reasonPhrase), + }; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs b/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs index 2f1c0f3..a72474b 100644 --- a/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs +++ b/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs @@ -1,4 +1,4 @@ -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Document; @@ -19,12 +19,12 @@ public CompaniesHouseDocumentMetadataClient(HttpClient httpClient, IDocumentUriB _documentUriBuilder = documentUriBuilder; } - public async Task> GetDocumentMetadataAsync(string documentId, CancellationToken caneCancellationToken = default) + public async Task> GetDocumentMetadataAsync(string documentId, CancellationToken caneCancellationToken = default) { var requestUri = _documentUriBuilder.Build(documentId); var response = await _httpClient.GetAsync(requestUri, caneCancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(caneCancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(caneCancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs b/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs index 4dcba93..6a2254c 100644 --- a/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs +++ b/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs @@ -19,13 +19,13 @@ public CompaniesHouseOfficerByByAppointmentClient(HttpClient httpClient, IOffice _officersAppointmentUriBuilder = officersAppointmentUriBuilder; } - public async Task> GetOfficerByAppointmentIdAsync(string companyNumber, string appointmentId, CancellationToken cancellationToken = default) + public async Task> GetOfficerByAppointmentIdAsync(string companyNumber, string appointmentId, CancellationToken cancellationToken = default) { var requestUri = _officersAppointmentUriBuilder.Build(companyNumber, appointmentId); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs index b282081..2be1b91 100644 --- a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs +++ b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs @@ -19,7 +19,7 @@ public CompaniesHouseOfficersClient(HttpClient httpClient, IOfficersUriBuilder o _officersUriBuilder = officersUriBuilder; } - public async Task> GetOfficersAsync( + public async Task> GetOfficersAsync( string companyNumber, int startIndex, int pageSize, @@ -32,7 +32,7 @@ public async Task> GetOfficersAsync( var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs b/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs index 09443f4..0fb8f6f 100644 --- a/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs +++ b/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs @@ -1,4 +1,4 @@ -using CompaniesHouse.Response.PersonsWithSignificantControl; +using CompaniesHouse.Response.PersonsWithSignificantControl; using CompaniesHouse.UriBuilders; using System.Net.Http; using System.Threading; @@ -19,13 +19,13 @@ public CompaniesHousePersonsWithSignificantControlClient(HttpClient httpClient, _personsWithSignificantControlBuilder = personsWithSignificantControlBuilder; } - public async Task> GetPersonsWithSignificantControlAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) + public async Task> GetPersonsWithSignificantControlAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)) { var requestUri = _personsWithSignificantControlBuilder.Build(companyNumber, startIndex, pageSize); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } diff --git a/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs b/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs index 48576a1..4db9af5 100644 --- a/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs +++ b/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs @@ -19,12 +19,12 @@ public CompaniesHouseRegisteredOfficeAddressClient(HttpClient httpClient, IRegis _registeredOfficeAddressUriBuilder = registeredOfficeAddressUriBuilder; } - public async Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken = default) + public async Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken = default) { var requestUri = _registeredOfficeAddressUriBuilder.Build(companyNumber); var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - return await response.ToCompaniesHouseClientResponseAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseResponse.cs b/src/CompaniesHouse/CompaniesHouseResponse.cs new file mode 100644 index 0000000..0e303e3 --- /dev/null +++ b/src/CompaniesHouse/CompaniesHouseResponse.cs @@ -0,0 +1,107 @@ +using System; +using System.Net.Http.Headers; + +namespace CompaniesHouse +{ + /// + /// Discriminated union representing every HTTP outcome of a Companies House API + /// call. Transport failures (network errors, DNS, timeout) surface as + /// from the underlying HttpClient. + /// + public abstract class CompaniesHouseResponse + { + private CompaniesHouseResponse(int statusCode, string? reasonPhrase) + { + StatusCode = statusCode; + ReasonPhrase = reasonPhrase; + } + + /// The HTTP status code of the response. + public int StatusCode { get; } + + /// The HTTP reason phrase, if any. + public string? ReasonPhrase { get; } + + /// + /// Returns the deserialized response body when this is a + /// response. Throws for any other subtype, + /// making the error explicit rather than silently returning null. + /// Use pattern matching when you need to handle non-success outcomes. + /// + /// + /// Thrown when the response is not . + /// + public T Data => this is Success s + ? s.Data + : throw new InvalidOperationException( + $"Cannot access Data on a {GetType().Name} response (HTTP {StatusCode})."); + + /// A 2xx response whose body deserialized successfully. + public sealed class Success : CompaniesHouseResponse + { + public Success(T data, int statusCode, string? reasonPhrase, HttpResponseHeaders headers) + : base(statusCode, reasonPhrase) + { + Data = data; + Headers = headers; + } + + /// The deserialized response body. Never null on this subtype. + public new T Data { get; } + + /// The full set of response headers. + public HttpResponseHeaders Headers { get; } + } + + /// + /// A 404 response — the requested resource does not exist or is not + /// accessible with the provided credentials. + /// + public sealed class NotFound : CompaniesHouseResponse + { + public NotFound(int statusCode, string? reasonPhrase) : base(statusCode, reasonPhrase) { } + } + + /// + /// A 429 response — the client has been rate-limited. Check + /// before retrying. + /// + public sealed class RateLimited : CompaniesHouseResponse + { + public RateLimited(TimeSpan? retryAfter, int statusCode, string? reasonPhrase) + : base(statusCode, reasonPhrase) => RetryAfter = retryAfter; + + /// How long to wait before retrying, if the server supplied the header. + public TimeSpan? RetryAfter { get; } + } + + /// + /// A 401/403 response — the API key is missing, wrong, or lacks permission. + /// + public sealed class Unauthorized : CompaniesHouseResponse + { + public Unauthorized(int statusCode, string? reasonPhrase) : base(statusCode, reasonPhrase) { } + } + + /// + /// Any other 4xx response not covered by the more specific subtypes. + /// + public sealed class ClientError : CompaniesHouseResponse + { + public ClientError(int statusCode, string? reasonPhrase) : base(statusCode, reasonPhrase) { } + } + + /// + /// A 5xx response — the server encountered an error. May carry a + /// hint (e.g. 503 with Retry-After). + /// + public sealed class ServerError : CompaniesHouseResponse + { + public ServerError(TimeSpan? retryAfter, int statusCode, string? reasonPhrase) + : base(statusCode, reasonPhrase) => RetryAfter = retryAfter; + + /// How long to wait before retrying, if the server supplied the header. + public TimeSpan? RetryAfter { get; } + } + } +} diff --git a/src/CompaniesHouse/CompaniesHouseSearchClient.cs b/src/CompaniesHouse/CompaniesHouseSearchClient.cs index 241b591..4551426 100644 --- a/src/CompaniesHouse/CompaniesHouseSearchClient.cs +++ b/src/CompaniesHouse/CompaniesHouseSearchClient.cs @@ -18,7 +18,7 @@ public CompaniesHouseSearchClient(HttpClient httpClient, ISearchUriBuilderFactor _searchUriBuilderFactory = searchUriBuilderFactory; } - public async Task> SearchAsync(TSearchRequest request, + public async Task> SearchAsync(TSearchRequest request, CancellationToken cancellationToken = default(CancellationToken)) { var searchUriBuilder = _searchUriBuilderFactory.Create(); @@ -26,7 +26,7 @@ public async Task> SearchAsync(cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs index 7becd49..289862e 100644 --- a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs +++ b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs @@ -1,4 +1,4 @@ -namespace CompaniesHouse.Extensions; +namespace CompaniesHouse.Extensions; using System.Net.Http; using System.Net.Http.Json; @@ -6,48 +6,46 @@ using System.Threading.Tasks; /// -/// The shared send/deserialize pipeline used by every sub-client: non-5xx responses are always -/// returned as a (including 404s, with -/// Data == default); 5xx responses raise . +/// The shared send/deserialize pipeline used by every sub-client: all HTTP responses +/// are returned as a subtype. +/// Transport failures (network errors, DNS, timeout) propagate as . /// public static class HttpResponseMessageExtensions { /// - /// Deserializes the response body (for success status codes) and wraps it, along with - /// transport metadata, in a . + /// Classifies the and returns the appropriate + /// subtype. /// - public static async Task> ToCompaniesHouseClientResponseAsync( + public static async Task> ToCompaniesHouseResponseAsync( this HttpResponseMessage response, CancellationToken cancellationToken = default) { - await response.EnsureNotServerErrorAsync().ConfigureAwait(false); - - var data = response.IsSuccessStatusCode - ? await response.Content.ReadFromJsonAsync(CompaniesHouseJsonSerializerOptions.Default, cancellationToken).ConfigureAwait(false) - : default; - - return new CompaniesHouseClientResponse( - data, - (int)response.StatusCode, - response.ReasonPhrase, - response.Headers.RetryAfter?.Delta, - response.Headers); - } + var statusCode = (int)response.StatusCode; + var reasonPhrase = response.ReasonPhrase; - /// - /// Throws for genuine server errors (5xx); returns - /// normally for everything else, including expected 4xx responses. - /// - public static Task EnsureNotServerErrorAsync(this HttpResponseMessage response) - { - if ((int)response.StatusCode >= 500) + return statusCode switch { - throw new CompaniesHouseApiException( - (int)response.StatusCode, - response.ReasonPhrase, - response.Headers.RetryAfter?.Delta); - } + >= 200 and < 300 => new CompaniesHouseResponse.Success( + await response.Content.ReadFromJsonAsync(CompaniesHouseJsonSerializerOptions.Default, cancellationToken).ConfigureAwait(false), + statusCode, + reasonPhrase, + response.Headers), + + 404 => new CompaniesHouseResponse.NotFound(statusCode, reasonPhrase), + + 429 => new CompaniesHouseResponse.RateLimited( + response.Headers.RetryAfter?.Delta, + statusCode, + reasonPhrase), + + 401 or 403 => new CompaniesHouseResponse.Unauthorized(statusCode, reasonPhrase), + + >= 500 => new CompaniesHouseResponse.ServerError( + response.Headers.RetryAfter?.Delta, + statusCode, + reasonPhrase), - return Task.CompletedTask; + _ => new CompaniesHouseResponse.ClientError(statusCode, reasonPhrase), + }; } } diff --git a/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs b/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs index c2f8a9d..8f955a5 100644 --- a/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs @@ -7,6 +7,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseAdvancedCompanySearchClient { - Task> AdvancedCompanySearchAsync(AdvancedCompanySearchRequest request, CancellationToken cancellationToken = default(CancellationToken)); + Task> AdvancedCompanySearchAsync(AdvancedCompanySearchRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/CompaniesHouse/ICompaniesHouseAppointmentsClient.cs b/src/CompaniesHouse/ICompaniesHouseAppointmentsClient.cs index dcffe81..f83ca84 100644 --- a/src/CompaniesHouse/ICompaniesHouseAppointmentsClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseAppointmentsClient.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Appointments; @@ -6,6 +6,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseAppointmentsClient { - Task> GetAppointmentsAsync(string officerId, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetAppointmentsAsync(string officerId, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseChargesClient.cs b/src/CompaniesHouse/ICompaniesHouseChargesClient.cs index 1bfd94c..c510eb4 100644 --- a/src/CompaniesHouse/ICompaniesHouseChargesClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseChargesClient.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse { public interface ICompaniesHouseChargesClient { - Task> GetChargesListAsync(string companyNumber,int startIndex, int pageSize, CancellationToken cancellationToken); - Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken); + Task> GetChargesListAsync(string companyNumber,int startIndex, int pageSize, CancellationToken cancellationToken); + Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseCompanyFilingHistoryClient.cs b/src/CompaniesHouse/ICompaniesHouseCompanyFilingHistoryClient.cs index f647ca5..168927e 100644 --- a/src/CompaniesHouse/ICompaniesHouseCompanyFilingHistoryClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseCompanyFilingHistoryClient.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse { public interface ICompaniesHouseCompanyFilingHistoryClient { - Task> GetCompanyFilingHistoryAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)); - Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default); + Task> GetCompanyFilingHistoryAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetFilingHistoryByTransactionAsync(string companyNumber, string transactionId, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseCompanyInsolvencyInformationClient.cs b/src/CompaniesHouse/ICompaniesHouseCompanyInsolvencyInformationClient.cs index 22d8ee2..d5e885a 100644 --- a/src/CompaniesHouse/ICompaniesHouseCompanyInsolvencyInformationClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseCompanyInsolvencyInformationClient.cs @@ -6,6 +6,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseCompanyInsolvencyInformationClient { - Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetCompanyInsolvencyInformationAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseCompanyProfileClient.cs b/src/CompaniesHouse/ICompaniesHouseCompanyProfileClient.cs index aadbddf..dbf68e1 100644 --- a/src/CompaniesHouse/ICompaniesHouseCompanyProfileClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseCompanyProfileClient.cs @@ -6,6 +6,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseCompanyProfileClient { - Task> GetCompanyProfileAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)); + Task> GetCompanyProfileAsync(string companyNumber, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseDocumentDownloadClient.cs b/src/CompaniesHouse/ICompaniesHouseDocumentDownloadClient.cs index 0433dfe..11ac32a 100644 --- a/src/CompaniesHouse/ICompaniesHouseDocumentDownloadClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseDocumentDownloadClient.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Document; @@ -6,6 +6,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseDocumentDownloadClient { - Task> DownloadDocumentAsync(string documentId, CancellationToken cancellationToken); + Task> DownloadDocumentAsync(string documentId, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseDocumentMetadataClient.cs b/src/CompaniesHouse/ICompaniesHouseDocumentMetadataClient.cs index ea36831..a881d49 100644 --- a/src/CompaniesHouse/ICompaniesHouseDocumentMetadataClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseDocumentMetadataClient.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Document; @@ -6,6 +6,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseDocumentMetadataClient { - Task> GetDocumentMetadataAsync(string documentId, CancellationToken caneCancellationToken = default); + Task> GetDocumentMetadataAsync(string documentId, CancellationToken caneCancellationToken = default); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseOfficerByAppointmentClient.cs b/src/CompaniesHouse/ICompaniesHouseOfficerByAppointmentClient.cs index e2b99d6..927cdc6 100644 --- a/src/CompaniesHouse/ICompaniesHouseOfficerByAppointmentClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseOfficerByAppointmentClient.cs @@ -6,6 +6,6 @@ namespace CompaniesHouse { internal interface ICompaniesHouseOfficerByAppointmentClient { - Task> GetOfficerByAppointmentIdAsync(string companyNumber, string appointmentId, CancellationToken cancellationToken); + Task> GetOfficerByAppointmentIdAsync(string companyNumber, string appointmentId, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs b/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs index b501f3e..e058d28 100644 --- a/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseOfficersClient.cs @@ -7,7 +7,7 @@ namespace CompaniesHouse public interface ICompaniesHouseOfficersClient { // Companies House defaults officer lists to 35 items, unlike several other paged endpoints. - Task> GetOfficersAsync( + Task> GetOfficersAsync( string companyNumber, int startIndex = 0, int pageSize = 35, diff --git a/src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlClient.cs b/src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlClient.cs index c91dec0..1ace242 100644 --- a/src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlClient.cs +++ b/src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlClient.cs @@ -1,4 +1,4 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.PersonsWithSignificantControl; @@ -6,6 +6,6 @@ namespace CompaniesHouse { public interface ICompaniesHousePersonsWithSignificantControlClient { - Task> GetPersonsWithSignificantControlAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default); + Task> GetPersonsWithSignificantControlAsync(string companyNumber, int startIndex, int pageSize, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs b/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs index 068eba4..04b2cef 100644 --- a/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs @@ -6,6 +6,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseRegisteredOfficeAddressClient { - Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken = default); + Task> GetRegisteredOfficeAddress(string companyNumber, CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseSearchAllClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchAllClient.cs index d4a6157..e259b34 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchAllClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchAllClient.cs @@ -7,6 +7,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchAllClient { - Task> SearchAllAsync(SearchAllRequest request, CancellationToken cancellationToken = default(CancellationToken)); + Task> SearchAllAsync(SearchAllRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseSearchClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchClient.cs index 4f508a2..e9a8e9a 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchClient.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchClient { - Task> SearchAsync(TSearchRequest request, + Task> SearchAsync(TSearchRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs index 11f38cd..383f893 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchCompaniesAlphabeticallyClient.cs @@ -7,6 +7,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchCompaniesAlphabeticallyClient { - Task> SearchCompaniesAlphabeticallyAsync(SearchCompaniesAlphabeticallyRequest request, CancellationToken cancellationToken = default(CancellationToken)); + Task> SearchCompaniesAlphabeticallyAsync(SearchCompaniesAlphabeticallyRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/CompaniesHouse/ICompaniesHouseSearchCompanyClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchCompanyClient.cs index 824dcad..c3e7a42 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchCompanyClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchCompanyClient.cs @@ -7,6 +7,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchCompanyClient { - Task> SearchCompanyAsync(SearchCompanyRequest request, CancellationToken cancellationToken = default(CancellationToken)); + Task> SearchCompanyAsync(SearchCompanyRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseSearchDisqualifiedOfficerClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchDisqualifiedOfficerClient.cs index c658574..ed34363 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchDisqualifiedOfficerClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchDisqualifiedOfficerClient.cs @@ -7,6 +7,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchDisqualifiedOfficerClient { - Task> SearchDisqualifiedOfficerAsync(SearchDisqualifiedOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)); + Task> SearchDisqualifiedOfficerAsync(SearchDisqualifiedOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs index 15470e0..c9c4b00 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchDissolvedCompaniesClient.cs @@ -7,6 +7,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchDissolvedCompaniesClient { - Task> SearchDissolvedCompaniesAsync(SearchDissolvedCompaniesRequest request, CancellationToken cancellationToken = default(CancellationToken)); + Task> SearchDissolvedCompaniesAsync(SearchDissolvedCompaniesRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/CompaniesHouse/ICompaniesHouseSearchOfficerClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchOfficerClient.cs index 5eef211..8aaf534 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchOfficerClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchOfficerClient.cs @@ -7,6 +7,6 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchOfficerClient { - Task> SearchOfficerAsync(SearchOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)); + Task> SearchOfficerAsync(SearchOfficerRequest request, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs index 83cae95..fdb9154 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs @@ -7,7 +7,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.AppointmentsTests public abstract class AppointmentsTestBase : IAsyncLifetime { protected CompaniesHouseClient Client = null!; - protected CompaniesHouseClientResponse Result = null!; + protected CompaniesHouseResponse Result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs index 33c4d44..081d2e2 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.ChargesTests public abstract class ChargesTestBase : IAsyncLifetime { protected CompaniesHouseClient Client { get; set; } = null!; - protected CompaniesHouseClientResponse Result = null!; + protected CompaniesHouseResponse Result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs index e2f0be7..e7e40eb 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs @@ -10,7 +10,7 @@ public class CompanyFilingHistoryTestsInvalid : CompanyFilingHistoryTestBase { private const string InvalidCompanyNumber = "ABC00000"; - private CompaniesHouseClientResponse _result = null!; + private CompaniesHouseResponse _result = null!; protected override async Task When() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs index 5eff803..51becf6 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs @@ -31,7 +31,7 @@ public async Task ThenTheDataItemsAreNotEmpty(string companyNumber) var size = 100; var results = new List(); - CompaniesHouseClientResponse result; + CompaniesHouseResponse result; do { result = await _client.GetCompanyFilingHistoryAsync(companyNumber, page++ * size, size); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs index 468a804..aca78b9 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs @@ -11,7 +11,7 @@ public class FilingHistoryByTransactionIdTestsInvalid : CompanyFilingHistoryTest private const string InvalidCompanyNumber = "ABC00000"; private const string InvalidTransactionId = "00000000"; - private CompaniesHouseClientResponse _result = null!; + private CompaniesHouseResponse _result = null!; protected override async Task When() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs index 8ba7138..3b373cf 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs @@ -11,7 +11,7 @@ public class FilingHistoryByTransactionIdTestsValid : CompanyFilingHistoryTestBa private const string ValidCompanyNumber = "00445790"; private const string ValidTransactionId = "MzUyNDY1MTExNmFkaXF6a2N4"; - private CompaniesHouseClientResponse _result = null!; + private CompaniesHouseResponse _result = null!; protected override async Task When() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs index e5d86b3..889149a 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs @@ -7,7 +7,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.CompanyInsolvencyInformationTest public abstract class CompanyInsolvencyInformationTestBase : IAsyncLifetime { protected CompaniesHouseClient Client = null!; - protected CompaniesHouseClientResponse Result = null!; + protected CompaniesHouseResponse Result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs index 33a66a2..71f4dd3 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs @@ -7,7 +7,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.CompanyProfileTests public abstract class CompanyProfileTestsBase : IAsyncLifetime { protected CompaniesHouseClient _client = null!; - protected CompaniesHouseClientResponse _result = null!; + protected CompaniesHouseResponse _result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs index 058ef4b..ac5ae79 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs @@ -10,7 +10,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.DocumentTests public class DocumentDownloadTests : DocumentTestBase { private const string DocumentId = "Mw2JX3NUZqy8_TwPkbHJSsZH1Xz-MygUbnurqpZZwvU"; - private CompaniesHouseClientResponse _result = null!; + private CompaniesHouseResponse _result = null!; protected override async Task When() => await DownloadingDocument(); @@ -33,7 +33,7 @@ public async Task ThenDocumentContentIsNotEmpty() public class DocumentDownloadTestsInvalid : DocumentTestBase { private const string DocumentId = "000000000000000000000000000000"; - private CompaniesHouseClientResponse _result = null!; + private CompaniesHouseResponse _result = null!; protected override async Task When() => await DownloadingDocument(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs index f9b944d..f7bdf09 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.DocumentTests public abstract class DocumentTestBase : IAsyncLifetime { protected CompaniesHouseDocumentClient Client = null!; - protected CompaniesHouseClientResponse Result = null!; + protected CompaniesHouseResponse Result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs index a009e6b..5e88744 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs @@ -6,7 +6,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.OfficerTests public abstract class OfficersTestBase : IAsyncLifetime { protected CompaniesHouseClient Client = null!; - protected CompaniesHouseClientResponse Result = null!; + protected CompaniesHouseResponse Result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs index 4ddd89b..643c06b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs @@ -7,7 +7,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.PersonsWithSignificantControlTes public abstract class PersonsWithSignificantControlTestBase : IAsyncLifetime { protected CompaniesHouseClient _client = null!; - protected CompaniesHouseClientResponse _result = null!; + protected CompaniesHouseResponse _result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs index afc4536..4c9d176 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressTestBase.cs @@ -7,7 +7,7 @@ namespace CompaniesHouse.IntegrationTests.Tests.RegisteredOfficeAddress public abstract class RegisteredOfficeAddressTestBase : IAsyncLifetime { protected CompaniesHouseClient Client { get; set; } = null!; - protected CompaniesHouseClientResponse Result = null!; + protected CompaniesHouseResponse Result = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs index bfc0cb6..7ac94dd 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs @@ -33,13 +33,10 @@ public async Task ThenCompaniesAreReturned(string query) [IntegrationFact] public async Task ThenPreviousNameSearchReturnsMatchedPreviousCompanyName() { - CompaniesHouseClientResponse result; + var result = await SearchPreviousNamesAsync(); - try - { - result = await SearchPreviousNamesAsync(); - } - catch (CompaniesHouseApiException exception) when (exception.StatusCode == 500) + // Retry once on 5xx server error + if (result is CompaniesHouseResponse.ServerError) { result = await SearchPreviousNamesAsync(); } @@ -63,7 +60,7 @@ public async Task ThenAlphabeticalSearchReturnsOrderedAlphaKeys() result.Data.Items.ShouldContain(x => !string.IsNullOrWhiteSpace(x.OrderedAlphaKeyWithId)); } - private Task> SearchPreviousNamesAsync() => + private Task> SearchPreviousNamesAsync() => _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest { Query = "radio rentals", diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs index 3b24b28..c83e809 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -21,7 +21,7 @@ public class CompaniesHouseCompanyProfileClientTests { private CompaniesHouseCompanyProfileClient _client; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; private ResourceBuilders.CompanyProfile _companyProfile; [Theory] @@ -84,8 +84,7 @@ public async Task GivenA404Response_WhenGettingACompanyProfile_ThenNullDataAndSt _result = await _client.GetCompanyProfileAsync("missing"); _result.ShouldNotBeNull(); - _result.Data.ShouldBeNull(); - _result.IsSuccess.ShouldBeFalse(); + _result.ShouldBeOfType.NotFound>(); _result.StatusCode.ShouldBe(404); } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs index f54681e..08f1ddc 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net.Http; using System.Threading.Tasks; @@ -12,7 +12,7 @@ namespace CompaniesHouse.Tests.CompaniesHouseDocumentClientTests { public class CompaniesHouseDocumentClientTests : IAsyncLifetime { - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; private const string ExpectedMediaType = "application/pdf"; private const string ExpectedContent = "test pdf"; private const string DocumentId = "wibble"; diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs index 99f7cd2..1a1f765 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net.Http; using CompaniesHouse.Response.Document; @@ -13,7 +13,7 @@ public class CompaniesHouseDocumentMetadataClientTests { private const string DocumentId = "wibble"; private DocumentMetadataTestCase _expected; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; public CompaniesHouseDocumentMetadataClientTests() { diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs index fd64cd2..30d64ff 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading.Tasks; using CompaniesHouse.Response.Officers; @@ -15,7 +15,7 @@ public class CompaniesHouseCompanyOfficersClientTests { private CompaniesHouseOfficersClient _client; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; private ResourceBuilders.Officers _officers; [Fact] diff --git a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs index 349d982..e1af730 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading.Tasks; using CompaniesHouse.Response.PersonsWithSignificantControl; @@ -15,7 +15,7 @@ public class CompaniesHousePersonsWithSignificantControlTests { private CompaniesHousePersonsWithSignificantControlClient _client; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; private ResourceBuilders.PersonsWithSignificantControl _personsWithSignificantControl; [Fact] diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs index be0051f..6a7d763 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs @@ -1,4 +1,4 @@ -using AutoFixture; +using AutoFixture; using CompaniesHouse.Request; using CompaniesHouse.Response; using CompaniesHouse.Response.Search.CompanySearch; @@ -12,7 +12,7 @@ public class CompaniesHouseSearchClientTestsForCompanySearch { private CompaniesHouseSearchClient _client; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; private ResourceDetails _resourceDetails; private List _expectedCompanies; diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs index 028426d..5e36ec5 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net.Http; using System.Threading.Tasks; using CompaniesHouse.Request; @@ -12,7 +12,7 @@ namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests { public class CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests : IAsyncLifetime { - private CompaniesHouseClientResponse? _response; + private CompaniesHouseResponse? _response; public async Task InitializeAsync() { @@ -34,9 +34,8 @@ public async Task InitializeAsync() public void ThenUnsuccessfulResponseIsReturned() { _response.ShouldNotBeNull(); - _response.IsSuccess.ShouldBeFalse(); + _response.ShouldBeOfType.RateLimited>(); _response.StatusCode.ShouldBe(429); - _response.Data.ShouldBeNull(); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs index 04cd8a2..339f970 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs @@ -16,7 +16,7 @@ namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests public class CompaniesHouseSearchClientTestsForOfficerSearch : IAsyncLifetime { private CompaniesHouseSearchClient _client; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; private ResourceDetails _resourceDetails; public async Task InitializeAsync() diff --git a/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs index 14bbd58..4b7bcc0 100644 --- a/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs +++ b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs @@ -12,92 +12,123 @@ public class HttpResponseMessageExtensionsTests { - [Fact] - public async Task GivenAnHttpResponse_WhenTheStatusCodeIsSuccess_ThenToCompaniesHouseClientResponseAsyncReturnsTheWrappedResponse() + [Theory] + [InlineData(200)] + [InlineData(201)] + [InlineData(204)] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs2xx_ThenReturnsSuccess(int statusCode) { - for (var statusCode = 200; statusCode < 299; statusCode++) + var sut = new HttpResponseMessage((HttpStatusCode)statusCode) { - var sut = new HttpResponseMessage((HttpStatusCode)statusCode) - { - Content = JsonContent.Create(new TestPayload { Value = "ok" }) - }; - - var response = await sut.ToCompaniesHouseClientResponseAsync(); - - response.Data.ShouldNotBeNull(); - response.Data.Value.ShouldBe("ok"); - response.StatusCode.ShouldBe(statusCode); - response.IsSuccess.ShouldBeTrue(); - response.Headers.ShouldBe(sut.Headers); - } + Content = JsonContent.Create(new TestPayload { Value = "ok" }) + }; + + var response = await sut.ToCompaniesHouseResponseAsync(); + + var success = response.ShouldBeOfType.Success>(); + success.Data.ShouldNotBeNull(); + success.Data.Value.ShouldBe("ok"); + success.StatusCode.ShouldBe(statusCode); + success.Headers.ShouldBe(sut.Headers); + } + + [Fact] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs404_ThenReturnsNotFound() + { + var sut = new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = "Not Found" }; + + var response = await sut.ToCompaniesHouseResponseAsync(); + + var notFound = response.ShouldBeOfType.NotFound>(); + notFound.StatusCode.ShouldBe(404); + notFound.ReasonPhrase.ShouldBe("Not Found"); + } + + [Fact] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs429_ThenReturnsRateLimited() + { + var sut = new HttpResponseMessage((HttpStatusCode)429) { ReasonPhrase = "Too Many Requests" }; + sut.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(300)); + + var response = await sut.ToCompaniesHouseResponseAsync(); + + var rateLimited = response.ShouldBeOfType.RateLimited>(); + rateLimited.StatusCode.ShouldBe(429); + rateLimited.RetryAfter.ShouldBe(TimeSpan.FromSeconds(300)); + } + + [Fact] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs429WithNoRetryAfter_ThenReturnsRateLimitedWithNullRetryAfter() + { + var sut = new HttpResponseMessage((HttpStatusCode)429) { ReasonPhrase = "Too Many Requests" }; + + var response = await sut.ToCompaniesHouseResponseAsync(); + + var rateLimited = response.ShouldBeOfType.RateLimited>(); + rateLimited.RetryAfter.ShouldBeNull(); } [Theory] - [InlineData(410, "Gone", 0, null)] - [InlineData(429, "Too Many Requests", 300, null)] - public async Task GivenAnHttpResponse_WhenTheStatusCodeIsNotServerError_ThenToCompaniesHouseClientResponseAsyncReturnsMetadataWithoutData( - int statusCode, - string reasonPhrase, - int retryAfterSeconds, - string? retryAfterDate) + [InlineData(401)] + [InlineData(403)] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs401Or403_ThenReturnsUnauthorized(int statusCode) { - var sut = new HttpResponseMessage((HttpStatusCode)statusCode) { ReasonPhrase = reasonPhrase }; - var retryAfterDateTimeOffset = DateTimeOffset.MinValue; - if (!string.IsNullOrWhiteSpace(retryAfterDate)) - { - retryAfterDateTimeOffset = DateTimeOffset.Parse(retryAfterDate); - } + var sut = new HttpResponseMessage((HttpStatusCode)statusCode); - if (retryAfterSeconds >= 0 || !string.IsNullOrWhiteSpace(retryAfterDate)) - { - sut.Headers.RetryAfter = string.IsNullOrWhiteSpace(retryAfterDate) - ? new RetryConditionHeaderValue(TimeSpan.FromSeconds(retryAfterSeconds)) - : new RetryConditionHeaderValue(retryAfterDateTimeOffset); - } - - var response = await sut.ToCompaniesHouseClientResponseAsync(); - - response.Data.ShouldBeNull(); - response.StatusCode.ShouldBe(statusCode); - response.ReasonPhrase.ShouldBe(reasonPhrase); - response.IsSuccess.ShouldBeFalse(); - response.RetryAfter.ShouldBe( - string.IsNullOrWhiteSpace(retryAfterDate) - ? TimeSpan.FromSeconds(retryAfterSeconds) - : null); + var response = await sut.ToCompaniesHouseResponseAsync(); + + var unauthorized = response.ShouldBeOfType.Unauthorized>(); + unauthorized.StatusCode.ShouldBe(statusCode); } [Theory] - [InlineData(503, "Service Unavailable", 0, null)] - [InlineData(503, "Service Unavailable", -1, "2015-10-08T12:34:56.000+1")] - [InlineData(503, "Service Unavailable", -1, null)] - public void GivenAnHttpResponse_WhenTheStatusCodeIsServerError_ThenEnsureNotServerErrorAsyncThrowsCompaniesHouseApiException( - int statusCode, - string reasonPhrase, - int retryAfterSeconds, - string? retryAfterDate) + [InlineData(500)] + [InlineData(503)] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs5xx_ThenReturnsServerError(int statusCode) { - var sut = new HttpResponseMessage((HttpStatusCode)statusCode) { ReasonPhrase = reasonPhrase }; - var retryAfterDateTimeOffset = DateTimeOffset.MinValue; - if (!string.IsNullOrWhiteSpace(retryAfterDate)) - { - retryAfterDateTimeOffset = DateTimeOffset.Parse(retryAfterDate); - } + var sut = new HttpResponseMessage((HttpStatusCode)statusCode) { ReasonPhrase = "Server Error" }; - if (retryAfterSeconds >= 0 || !string.IsNullOrWhiteSpace(retryAfterDate)) - { - sut.Headers.RetryAfter = string.IsNullOrWhiteSpace(retryAfterDate) - ? new RetryConditionHeaderValue(TimeSpan.FromSeconds(retryAfterSeconds)) - : new RetryConditionHeaderValue(retryAfterDateTimeOffset); - } - - var exception = Should.Throw(() => sut.EnsureNotServerErrorAsync().GetAwaiter().GetResult()); - exception.StatusCode.ShouldBe(statusCode); - exception.ReasonPhrase.ShouldBe(reasonPhrase); - exception.RetryAfter.ShouldBe( - string.IsNullOrWhiteSpace(retryAfterDate) - ? retryAfterSeconds >= 0 ? TimeSpan.FromSeconds(retryAfterSeconds) : null - : null); + var response = await sut.ToCompaniesHouseResponseAsync(); + + var serverError = response.ShouldBeOfType.ServerError>(); + serverError.StatusCode.ShouldBe(statusCode); + serverError.RetryAfter.ShouldBeNull(); + } + + [Fact] + public async Task GivenAnHttpResponse_WhenThe5xxResponseHasRetryAfter_ThenServerErrorIncludesRetryAfter() + { + var sut = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable); + sut.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(60)); + + var response = await sut.ToCompaniesHouseResponseAsync(); + + var serverError = response.ShouldBeOfType.ServerError>(); + serverError.RetryAfter.ShouldBe(TimeSpan.FromSeconds(60)); + } + + [Theory] + [InlineData(400)] + [InlineData(410)] + [InlineData(422)] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIsOther4xx_ThenReturnsClientError(int statusCode) + { + var sut = new HttpResponseMessage((HttpStatusCode)statusCode); + + var response = await sut.ToCompaniesHouseResponseAsync(); + + var clientError = response.ShouldBeOfType.ClientError>(); + clientError.StatusCode.ShouldBe(statusCode); + } + + [Fact] + public async Task GivenANonSuccessResponse_WhenAccessingData_ThenThrowsInvalidOperationException() + { + var sut = new HttpResponseMessage(HttpStatusCode.NotFound) { ReasonPhrase = "Not Found" }; + + var response = await sut.ToCompaniesHouseResponseAsync(); + + Should.Throw(() => _ = response.Data); } private sealed class TestPayload @@ -106,3 +137,4 @@ private sealed class TestPayload } } } + From 88ca64857b79ce16be479d701848b00f46f1eb6d Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 18:23:06 +0100 Subject: [PATCH 26/38] Update README for discriminated union response type --- README.md | 68 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 71be0f8..00165b3 100644 --- a/README.md +++ b/README.md @@ -157,29 +157,61 @@ value types are generated from the official ## Reading responses -Every client method returns a `CompaniesHouseClientResponse`, which carries -transport metadata alongside the deserialized body: +Every client method returns a `CompaniesHouseResponse` — a discriminated +union whose concrete subtype tells you exactly what happened: -```csharp -var result = await client.GetCompanyProfileAsync(companyNumber); +| Subtype | When | Extra property | +|---|---|---| +| `Success` | 2xx | `Data` (non-null), `Headers` | +| `NotFound` | 404 | — | +| `RateLimited` | 429 | `RetryAfter` | +| `Unauthorized` | 401/403 | — | +| `ClientError` | other 4xx | — | +| `ServerError` | 5xx | `RetryAfter` | + +All subtypes expose `StatusCode` and `ReasonPhrase`. Transport failures (network +errors, DNS, timeout) propagate as `HttpRequestException` from the underlying +`HttpClient`. -result.StatusCode; // the HTTP status code, e.g. 200 or 404 -result.IsSuccess; // true for 2xx responses -result.ReasonPhrase; // the HTTP reason phrase, if any -result.RetryAfter; // the Retry-After header value, if present (e.g. on 429s) -result.Headers; // the raw response headers -result.Data; // the deserialized body, or default for non-success responses +### Simple happy path + +Call `.Data` directly — it returns the deserialized body on `Success` and throws +`InvalidOperationException` for every other subtype, so you never silently get +`null`: + +```csharp +var company = (await client.GetCompanyProfileAsync(companyNumber)).Data; +Console.WriteLine(company.CompanyName); ``` -`Data` is `null`/`default` (rather than throwing) when the API returns a -non-success response, so check it (or `IsSuccess`) before using it: +### Full branching + +Pattern-match when you need to handle specific outcomes: ```csharp var result = await client.GetCompanyProfileAsync(companyNumber); -if (result.Data is null) + +switch (result) { - // no match for that company number (404), or another non-success response - return; + case CompaniesHouseResponse.Success { Data: var company }: + Console.WriteLine(company.CompanyName); + break; + + case CompaniesHouseResponse.NotFound: + Console.WriteLine("Company not found."); + break; + + case CompaniesHouseResponse.RateLimited { RetryAfter: var delay }: + Console.WriteLine($"Rate limited. Retry after {delay}."); + break; + + case CompaniesHouseResponse.Unauthorized: + Console.WriteLine("Check your API key."); + break; + + case CompaniesHouseResponse.ServerError { RetryAfter: var delay, StatusCode: var code }: + Console.WriteLine($"Server error {code}. Retry after {delay}."); + break; } ``` @@ -220,7 +252,7 @@ var disqualified = await client.SearchDisqualifiedOfficerAsync(new SearchDisqual var result = await client.GetCompanyProfileAsync("10440441"); ``` -`result.Data` is `null` if there was no match for that company number. +`result` is a `NotFound` subtype if there was no match for that company number. ### Getting the company officer list @@ -257,7 +289,7 @@ var item = await client.GetFilingHistoryByTransactionAsync("10440441", transacti var result = await client.GetCompanyInsolvencyInformationAsync("10440441"); ``` -`result.Data` is `null` if there is no insolvency information for the company. +`result` is a `NotFound` subtype if there is no insolvency information for the company. ### Getting persons with significant control @@ -287,7 +319,7 @@ var metadata = await client.GetDocumentMetadataAsync("FIxRR8teCKodjkBLRDHv2Cb8y0 var document = await client.DownloadDocumentAsync("FIxRR8teCKodjkBLRDHv2Cb8y0-nQ7T5G3BEXfWtOu4"); ``` -`result.Data` is `null` if there was no metadata/document for the given id. +`metadata`/`document` is a `NotFound` subtype if there was no metadata/document for the given id. More endpoints land progressively - see `.plans/` for what's in flight. From c262da3017635af61b39d6db2d1476ff88a959c0 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 18:29:34 +0100 Subject: [PATCH 27/38] Polish docs and sample for discriminated union response type - README: switch the full-branching example to a switch expression - MIGRATION.md: replace stale CompaniesHouseClientResponse 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 --- MIGRATION.md | 57 +++++++++++---- README.md | 33 ++++----- samples/SampleProject/Program.cs | 118 ++++++++++++++++++------------- 3 files changed, 128 insertions(+), 80 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 5a3bdcd..5d68415 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -97,27 +97,58 @@ See the [README's enum section](README.md#enumvalue-type-handling) and the [design rationale](https://kevsoft.net/2026/06/28/enums-in-api-contracts.html) for more detail. -## `CompaniesHouseClientResponse` shape change +## Response type: discriminated union -**Before:** the response wrapper exposed only the deserialized body (shape -varied by version; some versions returned `T` directly). +**Before:** the response wrapper exposed only the deserialized body: -**After:** every client method returns `CompaniesHouseClientResponse`, -which always carries transport metadata: +```csharp +var profile = await client.GetCompanyProfileAsync(companyNumber); +// profile was the data itself, or null +``` + +**After:** every client method returns `CompaniesHouseResponse` — a sealed +type hierarchy. The concrete subtype tells you exactly what happened: ```diff - var profile = await client.GetCompanyProfileAsync(companyNumber); -- // profile was the data itself, or null +- if (profile == null) +- return; // 404 or some other error +- Console.WriteLine(profile.CompanyName); + ++ // Happy path: .Data throws InvalidOperationException on non-success ++ var company = (await client.GetCompanyProfileAsync(companyNumber)).Data; ++ Console.WriteLine(company.CompanyName); + ++ // Or pattern-match for fine-grained handling + var result = await client.GetCompanyProfileAsync(companyNumber); -+ var profile = result.Data; // null for non-success responses -+ result.StatusCode; // now available -+ result.RetryAfter; // now available - see #181/#182 -+ result.IsSuccess; // convenience check for 2xx ++ var message = result switch ++ { ++ CompaniesHouseResponse.Success { Data: var c } => c.CompanyName, ++ CompaniesHouseResponse.NotFound => "not found", ++ CompaniesHouseResponse.RateLimited { RetryAfter: var d } => $"retry after {d}", ++ CompaniesHouseResponse.Unauthorized => "check API key", ++ CompaniesHouseResponse.ServerError { StatusCode: var s } => $"server error {s}", ++ _ => $"HTTP {result.StatusCode}", ++ }; ``` -Update any code that used the return value directly as the model to go -through `.Data` instead, and consider using `.StatusCode`/`.IsSuccess` for -error handling instead of catching exceptions or checking for `null` alone. +All subtypes expose `StatusCode` and `ReasonPhrase`. `Success` additionally +exposes the full `HttpResponseHeaders`. `RateLimited` and `ServerError` expose +`RetryAfter` (resolves issues #181/#182). Transport-level failures +(`HttpRequestException`) are not caught — they propagate as normal exceptions. + +`CompaniesHouseApiException` has been removed. If you were catching it for 5xx +handling, switch to matching on `ServerError` instead: + +```diff +- catch (CompaniesHouseApiException ex) when (ex.StatusCode == 503) +- { +- await Task.Delay(ex.RetryAfter ?? TimeSpan.FromSeconds(30)); +- } + ++ if (result is CompaniesHouseResponse.ServerError { RetryAfter: var delay }) ++ await Task.Delay(delay ?? TimeSpan.FromSeconds(30)); +``` ## Default base URI change diff --git a/README.md b/README.md index 00165b3..58aaf69 100644 --- a/README.md +++ b/README.md @@ -186,33 +186,30 @@ Console.WriteLine(company.CompanyName); ### Full branching -Pattern-match when you need to handle specific outcomes: +Use a switch expression when you need to handle specific outcomes: ```csharp var result = await client.GetCompanyProfileAsync(companyNumber); -switch (result) +var message = result switch { - case CompaniesHouseResponse.Success { Data: var company }: - Console.WriteLine(company.CompanyName); - break; + CompaniesHouseResponse.Success { Data: var company } => + $"Found: {company.CompanyName}", - case CompaniesHouseResponse.NotFound: - Console.WriteLine("Company not found."); - break; + CompaniesHouseResponse.NotFound => + "Company not found.", - case CompaniesHouseResponse.RateLimited { RetryAfter: var delay }: - Console.WriteLine($"Rate limited. Retry after {delay}."); - break; + CompaniesHouseResponse.RateLimited { RetryAfter: var delay } => + $"Rate limited — retry after {delay}.", - case CompaniesHouseResponse.Unauthorized: - Console.WriteLine("Check your API key."); - break; + CompaniesHouseResponse.Unauthorized => + "Check your API key.", - case CompaniesHouseResponse.ServerError { RetryAfter: var delay, StatusCode: var code }: - Console.WriteLine($"Server error {code}. Retry after {delay}."); - break; -} + CompaniesHouseResponse.ServerError { StatusCode: var code, RetryAfter: var delay } => + $"Server error {code} — retry after {delay}.", + + _ => $"Unexpected response: {result.StatusCode}", +}; ``` ## Usage diff --git a/samples/SampleProject/Program.cs b/samples/SampleProject/Program.cs index f275a30..5ead716 100644 --- a/samples/SampleProject/Program.cs +++ b/samples/SampleProject/Program.cs @@ -1,14 +1,13 @@ using CompaniesHouse; using CompaniesHouse.Request; using CompaniesHouse.Response; +using CompaniesHouse.Response.CompanyProfile; using CompaniesHouse.Response.Search.AllSearch; using CompaniesHouse.Response.Search.CompanySearch; using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch; using CompaniesHouse.Response.Search.OfficerSearch; using Microsoft.Extensions.DependencyInjection; -using System; -using System.Linq; -using System.Threading.Tasks; +using Officers = CompaniesHouse.Response.Officers.Officers; namespace SampleProject; @@ -29,7 +28,6 @@ static async Task Main() const string nameToSearchFor = "Bigman"; await RunWithDirectClientAsync(nameToSearchFor, companyNumber); - await RunWithDependencyInjectionAsync(companyNumber); } @@ -42,23 +40,22 @@ private static async Task RunWithDirectClientAsync(string nameToSearchFor, strin var settings = new CompaniesHouseSettings(ApiKey); using var client = new CompaniesHouseClient(settings); - var request = new SearchAllRequest + var searchResult = await client.SearchAllAsync(new SearchAllRequest { Query = nameToSearchFor, StartIndex = 0, ItemsPerPage = 10 - }; + }); - var searchResult = await client.SearchAllAsync(request); DisplaySearchResults(searchResult, nameToSearchFor); - var officers = await client.GetOfficersAsync(companyNumber); - DisplayOfficers(officers); + var officersResult = await client.GetOfficersAsync(companyNumber); + DisplayOfficers(officersResult, companyNumber); } /// /// The recommended way to use the client from an app with an - /// (ASP.NET Core, worker services, etc.) - see the "CompaniesHouse.Extensions.Microsoft.DependencyInjection" package. + /// (ASP.NET Core, worker services, etc.). /// private static async Task RunWithDependencyInjectionAsync(string companyNumber) { @@ -68,69 +65,92 @@ private static async Task RunWithDependencyInjectionAsync(string companyNumber) var client = provider.GetRequiredService(); - var profileResult = await client.GetCompanyProfileAsync(companyNumber); - DisplayCompanyProfile(profileResult); + var result = await client.GetCompanyProfileAsync(companyNumber); + DisplayCompanyProfile(result, companyNumber); } - private static void DisplaySearchResults(CompaniesHouseResponse result, string nameSearchedFor) + private static void DisplaySearchResults(CompaniesHouseResponse result, string query) { - Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); - Console.WriteLine($"Companies found when searching for '{nameSearchedFor}' :"); - foreach (var item in result.Data.Items.OfType()) - { - // CompanyStatus is a string-backed value type - it never throws on an unrecognised - // wire value, so we can always describe it, even for values added after this release. - Console.WriteLine($"* {item.Title} - {item.Description} - {DescribeCompanyStatus(item.CompanyStatus)}"); - } + Console.WriteLine($"\n----------------------------------------------"); - Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); - Console.WriteLine($"Officers found when searching for '{nameSearchedFor}' :"); - foreach (var item in result.Data.Items.OfType()) + // .Data throws InvalidOperationException on non-success — pattern-match + // when you need to handle error outcomes explicitly. + if (result is not CompaniesHouseResponse.Success { Data: var data }) { - Console.WriteLine($"* {item.Title} - {item.Description}"); + Console.WriteLine($"Search failed (HTTP {result.StatusCode})."); + return; } - Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); - Console.WriteLine($"Disqualified Officers found when searching for '{nameSearchedFor}' :"); - foreach (var item in result.Data.Items.OfType()) + Console.WriteLine($"Companies matching '{query}':"); + foreach (var item in data.Items.OfType()) { - Console.WriteLine($"* {item.Title}"); + // CompanyStatus is a string-backed value type — it never throws on an + // unrecognised wire value, so we can always describe it safely. + Console.WriteLine($" * {item.Title} — {DescribeCompanyStatus(item.CompanyStatus)}"); } + + Console.WriteLine($"\nOfficers matching '{query}':"); + foreach (var item in data.Items.OfType()) + Console.WriteLine($" * {item.Title} — {item.Description}"); + + Console.WriteLine($"\nDisqualified officers matching '{query}':"); + foreach (var item in data.Items.OfType()) + Console.WriteLine($" * {item.Title}"); } - private static void DisplayOfficers(CompaniesHouseResponse result) + private static void DisplayOfficers(CompaniesHouseResponse result, string companyNumber) { - Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); - Console.WriteLine("Officers:"); - foreach (var officer in result.Data.Items ?? []) + Console.WriteLine($"\n----------------------------------------------"); + Console.WriteLine($"Officers for {companyNumber}:"); + + if (result is not CompaniesHouseResponse.Success { Data: var data }) { - Console.WriteLine($"* {officer.Name}"); + Console.WriteLine($" Could not retrieve officers (HTTP {result.StatusCode})."); + return; } + + foreach (var officer in data.Items ?? []) + Console.WriteLine($" * {officer.Name}"); } - private static void DisplayCompanyProfile(CompaniesHouseResponse result) + private static void DisplayCompanyProfile(CompaniesHouseResponse result, string companyNumber) { - Console.WriteLine($"{Environment.NewLine}----------------------------------------------"); - if (result is CompaniesHouseResponse.Success success) - { - Console.WriteLine($"Company profile: {success.Data.CompanyName} - {DescribeCompanyStatus(success.Data.CompanyStatus)}"); - } - else + Console.WriteLine($"\n----------------------------------------------"); + + // Switch expression — the compiler guides you through every outcome. + var summary = result switch { - Console.WriteLine($"No company profile found (HTTP {result.StatusCode})."); - } + CompaniesHouseResponse.Success { Data: var company } => + $"{company.CompanyName} — {DescribeCompanyStatus(company.CompanyStatus)}", + + CompaniesHouseResponse.NotFound => + $"Company {companyNumber} not found.", + + CompaniesHouseResponse.RateLimited { RetryAfter: var delay } => + $"Rate limited — retry after {delay}.", + + CompaniesHouseResponse.Unauthorized => + "Unauthorized — check your API key.", + + CompaniesHouseResponse.ServerError { StatusCode: var code } => + $"Server error {code} — try again later.", + + _ => $"Unexpected response: {result.StatusCode}", + }; + + Console.WriteLine(summary); } /// - /// Demonstrates handling an unknown enum value gracefully: string-backed value types never - /// throw for a value Companies House hasn't announced yet, so unrecognised values fall back - /// to instead of crashing. + /// String-backed value types never throw for an unrecognised value, so this + /// switch can handle future Companies House statuses gracefully. /// private static string DescribeCompanyStatus(CompanyStatus status) => status switch { - _ when status == CompanyStatus.Active => "active", + _ when status == CompanyStatus.Active => "active", _ when status == CompanyStatus.Dissolved => "dissolved", - _ when status.IsKnown => status.Description ?? status.Value, - _ => $"unknown status ({status.Value})", + _ when status.IsKnown => status.Description ?? status.Value, + _ => $"unknown status ({status.Value})", }; } + From 4ce4a083cf832edaf1b1b636861648880411091d Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 20:03:33 +0100 Subject: [PATCH 28/38] Fix workflow release notes generation --- .../continuous-integration-workflow.yml | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 2a3e4cf..4f957bd 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -60,22 +60,22 @@ jobs: - name: Generate release notes with NuGet links if: ${{ env.PUBLISH_PACKAGE }} run: | - cat > release_notes.md < release_notes.md - name: Create Release id: create_release if: ${{ env.PUBLISH_PACKAGE }} From d3b0cc892ad5342871d71f59a02175ade02fda68 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 22:08:57 +0100 Subject: [PATCH 29/38] Fix integration tests for discriminated union response type All 'Invalid' tests were asserting .Data.ShouldBeNull() on 404 responses, which now throws InvalidOperationException. Updated to assert NotFound subtype using ShouldBeOfType.NotFound>() instead. --- .../Tests/ChargesTests/ChargeByIdTestsInValid.cs | 2 +- .../FilingHistoryByTransactionIdTestsInvalid.cs | 2 +- .../CompanyInsolvencyInformationTestsInvalid.cs | 3 ++- .../Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs | 3 ++- .../Tests/DocumentTests/DocumentDownloadTests.cs | 2 +- .../Tests/DocumentTests/DocumentMetadataTestsInvalid.cs | 2 +- .../Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs | 2 +- .../RegisteredOfficeAddressesTestsInValid.cs | 3 ++- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs index 29af9cb..16d7df8 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsInValid.cs @@ -14,6 +14,6 @@ public class ChargeByIdTestsInValid : ChargesTestBase protected override async Task When() => Result = await Client.GetChargeByIdAsync(CompanyNumber, ChargeId); [IntegrationFact] - public void ThenChargesListIsNull() => Result.Data.ShouldBeNull(); + public void ThenChargesListIsNull() => Result.ShouldBeOfType.NotFound>(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs index aca78b9..3781b20 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs @@ -22,7 +22,7 @@ await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() [IntegrationFact] public void ThenTheDataItemsAreNull() { - _result.Data.ShouldBeNull(); + _result.ShouldBeOfType.NotFound>(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs index 15ecd40..a59652e 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using CompaniesHouse.Response.Insolvency; using Shouldly; using Xunit; @@ -13,6 +14,6 @@ protected override async Task When() => Result = await Client.GetCompanyInsolvencyInformationAsync(InvalidCompanyNumber); [IntegrationFact] - public void ThenTheItemsAreNull() => Result.Data.ShouldBeNull(); + public void ThenTheItemsAreNull() => Result.ShouldBeOfType.NotFound>(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs index 6cb7b19..205dabf 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using CompaniesHouse.Response.CompanyProfile; using Shouldly; using Xunit; @@ -19,7 +20,7 @@ await WhenRetrievingAnInvalidCompanyProfile() [IntegrationFact] public void ThenTheProfileIsNotReturned() { - _result.Data.ShouldBeNull(); + _result.ShouldBeOfType.NotFound>(); _result.StatusCode.ShouldBe(404); } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs index ac5ae79..ac4d976 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs @@ -41,6 +41,6 @@ public class DocumentDownloadTestsInvalid : DocumentTestBase private async Task DownloadingDocument() => _result = await Client.DownloadDocumentAsync(DocumentId); [IntegrationFact] - public void ThenDocumentDataIsNull() => _result.Data.ShouldBeNull(); + public void ThenDocumentDataIsNull() => _result.ShouldBeOfType.NotFound>(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs index 5324d4c..dc3a8c9 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsInvalid.cs @@ -17,6 +17,6 @@ private async Task RetrievingDocumentMetadata() => Result = await Client.GetDocumentMetadataAsync(DocumentId); [IntegrationFact] - public void ThenDocumentMetadataIsNull() => Result.Data.ShouldBeNull(); + public void ThenDocumentMetadataIsNull() => Result.ShouldBeOfType.NotFound>(); } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs index bdec3e5..b73b826 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs @@ -15,6 +15,6 @@ protected override async Task When() => Result = await Client.GetOfficerByAppointmentIdAsync(InvalidCompanyNumber, InvalidAppointmentId); [IntegrationFact] - public void ThenTheDataIsNull() => Result.Data.ShouldBeNull(); + public void ThenTheDataIsNull() => Result.ShouldBeOfType.NotFound>(); } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs index 25b9955..04dbaca 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using CompaniesHouse.Response.RegisteredOfficeAddress; using Shouldly; using Xunit; @@ -12,6 +13,6 @@ public class RegisteredOfficeAddressesTestsInValid : RegisteredOfficeAddressTest protected override async Task When() => Result = await Client.GetRegisteredOfficeAddress(InvalidCompanyNumber); [IntegrationFact] - public void ThenRegisteredOfficeAddressIsNull() => Result.Data.ShouldBeNull(); + public void ThenRegisteredOfficeAddressIsNull() => Result.ShouldBeOfType.NotFound>(); } } From db40724f3fffed02a18de89bfb484ad724c36ce0 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 22:16:19 +0100 Subject: [PATCH 30/38] Fix nullable warnings and enforce warnings-as-errors --- Directory.Build.props | 5 --- .../CompaniesHouseDocumentDownloadClient.cs | 2 +- .../HttpResponseMessageExtensions.cs | 4 +- src/CompaniesHouse/Response/Address.cs | 18 ++++----- .../Response/Appointments/NameElements.cs | 8 ++-- src/CompaniesHouse/Response/Charges/Charge.cs | 2 +- .../CompanyFiling/FilingHistoryItem.cs | 2 +- .../FilingHistoryItemAnnotation.cs | 2 +- .../FilingHistoryItemResolution.cs | 2 +- .../Response/CompanyProfile/Accounts.cs | 6 +-- .../CompanyProfile/BranchCompanyDetails.cs | 6 +-- .../Response/CompanyProfile/CompanyProfile.cs | 22 +++++----- .../CompanyProfile/PreviousCompanyName.cs | 2 +- .../Response/Document/DocumentDownload.cs | 4 +- .../Response/Officers/OfficerFormerName.cs | 4 +- .../Response/Officers/Officers.cs | 2 +- .../AdvancedCompanySearch.cs | 8 ++-- .../Search/AdvancedCompanySearch/Company.cs | 8 ++-- .../Response/Search/AllSearch/Address.cs | 16 ++++---- .../Response/Search/AllSearch/AllSearch.cs | 6 +-- .../Response/Search/AllSearch/Item.cs | 18 ++++----- .../Response/Search/AllSearch/Links.cs | 2 +- .../Response/Search/AllSearch/Matches.cs | 6 +-- .../CompaniesAlphabeticallySearch.cs | 6 +-- .../CompaniesAlphabeticallySearch/Company.cs | 10 ++--- .../Response/Search/CompanyProfileLinks.cs | 2 +- .../Response/Search/CompanySearch/Company.cs | 14 +++---- .../Search/CompanySearch/CompanySearch.cs | 6 +-- .../DisqualifiedOfficersSearch/Address.cs | 14 +++---- .../DisqualifiedOfficer.cs | 14 +++---- .../DisqualifiedOfficerSearch.cs | 4 +- .../DisqualifiedOfficersSearch/Match.cs | 6 +-- .../DissolvedCompaniesSearch/Company.cs | 6 +-- .../DissolvedCompaniesSearch.cs | 8 ++-- .../PreviousCompanyName.cs | 4 +- src/CompaniesHouse/Response/Search/Links.cs | 2 +- .../Response/Search/OfficerSearch/Address.cs | 18 ++++----- .../Response/Search/OfficerSearch/Match.cs | 6 +-- .../Response/Search/OfficerSearch/Officer.cs | 16 ++++---- .../Search/OfficerSearch/OfficerSearch.cs | 4 +- .../Response/Search/SearchItem.cs | 4 +- .../ChargesTests/ChargesListTestsValid.cs | 6 ++- .../CompanyFilingHistoryTestsValid.cs | 5 ++- ...PersonsWithSignificantControlTestsValid.cs | 5 ++- .../AppointmentsAndPscScenarios.cs | 10 +++-- ...panyProfileDeserializationScenarioTests.cs | 10 ++--- .../FilingAndChargesScenarios.cs | 6 ++- .../InsolvencyScenarios.cs | 6 ++- .../OfficersDeserializationScenarioTests.cs | 4 +- .../RegisteredOfficeAndDocumentsScenarios.cs | 1 + ...dFetchCorrespondingCompanyScenarioTests.cs | 13 +++--- ...rchResponseDeserializationScenarioTests.cs | 6 +-- .../CompaniesHouseChargesClientTestCase.cs | 10 ++--- .../CompaniesHouseChargesClientTests.cs | 8 ++-- ...HouseCompanyFilingHistoryClientTestCase.cs | 8 ++-- ...CompanyInsolvencyInformationClientTests.cs | 4 +- ...paniesHouseCompanyProfileClientTestCase.cs | 10 ++--- ...CompaniesHouseCompanyProfileClientTests.cs | 6 +-- .../CompaniesHouseDocumentClientTests.cs | 2 +- ...mpaniesHouseDocumentMetadataClientTests.cs | 1 + .../DocumentMetadataTestCase.cs | 20 +++++----- ...paniesHouseOfficerByAppointmentTestCase.cs | 2 +- ...ompaniesHouseCompanyOfficersClientTests.cs | 10 ++--- ...HousePersonsWithSignificantControlTests.cs | 8 ++-- ...iesHouseRegisteredOfficeAddressTestCase.cs | 2 +- ...archClientTestsForAdvancedCompanySearch.cs | 2 +- ...sHouseSearchClientTestsForCompanySearch.cs | 2 +- ...hClientTestsForDissolvedCompaniesSearch.cs | 6 +-- ...sHouseSearchClientTestsForOfficerSearch.cs | 6 +-- ...CompaniesHouseAuthorizationHandlerTests.cs | 7 ++-- .../EquivalencyAssertionExtensions.cs | 12 +++--- .../ResourceBuilders/Accounts.cs | 6 +-- .../ResourceBuilders/Charge.cs | 26 ++++++------ .../ResourceBuilders/Classification.cs | 4 +- .../CompanyChargesResourceBuilder.cs | 2 +- .../ResourceBuilders/CompanyFilingHistory.cs | 10 ++--- .../ResourceBuilders/CompanyFillingLinks.cs | 6 +-- .../ResourceBuilders/CompanyProfile.cs | 32 +++++++-------- .../CompanyProfileBranchCompanyDetails.cs | 6 +-- .../ResourceBuilders/CompanyProfileLinks.cs | 16 ++++---- .../CompanyProfileResourceBuilder.cs | 4 +- .../CompanySearchResource/CompanyDetails.cs | 40 +++++++++---------- .../CompanySearchResource/ResourceDetails.cs | 4 +- .../ResourceBuilders/FilingHistoryItem.cs | 24 +++++------ .../FilingHistoryItemAnnotation.cs | 8 ++-- .../FilingHistoryItemAssociatedFiling.cs | 6 +-- .../FilingHistoryItemResolution.cs | 14 +++---- .../ResourceBuilders/InsolvencyCase.cs | 4 +- .../ResourceBuilders/LastAccounts.cs | 4 +- .../ResourceBuilders/Links.cs | 2 +- .../ResourceBuilders/OfficeAddress.cs | 24 +++++------ .../ResourceBuilders/Officer.cs | 20 +++++----- .../OfficerSearchResource/Address.cs | 20 +++++----- .../OfficerSearchResource/Item.cs | 22 +++++----- .../OfficerSearchResource/Links.cs | 4 +- .../OfficerSearchResource/Matches.cs | 8 ++-- .../OfficerSearchResource/ResourceDetails.cs | 6 +-- .../ResourceBuilders/OfficerSummary.cs | 2 +- .../ResourceBuilders/Officers.cs | 2 +- .../OfficersResourceBuilder.cs | 2 +- .../ResourceBuilders/Particular.cs | 4 +- .../ResourceBuilders/PersonEntitled.cs | 2 +- .../PersonWithSignificantControl.cs | 22 +++++----- ...sonWithSignificantControlIdentification.cs | 10 ++--- .../PersonWithSignificantControlLinks.cs | 4 +- .../PersonsWithSignificantControl.cs | 4 +- .../ResourceBuilders/PreviousCompanyName.cs | 2 +- .../RegisteredOfficeAddress.cs | 18 ++++----- .../ResourceBuilders/SecuredDetail.cs | 4 +- .../ResourceBuilders/Transaction.cs | 4 +- .../ResourceBuilders/TransactionLinks.cs | 4 +- .../StubHttpMessageHandler.cs | 5 ++- 112 files changed, 464 insertions(+), 444 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 752047e..8f4969c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,11 +5,6 @@ enable enable true - - $(WarningsNotAsErrors);Nullable diff --git a/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs b/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs index baae0b0..ae1b8b4 100644 --- a/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs +++ b/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs @@ -32,7 +32,7 @@ public async Task> DownloadDocumentAsyn { Content = await response.Content.ReadAsStreamAsync(cancellationToken), ContentLength = response.Content.Headers.ContentLength, - ContentType = response.Content.Headers.ContentType?.MediaType + ContentType = response.Content.Headers.ContentType?.MediaType ?? string.Empty }, statusCode, reasonPhrase, diff --git a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs index 289862e..fbab8ff 100644 --- a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs +++ b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs @@ -25,7 +25,8 @@ public static async Task> ToCompaniesHouseResponseAsyn return statusCode switch { >= 200 and < 300 => new CompaniesHouseResponse.Success( - await response.Content.ReadFromJsonAsync(CompaniesHouseJsonSerializerOptions.Default, cancellationToken).ConfigureAwait(false), + await response.Content.ReadFromJsonAsync(CompaniesHouseJsonSerializerOptions.Default, cancellationToken).ConfigureAwait(false) + ?? throw new HttpRequestException("Response content was empty or could not be deserialized."), statusCode, reasonPhrase, response.Headers), @@ -48,4 +49,3 @@ await response.Content.ReadFromJsonAsync(CompaniesHouseJsonSerializerOptions. }; } } - diff --git a/src/CompaniesHouse/Response/Address.cs b/src/CompaniesHouse/Response/Address.cs index 391ec07..69c77a0 100644 --- a/src/CompaniesHouse/Response/Address.cs +++ b/src/CompaniesHouse/Response/Address.cs @@ -5,30 +5,30 @@ namespace CompaniesHouse.Response public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; [JsonPropertyName("care_of")] - public string CareOf { get; set; } + public string CareOf { get; set; } = null!; [JsonPropertyName("country")] - public string Country { get; set; } + public string Country { get; set; } = null!; [JsonPropertyName("locality")] - public string Locality { get; set; } + public string Locality { get; set; } = null!; [JsonPropertyName("po_box")] - public string PoBox { get; set; } + public string PoBox { get; set; } = null!; [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; [JsonPropertyName("premises")] - public string Premises { get; set; } + public string Premises { get; set; } = null!; [JsonPropertyName("region")] - public string Region { get; set; } + public string Region { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Appointments/NameElements.cs b/src/CompaniesHouse/Response/Appointments/NameElements.cs index 0eaafbb..2a66f87 100644 --- a/src/CompaniesHouse/Response/Appointments/NameElements.cs +++ b/src/CompaniesHouse/Response/Appointments/NameElements.cs @@ -5,16 +5,16 @@ namespace CompaniesHouse.Response.Appointments public class NameElements { [JsonPropertyName("title")] - public string Title { get; set; } + public string Title { get; set; } = null!; [JsonPropertyName("forename")] - public string Forename { get; set; } + public string Forename { get; set; } = null!; [JsonPropertyName("surname")] - public string Surname { get; set; } + public string Surname { get; set; } = null!; [JsonPropertyName("other_forenames")] - public string OtherForenames { get; set; } + public string OtherForenames { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Charges/Charge.cs b/src/CompaniesHouse/Response/Charges/Charge.cs index 1a0c9af..6d02f63 100644 --- a/src/CompaniesHouse/Response/Charges/Charge.cs +++ b/src/CompaniesHouse/Response/Charges/Charge.cs @@ -19,7 +19,7 @@ public class Charge public int? ChargeNumber { get; set; } [JsonPropertyName("classification")] - public Classification Classification { get; set; } + public Classification Classification { get; set; } = null!; [JsonPropertyName("covering_instrument_date")] public DateTime? CoveringInstrumentDate { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs index 0a1234a..90ad0ed 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs @@ -12,7 +12,7 @@ public class FilingHistoryItem : IDescriptable public FilingCategory Category { get; set; } [JsonPropertyName("subcategory")] - public FilingSubcategory[] Subcategory { get; set; } + public FilingSubcategory[] Subcategory { get; set; } = null!; [JsonPropertyName("transaction_id")] public string? TransactionId { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs index 323145d..22cb3e1 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs @@ -8,7 +8,7 @@ namespace CompaniesHouse.Response.CompanyFiling public class FilingHistoryItemAnnotation : IDescriptable { [JsonPropertyName("annotation")] - public string Annotation { get; set; } + public string Annotation { get; set; } = null!; [JsonPropertyName("date")] public DateTime? DateOfAnnotation { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs index 9afbdb7..9fcc2ad 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs @@ -12,7 +12,7 @@ public class FilingHistoryItemResolution : IDescriptable public ResolutionCategory Category { get; set; } [JsonPropertyName("subcategory")] - public FilingSubcategory[] Subcategory { get; set; } + public FilingSubcategory[] Subcategory { get; set; } = null!; [JsonPropertyName("description")] public string? Description { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs index 9ebd3d7..f60f6a8 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs @@ -7,13 +7,13 @@ namespace CompaniesHouse.Response.CompanyProfile public class Accounts { [JsonPropertyName("accounting_reference_date")] - public AccountingReferenceDate AccountingReferenceDate { get; set; } + public AccountingReferenceDate AccountingReferenceDate { get; set; } = null!; [JsonPropertyName("last_accounts")] - public LastAccounts LastAccounts { get; set; } + public LastAccounts LastAccounts { get; set; } = null!; [JsonPropertyName("next_accounts")] - public NextAccounts NextAccounts { get; set; } + public NextAccounts NextAccounts { get; set; } = null!; [JsonPropertyName("next_due")] [JsonConverter(typeof(OptionalDateJsonConverter))] diff --git a/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs b/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs index cf5d16a..19179df 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs @@ -5,10 +5,10 @@ namespace CompaniesHouse.Response.CompanyProfile public class BranchCompanyDetails { [JsonPropertyName("business_activity")] - public string BusinessActivity { get; set; } + public string BusinessActivity { get; set; } = null!; [JsonPropertyName("parent_company_name")] - public string ParentCompanyName { get; set; } + public string ParentCompanyName { get; set; } = null!; [JsonPropertyName("parent_company_number")] - public string ParentCompanyNumber { get; set; } + public string ParentCompanyNumber { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs index 8f589a0..b8553fe 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs @@ -10,25 +10,25 @@ public class CompanyProfile public CompanyType Type { get; set; } [JsonPropertyName("etag")] - public string ETag { get; set; } + public string ETag { get; set; } = null!; [JsonPropertyName("accounts")] - public Accounts Accounts { get; set; } + public Accounts Accounts { get; set; } = null!; [JsonPropertyName("annual_return")] - public AnnualReturn AnnualReturn { get; set; } + public AnnualReturn AnnualReturn { get; set; } = null!; [JsonPropertyName("confirmation_statement")] - public ConfirmationStatement ConfirmationStatement { get; set; } + public ConfirmationStatement ConfirmationStatement { get; set; } = null!; [JsonPropertyName("can_file")] public bool? CanFile { get; set; } [JsonPropertyName("company_name")] - public string CompanyName { get; set; } + public string CompanyName { get; set; } = null!; [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -75,24 +75,24 @@ public class CompanyProfile public DateTime? LastFullMembersListDate { get; set; } [JsonPropertyName("links")] - public CompanyProfileLinks Links { get; set; } + public CompanyProfileLinks Links { get; set; } = null!; [JsonPropertyName("previous_company_names")] - public PreviousCompanyName[] PreviousCompanyNames { get; set; } + public PreviousCompanyName[] PreviousCompanyNames { get; set; } = null!; [JsonPropertyName("registered_office_address")] - public Address RegisteredOfficeAddress { get; set; } + public Address RegisteredOfficeAddress { get; set; } = null!; [JsonPropertyName("registered_office_is_in_dispute")] public bool? RegisteredOfficeIsInDispute { get; set; } [JsonPropertyName("sic_codes")] - public string[] SicCodes { get; set; } + public string[] SicCodes { get; set; } = null!; [JsonPropertyName("undeliverable_registered_office_address")] public bool? UndeliverableRegisteredOfficeAddress { get; set; } [JsonPropertyName("branch_company_details")] - public BranchCompanyDetails BranchCompanyDetails { get; set; } + public BranchCompanyDetails BranchCompanyDetails { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs index 4baa49d..b5688e5 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs @@ -9,7 +9,7 @@ namespace CompaniesHouse.Response.CompanyProfile public class PreviousCompanyName { [JsonPropertyName("name")] - public string Name { get; set; } + public string Name { get; set; } = null!; [JsonPropertyName("ceased_on")] public DateTime CeasedOn { get; set; } diff --git a/src/CompaniesHouse/Response/Document/DocumentDownload.cs b/src/CompaniesHouse/Response/Document/DocumentDownload.cs index 14a08e2..9f9ad12 100644 --- a/src/CompaniesHouse/Response/Document/DocumentDownload.cs +++ b/src/CompaniesHouse/Response/Document/DocumentDownload.cs @@ -4,8 +4,8 @@ namespace CompaniesHouse.Response.Document { public class DocumentDownload { - public Stream Content { get; set; } - public string ContentType { get; set; } + public Stream Content { get; set; } = null!; + public string ContentType { get; set; } = null!; public long? ContentLength { get; set; } } } diff --git a/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs b/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs index fa7f475..1dd1a75 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs @@ -5,9 +5,9 @@ namespace CompaniesHouse.Response.Officers public class OfficerFormerName { [JsonPropertyName("forenames")] - public string ForeNames { get; set; } + public string ForeNames { get; set; } = null!; [JsonPropertyName("surname")] - public string Surname { get; set; } + public string Surname { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Officers/Officers.cs b/src/CompaniesHouse/Response/Officers/Officers.cs index 18dfc09..11c2cd6 100644 --- a/src/CompaniesHouse/Response/Officers/Officers.cs +++ b/src/CompaniesHouse/Response/Officers/Officers.cs @@ -14,7 +14,7 @@ public class Officers public int? InactiveCount { get; set; } [JsonPropertyName("items")] - public Officer[] Items { get; set; } + public Officer[] Items { get; set; } = null!; [JsonPropertyName("items_per_page")] public int? ItemsPerPage { get; set; } diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs index b9a36d6..f347490 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs @@ -5,18 +5,18 @@ namespace CompaniesHouse.Response.Search.AdvancedCompanySearch public class AdvancedCompanySearch { [JsonPropertyName("etag")] - public string ETag { get; set; } + public string ETag { get; set; } = null!; [JsonPropertyName("hits")] public int? Hits { get; set; } [JsonPropertyName("items")] - public Company[] Items { get; set; } + public Company[] Items { get; set; } = null!; [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("top_hit")] - public Company TopHit { get; set; } + public Company TopHit { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs index 2a74582..1245cca 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs @@ -8,10 +8,10 @@ namespace CompaniesHouse.Response.Search.AdvancedCompanySearch public class Company { [JsonPropertyName("company_name")] - public string CompanyName { get; set; } + public string CompanyName { get; set; } = null!; [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -30,10 +30,10 @@ public class Company public DateTime? DateOfCreation { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("links")] - public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } + public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } = null!; [JsonPropertyName("registered_office_address")] public Address? RegisteredOfficeAddress { get; set; } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Address.cs b/src/CompaniesHouse/Response/Search/AllSearch/Address.cs index 842a262..5455181 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Address.cs @@ -5,27 +5,27 @@ namespace CompaniesHouse.Response.Search.AllSearch public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; [JsonPropertyName("care_of")] - public string CareOf { get; set; } + public string CareOf { get; set; } = null!; [JsonPropertyName("country")] - public string Country { get; set; } + public string Country { get; set; } = null!; [JsonPropertyName("locality")] - public string Locality { get; set; } + public string Locality { get; set; } = null!; [JsonPropertyName("po_box")] - public string PoBox { get; set; } + public string PoBox { get; set; } = null!; [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; [JsonPropertyName("region")] - public string Region { get; set; } + public string Region { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs index 85531ae..e92e6a6 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs @@ -6,16 +6,16 @@ namespace CompaniesHouse.Response.Search.AllSearch public class AllSearch { [JsonPropertyName("etag")] - public string Etag { get; set; } + public string Etag { get; set; } = null!; [JsonPropertyName("items")] - public SearchItem[] Items { get; set; } + public SearchItem[] Items { get; set; } = null!; [JsonPropertyName("items_per_page")] public int? ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Item.cs b/src/CompaniesHouse/Response/Search/AllSearch/Item.cs index d8343ae..1fc47c0 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Item.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Item.cs @@ -2,14 +2,14 @@ namespace CompaniesHouse.Response.Search.AllSearch { public class Item { - public Address address { get; set; } - public string address_snippet { get; set; } - public string description { get; set; } - public string[] description_identifier { get; set; } - public string kind { get; set; } - public Links links { get; set; } - public Matches matches { get; set; } - public string snippet { get; set; } - public string title { get; set; } + public Address address { get; set; } = null!; + public string address_snippet { get; set; } = null!; + public string description { get; set; } = null!; + public string[] description_identifier { get; set; } = null!; + public string kind { get; set; } = null!; + public Links links { get; set; } = null!; + public Matches matches { get; set; } = null!; + public string snippet { get; set; } = null!; + public string title { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Links.cs b/src/CompaniesHouse/Response/Search/AllSearch/Links.cs index bf8fc34..6dd0da4 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Links.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Links.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Search.AllSearch public class Links { [JsonPropertyName("self")] - public string Self { get; set; } + public string Self { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs b/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs index c90e7d4..4854b25 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs @@ -5,10 +5,10 @@ namespace CompaniesHouse.Response.Search.AllSearch public class Matches { [JsonPropertyName("address_snippet")] - public string[] AddressSnippet { get; set; } + public string[] AddressSnippet { get; set; } = null!; [JsonPropertyName("snippet")] - public string[] Snippet { get; set; } + public string[] Snippet { get; set; } = null!; [JsonPropertyName("title")] - public string[] Title { get; set; } + public string[] Title { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs index c19a2ef..ea318b2 100644 --- a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs +++ b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch public class CompaniesAlphabeticallySearch { [JsonPropertyName("items")] - public Company[] Items { get; set; } + public Company[] Items { get; set; } = null!; [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("top_hit")] - public Company TopHit { get; set; } + public Company TopHit { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs index 7867600..7833c51 100644 --- a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs @@ -6,10 +6,10 @@ namespace CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch public class Company { [JsonPropertyName("company_name")] - public string CompanyName { get; set; } + public string CompanyName { get; set; } = null!; [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -18,12 +18,12 @@ public class Company public CompanyType CompanyType { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("links")] - public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } + public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } = null!; [JsonPropertyName("ordered_alpha_key_with_id")] - public string OrderedAlphaKeyWithId { get; set; } + public string OrderedAlphaKeyWithId { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs b/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs index 4999911..a8b524b 100644 --- a/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs +++ b/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Search public class CompanyProfileLinks { [JsonPropertyName("company_profile")] - public string CompanyProfile { get; set; } + public string CompanyProfile { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs index bf13fb8..60d3da7 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs @@ -7,13 +7,13 @@ namespace CompaniesHouse.Response.Search.CompanySearch public class Company : SearchItem { [JsonPropertyName("address")] - public Address Address { get; set; } + public Address Address { get; set; } = null!; [JsonPropertyName("address_snippet")] public string? AddressSnippet { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -29,21 +29,21 @@ public class Company : SearchItem public DateTime? DateOfCreation { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } + public string Description { get; set; } = null!; [JsonPropertyName("description_identifier")] - public string[] DescriptionIdentifier { get; set; } + public string[] DescriptionIdentifier { get; set; } = null!; [JsonPropertyName("external_registration_number")] public string? ExternalRegistrationNumber { get; set; } [JsonPropertyName("matches")] - public Matches Matches { get; set; } + public Matches Matches { get; set; } = null!; [JsonPropertyName("snippet")] - public string Snippet { get; set; } + public string Snippet { get; set; } = null!; [JsonPropertyName("title")] - public string Title { get; set; } + public string Title { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs b/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs index 86d20f8..4998ff2 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs @@ -5,16 +5,16 @@ namespace CompaniesHouse.Response.Search.CompanySearch public class CompanySearch { [JsonPropertyName("etag")] - public string ETag { get; set; } + public string ETag { get; set; } = null!; [JsonPropertyName("items")] - public Company[] Companies { get; set; } + public Company[] Companies { get; set; } = null!; [JsonPropertyName("items_per_page")] public int? ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs index 5924b82..0f59bee 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs @@ -5,24 +5,24 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; [JsonPropertyName("country")] - public string Country { get; set; } + public string Country { get; set; } = null!; [JsonPropertyName("locality")] - public string Locality { get; set; } + public string Locality { get; set; } = null!; [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; [JsonPropertyName("premises")] - public string Premises { get; set; } + public string Premises { get; set; } = null!; [JsonPropertyName("region")] - public string Region { get; set; } + public string Region { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs index 88b9cb4..2b8d9fe 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs @@ -6,27 +6,27 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class DisqualifiedOfficer : SearchItem { [JsonPropertyName("address")] - public Address Address { get; set; } + public Address Address { get; set; } = null!; [JsonPropertyName("address_snippet")] - public string AddressSnippet { get; set; } + public string AddressSnippet { get; set; } = null!; [JsonPropertyName("date_of_birth")] public DateTime DateOfBirth { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } + public string Description { get; set; } = null!; [JsonPropertyName("description_identifiers")] - public string[] DescriptionIdentifiers { get; set; } + public string[] DescriptionIdentifiers { get; set; } = null!; [JsonPropertyName("matches")] - public Match Matches { get; set; } + public Match Matches { get; set; } = null!; [JsonPropertyName("snippet")] - public string Snippet { get; set; } + public string Snippet { get; set; } = null!; [JsonPropertyName("title")] - public string Title { get; set; } + public string Title { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs index fcf7868..e2ccadb 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs @@ -5,13 +5,13 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class DisqualifiedOfficerSearch { [JsonPropertyName("items")] - public DisqualifiedOfficer[] DisqualifiedOfficers { get; set; } + public DisqualifiedOfficer[] DisqualifiedOfficers { get; set; } = null!; [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs index 2259bf2..0090ef0 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class Match { [JsonPropertyName("address_snippet")] - public string[] AddressSnippet { get; set; } + public string[] AddressSnippet { get; set; } = null!; [JsonPropertyName("snippet")] - public string[] Snippet { get; set; } + public string[] Snippet { get; set; } = null!; [JsonPropertyName("title")] - public string[] Title { get; set; } + public string[] Title { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs index 3cb49ee..198e79c 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs @@ -8,10 +8,10 @@ namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch public class Company { [JsonPropertyName("company_name")] - public string CompanyName { get; set; } + public string CompanyName { get; set; } = null!; [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -24,7 +24,7 @@ public class Company public DateTime? DateOfCreation { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("matched_previous_company_name")] public PreviousCompanyName? MatchedPreviousCompanyName { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs index f9fa539..1f2794d 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs @@ -5,18 +5,18 @@ namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch public class DissolvedCompaniesSearch { [JsonPropertyName("etag")] - public string ETag { get; set; } + public string ETag { get; set; } = null!; [JsonPropertyName("hits")] public int? Hits { get; set; } [JsonPropertyName("items")] - public Company[] Items { get; set; } + public Company[] Items { get; set; } = null!; [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("top_hit")] - public Company TopHit { get; set; } + public Company TopHit { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs index 7c9a2bf..f46a468 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs @@ -9,12 +9,12 @@ public class PreviousCompanyName public DateTime? CeasedOn { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; [JsonPropertyName("effective_from")] public DateTime? EffectiveFrom { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public string Name { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/Links.cs b/src/CompaniesHouse/Response/Search/Links.cs index e51974e..f1a1eea 100644 --- a/src/CompaniesHouse/Response/Search/Links.cs +++ b/src/CompaniesHouse/Response/Search/Links.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Search public class Links { [JsonPropertyName("self")] - public string Self { get; set; } + public string Self { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs index cd4fca6..8769539 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs @@ -5,30 +5,30 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; [JsonPropertyName("care_of")] - public string CareOf { get; set; } + public string CareOf { get; set; } = null!; [JsonPropertyName("country")] - public string Country { get; set; } + public string Country { get; set; } = null!; [JsonPropertyName("locality")] - public string Locality { get; set; } + public string Locality { get; set; } = null!; [JsonPropertyName("po_box")] - public string PoBox { get; set; } + public string PoBox { get; set; } = null!; [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; [JsonPropertyName("premises")] - public string Premises { get; set; } + public string Premises { get; set; } = null!; [JsonPropertyName("region")] - public string Region { get; set; } + public string Region { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs index 141891f..39b1cf1 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class Match { [JsonPropertyName("address_snippet")] - public int[] AddressSnippet { get; set; } + public int[] AddressSnippet { get; set; } = null!; [JsonPropertyName("snippet")] - public int[] Snippet { get; set; } + public int[] Snippet { get; set; } = null!; [JsonPropertyName("title")] - public int[] Title { get; set; } + public int[] Title { get; set; } = null!; } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs index 9bb19aa..870e865 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs @@ -5,31 +5,31 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class Officer : SearchItem { [JsonPropertyName("address")] - public Address Address { get; set; } + public Address Address { get; set; } = null!; [JsonPropertyName("address_snippet")] - public string AddressSnippet { get; set; } + public string AddressSnippet { get; set; } = null!; [JsonPropertyName("appointment_count")] public int AppointmentCount { get; set; } [JsonPropertyName("date_of_birth")] - public DateOfBirth DateOfBirth { get; set; } + public DateOfBirth DateOfBirth { get; set; } = null!; [JsonPropertyName("description")] - public string Description { get; set; } + public string Description { get; set; } = null!; [JsonPropertyName("description_identifiers")] - public string[] DescriptionIdentifiers { get; set; } + public string[] DescriptionIdentifiers { get; set; } = null!; [JsonPropertyName("matches")] - public Match Matches { get; set; } + public Match Matches { get; set; } = null!; [JsonPropertyName("snippet")] - public string Snippet { get; set; } + public string Snippet { get; set; } = null!; [JsonPropertyName("title")] - public string Title { get; set; } + public string Title { get; set; } = null!; public string OfficerId { diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs index ddf25ca..f5c53f8 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs @@ -6,13 +6,13 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class OfficerSearch { [JsonPropertyName("items")] - public Officer[] Officers { get; set; } + public Officer[] Officers { get; set; } = null!; [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/SearchItem.cs b/src/CompaniesHouse/Response/Search/SearchItem.cs index 3e29f4e..e830e85 100644 --- a/src/CompaniesHouse/Response/Search/SearchItem.cs +++ b/src/CompaniesHouse/Response/Search/SearchItem.cs @@ -8,9 +8,9 @@ namespace CompaniesHouse.Response.Search public abstract class SearchItem { [JsonPropertyName("kind")] - public string Kind { get; set; } + public string Kind { get; set; } = null!; [JsonPropertyName("links")] - public Links Links { get; set; } + public Links Links { get; set; } = null!; } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs index d28156a..9ef8ae5 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs @@ -29,11 +29,13 @@ public async Task ThenChargesListIsNotEmpty(string companyNumber) public async Task ThenKnownChargeListIncludesObservedGeneratedValues() { var result = await _client.GetChargesListAsync("03977902"); + var items = result.Data.Items ?? []; result.Data.UnfilteredCount.ShouldNotBeNull(); result.Data.UnfilteredCount.Value.ShouldBeGreaterThan(0); - result.Data.Items[0].Status.Value.ShouldNotBeNullOrWhiteSpace(); - result.Data.Items[0].Links?.Self.ShouldNotBeNullOrWhiteSpace(); + items.ShouldNotBeEmpty(); + items[0].Status.Value.ShouldNotBeNullOrWhiteSpace(); + items[0].Links?.Self.ShouldNotBeNullOrWhiteSpace(); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs index 51becf6..4116d6b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs @@ -35,8 +35,9 @@ public async Task ThenTheDataItemsAreNotEmpty(string companyNumber) do { result = await _client.GetCompanyFilingHistoryAsync(companyNumber, page++ * size, size); - results.AddRange(result.Data.Items); - } while (result.Data.Items.Any()); + var items = result.Data.Items ?? []; + results.AddRange(items); + } while ((result.Data.Items ?? []).Any()); results.ShouldNotBeEmpty(); } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs index 66bc617..b3f9c32 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs @@ -25,9 +25,12 @@ public void ThenTheDataItemsAreNotEmpty() [IntegrationFact] public void ThenObservedCountsAndKindsAreReturned() { + var items = _result.Data.Items ?? []; + _result.Data.TotalResults.ShouldNotBeNull(); _result.Data.TotalResults.Value.ShouldBeGreaterThan(0); - _result.Data.Items[0].Kind.Value.ShouldNotBeNullOrWhiteSpace(); + items.ShouldNotBeEmpty(); + items[0].Kind.Value.ShouldNotBeNullOrWhiteSpace(); } private async Task WhenRetrievingAnCompanyPersonsWithSignificantControlForAnValidCompany() diff --git a/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs b/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs index cc1890c..2a27519 100644 --- a/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs +++ b/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs @@ -39,10 +39,12 @@ public void Appointments_DeserializesCorporateAndEnvelopeFields() """; var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + var items = value?.Items ?? []; value.ShouldNotBeNull(); value.IsCorporateOfficer.ShouldBeTrue(); - value.Items[0].Identification?.RegistrationNumber.ShouldBe("3849195"); + items.ShouldNotBeEmpty(); + items[0].Identification?.RegistrationNumber.ShouldBe("3849195"); } [Fact] @@ -71,11 +73,13 @@ public void PersonsWithSignificantControl_DeserializesCorporateEntityList() """; var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + var items = value?.Items ?? []; value.ShouldNotBeNull(); value.TotalResults.ShouldBe(1); - value.Items[0].Kind.ShouldBe(new PersonWithSignificantControlKind("corporate-entity-person-with-significant-control")); - value.Items[0].NaturesOfControl.ShouldContain(new PersonWithSignificantControlNatureOfControl("right-to-appoint-and-remove-directors")); + items.ShouldNotBeEmpty(); + items[0].Kind.ShouldBe(new PersonWithSignificantControlKind("corporate-entity-person-with-significant-control")); + (items[0].NaturesOfControl ?? []).ShouldContain(new PersonWithSignificantControlNatureOfControl("right-to-appoint-and-remove-directors")); } } } diff --git a/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs index 2c6cf1c..6beeb15 100644 --- a/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs @@ -34,13 +34,13 @@ public void ForeignCompanyProfile_DeserializesForeignCompanyDetails() profile.Type.ShouldBe(CompanyType.OverseaCompany); profile.ExternalRegistrationNumber.ShouldBe("198600479406"); profile.ForeignCompanyDetails.ShouldNotBeNull(); - profile.ForeignCompanyDetails.AccountingRequirement.ForeignAccountType.ShouldBe( + profile.ForeignCompanyDetails!.AccountingRequirement!.ForeignAccountType.ShouldBe( ForeignAccountType.AccountingRequirementsOfOriginatingCountryApply); - profile.ForeignCompanyDetails.AccountingRequirement.TermsOfAccountPublication.ShouldBe( + profile.ForeignCompanyDetails.AccountingRequirement!.TermsOfAccountPublication.ShouldBe( TermsOfAccountPublication.AccountsPublicationDateSuppliedByCompany); - profile.ForeignCompanyDetails.Accounts.AccountPeriodFrom.Day.ShouldBe(1); - profile.ForeignCompanyDetails.Accounts.AccountPeriodTo.Month.ShouldBe(12); - profile.ForeignCompanyDetails.Accounts.MustFileWithin.Months.ShouldBe("12"); + profile.ForeignCompanyDetails.Accounts!.AccountPeriodFrom!.Day.ShouldBe(1); + profile.ForeignCompanyDetails.Accounts.AccountPeriodTo!.Month.ShouldBe(12); + profile.ForeignCompanyDetails.Accounts.MustFileWithin!.Months.ShouldBe("12"); profile.ForeignCompanyDetails.IsACreditFinancialInstitution.ShouldBe(true); profile.Links.UkEstablishments.ShouldBe("/company/FC040879/uk-establishments"); } diff --git a/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs b/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs index 9fefc84..9e846d8 100644 --- a/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs +++ b/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs @@ -64,11 +64,13 @@ public void CompanyCharges_DeserializesUnfilteredCountAndGeneratedValueTypes() """; var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + var items = value?.Items ?? []; value.ShouldNotBeNull(); value.UnfilteredCount.ShouldBe(1); - value.Items[0].Status.ShouldBe(new ChargeStatus("outstanding")); - value.Items[0].Classification?.Type.ShouldBe(new ClassificationChargeType("charge-description")); + items.ShouldNotBeEmpty(); + items[0].Status.ShouldBe(new ChargeStatus("outstanding")); + items[0].Classification?.Type.ShouldBe(new ClassificationChargeType("charge-description")); } } } diff --git a/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs b/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs index 73973d8..ed800aa 100644 --- a/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs +++ b/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs @@ -31,11 +31,13 @@ public void CompanyInsolvencyInformation_DeserializesStatusesAndCaseDates() """; var value = JsonSerializer.Deserialize(json, CompaniesHouseJsonSerializerOptions.Default); + var cases = value?.Cases ?? []; value.ShouldNotBeNull(); value.Status.ShouldBe([new InsolvencyStatus("liquidation")]); - value.Cases[0].Type.ShouldBe(InsolvencyCaseType.CreditorsVoluntaryLiquidation); - value.Cases[0].Dates.ShouldContain(x => x.Type == new CaseDateType("liquidation-started-on") && x.Date == new DateTime(2013, 05, 29)); + cases.ShouldNotBeEmpty(); + cases[0].Type.ShouldBe(InsolvencyCaseType.CreditorsVoluntaryLiquidation); + (cases[0].Dates ?? []).ShouldContain(x => x.Type == new CaseDateType("liquidation-started-on") && x.Date == new DateTime(2013, 05, 29)); } } } diff --git a/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs index e9f8b8d..4307e92 100644 --- a/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs @@ -49,8 +49,8 @@ public void CorporateOfficerList_DeserializesIdentificationType() officers.Items.Length.ShouldBe(1); officers.Items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); officers.Items[0].Identification.ShouldNotBeNull(); - officers.Items[0].Identification.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); - officers.Items[0].Identification.RegistrationNumber.ShouldBe("3849195"); + officers.Items[0].Identification!.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); + officers.Items[0].Identification!.RegistrationNumber.ShouldBe("3849195"); } private const string OfficerListJson = """ diff --git a/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs b/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs index 1b7bb4d..1ba4bc1 100644 --- a/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs +++ b/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs @@ -57,6 +57,7 @@ public void DocumentMetadata_DeserializesObservedFields() value.CreatedAt.Value.Year.ShouldBe(2026); value.CreatedAt.Value.Month.ShouldBe(7); value.CreatedAt.Value.Day.ShouldBe(1); + value.Resources.ShouldNotBeNull(); value.Resources["application/pdf"].ContentLength.ShouldBe(82803); } } diff --git a/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs index 41c2bb5..811d324 100644 --- a/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs @@ -20,16 +20,19 @@ public SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests() public async Task RunScenario() { var officersSearch = await _client.SearchOfficerAsync(new SearchOfficerRequest() { Query = "Richard Branson" }); + var officers = officersSearch.Data.Officers ?? []; - var foundOfficer = officersSearch.Data.Officers.Single(x => x.DateOfBirth?.Year == 1950 && x.DateOfBirth?.Month == 7); + var foundOfficer = officers.Single(x => x.DateOfBirth?.Year == 1950 && x.DateOfBirth?.Month == 7); var officerAppointments = await _client.GetAppointmentsAsync(foundOfficer.OfficerId); + var appointments = officerAppointments.Data.Items ?? []; - var companyNumber = officerAppointments.Data.Items - .Single(x => x.Appointed.CompanyName == "VIRGIN LIMITED") - .Appointed.CompanyNumber; + var companyNumber = appointments + .Single(x => x.Appointed?.CompanyName == "VIRGIN LIMITED") + .Appointed?.CompanyNumber; + companyNumber.ShouldNotBeNullOrWhiteSpace(); - var companyProfile = await _client.GetCompanyProfileAsync(companyNumber); + var companyProfile = await _client.GetCompanyProfileAsync(companyNumber!); companyProfile.Data.ShouldNotBeNull(); companyProfile.Data.CompanyNumber.ShouldBe("01946167"); diff --git a/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs index 307a3c7..f4ae204 100644 --- a/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs @@ -62,7 +62,7 @@ public void AdvancedCompanySearchPayload_DeserializesOptionalSubtypeAndSicCodes( payload.ShouldNotBeNull(); payload.TopHit.CompanySubtype.ShouldBeNull(); payload.Items[0].RegisteredOfficeAddress.ShouldNotBeNull(); - payload.Items[0].RegisteredOfficeAddress.AddressLine1.ShouldBeNull(); + payload.Items[0].RegisteredOfficeAddress?.AddressLine1.ShouldBeNull(); payload.Items[0].SicCodes.ShouldBeNull(); payload.Items[1].CompanySubtype.ShouldBe(CompanySubtype.CommunityInterestCompany); payload.Items[1].SicCodes.ShouldBe(["86900"]); @@ -78,10 +78,10 @@ public void DissolvedCompaniesPayload_DeserializesSearchTypeSpecificOptionalFiel payload.Hits.ShouldBe(932); payload.TopHit.OrderedAlphaKeyWithId.ShouldBeNull(); payload.TopHit.MatchedPreviousCompanyName.ShouldNotBeNull(); - payload.TopHit.MatchedPreviousCompanyName.Name.ShouldBe("RADIO RENTALS VODAFONE LIMITED"); + payload.TopHit.MatchedPreviousCompanyName!.Name.ShouldBe("RADIO RENTALS VODAFONE LIMITED"); payload.TopHit.RegisteredOfficeAddress.ShouldBeNull(); payload.Items.Single().PreviousCompanyNames.ShouldNotBeNull(); - payload.Items.Single().PreviousCompanyNames.Length.ShouldBe(3); + payload.Items.Single().PreviousCompanyNames?.Length.ShouldBe(3); } private const string SearchAllJson = """ diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTestCase.cs b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTestCase.cs index 91de9e3..d6534e2 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTestCase.cs @@ -2,10 +2,10 @@ namespace CompaniesHouse.Tests.CompaniesHouseChargesClientTests { public class CompaniesHouseChargesClientTestCase { - public string ParticularType { get; set; } - public string SecureDetailType { get; set; } - public string AssetsCeasedReleased { get; set; } - public string ClassificationChargeType { get; set; } - public string Status { get; set; } + public string ParticularType { get; set; } = null!; + public string SecureDetailType { get; set; } = null!; + public string AssetsCeasedReleased { get; set; } = null!; + public string ClassificationChargeType { get; set; } = null!; + public string Status { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs index 1df276c..7c4c5a4 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs @@ -28,9 +28,10 @@ public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyCharges(Co var result = await client.GetChargesListAsync("1", 0, 25); EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, charges, "TransactionId", "UnfilteredCount"); - foreach (var (actual, expected) in result.Data.Items.Zip(charges.Items)) + foreach (var (actual, expected) in (result.Data.Items ?? []).Zip(charges.Items ?? [])) { - actual.InsolvencyCases.Select(x => x.TransactionId).ShouldBe(expected.InsolvencyCases.Select(x => (long?)x.TransactionId)); + (actual.InsolvencyCases ?? []).Select(x => x.TransactionId) + .ShouldBe((expected.InsolvencyCases ?? []).Select(x => (long?)x.TransactionId)); } } @@ -49,7 +50,8 @@ public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyChargeById var result = await client.GetChargeByIdAsync("1", "1"); EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, charge, "TransactionId"); - result.Data.InsolvencyCases.Select(x => x.TransactionId).ShouldBe(charge.InsolvencyCases.Select(x => (long?)x.TransactionId)); + (result.Data.InsolvencyCases ?? []).Select(x => x.TransactionId) + .ShouldBe((charge.InsolvencyCases ?? []).Select(x => (long?)x.TransactionId)); } public static IEnumerable TestCases() diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTestCase.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTestCase.cs index 8d0ff49..a9a9098 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTestCase.cs @@ -2,12 +2,12 @@ namespace CompaniesHouse.Tests.CompaniesHouseCompanyFilingHistoryClientTests { public class CompaniesHouseCompanyFilingHistoryClientTestCase { - public string Category { get; set; } + public string Category { get; set; } = null!; - public string Subcategory { get; set; } + public string Subcategory { get; set; } = null!; - public string HistoryStatus { get; set; } + public string HistoryStatus { get; set; } = null!; - public string ResolutionCategory { get; set; } + public string ResolutionCategory { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs index ce1b9d3..c3bc06a 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyInsolvencyInformationClientTests/CompaniesHouseCompanyInsolvencyInformationClientTests.cs @@ -46,8 +46,8 @@ public async Task GivenARealCapturedInsolvencyPayload_WhenGettingCompanyInsolven result.Data.Status.ShouldBe([new InsolvencyStatus("in-administration"), new InsolvencyStatus("administrative-receiver")]); result.Data.Cases.ShouldNotBeNull(); result.Data.Cases[0].Type.ShouldBe(InsolvencyCaseType.InAdministration); - result.Data.Cases[0].Dates.ShouldContain(x => x.Type == new CaseDateType("administration-started-on")); - result.Data.Cases[0].Practitioners.ShouldContain(x => x.Name == "Charles William Anthony Escott" && x.CeasedToActOn == new DateTime(2012, 06, 01)); + (result.Data.Cases[0].Dates ?? []).ShouldContain(x => x.Type == new CaseDateType("administration-started-on")); + (result.Data.Cases[0].Practitioners ?? []).ShouldContain(x => x.Name == "Charles William Anthony Escott" && x.CeasedToActOn == new DateTime(2012, 06, 01)); } } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTestCase.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTestCase.cs index e2c8567..2c94e1f 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTestCase.cs @@ -2,14 +2,14 @@ namespace CompaniesHouse.Tests.CompaniesHouseCompanyProfileClientTests { public class CompaniesHouseCompanyProfileClientTestCase { - public string LastAccountsType { get; set; } + public string LastAccountsType { get; set; } = null!; - public string CompanyStatus { get; set; } + public string CompanyStatus { get; set; } = null!; - public string CompanyStatusDetail { get; set; } + public string CompanyStatusDetail { get; set; } = null!; - public string Jurisdiction { get; set; } + public string Jurisdiction { get; set; } = null!; - public string Type { get; set; } + public string Type { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs index c83e809..aeab7ce 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs @@ -19,10 +19,10 @@ namespace CompaniesHouse.Tests.CompaniesHouseCompanyProfileClientTests { public class CompaniesHouseCompanyProfileClientTests { - private CompaniesHouseCompanyProfileClient _client; + private CompaniesHouseCompanyProfileClient _client = null!; - private CompaniesHouseResponse _result; - private ResourceBuilders.CompanyProfile _companyProfile; + private CompaniesHouseResponse _result = null!; + private ResourceBuilders.CompanyProfile _companyProfile = null!; [Theory] [MemberData(nameof(TestCases))] diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs index 08f1ddc..3c62e78 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs @@ -12,7 +12,7 @@ namespace CompaniesHouse.Tests.CompaniesHouseDocumentClientTests { public class CompaniesHouseDocumentClientTests : IAsyncLifetime { - private CompaniesHouseResponse _result; + private CompaniesHouseResponse _result = null!; private const string ExpectedMediaType = "application/pdf"; private const string ExpectedContent = "test pdf"; private const string DocumentId = "wibble"; diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs index 1a1f765..de2c3a6 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs @@ -63,6 +63,7 @@ public async Task GivenARealCapturedDocumentMetadata_WhenGettingDocumentMetadata result.Data.ShouldNotBeNull(); result.Data.Filename.ShouldBe("00445790_cs01_2026-07-01"); + result.Data.Resources.ShouldNotBeNull(); result.Data.Resources.ShouldContainKey("application/pdf"); result.Data.Resources["application/pdf"].ContentLength.ShouldBe(82803); } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs index 85c9301..842a6d3 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs @@ -1,20 +1,20 @@ -using System; +using System; using System.Collections.Generic; namespace CompaniesHouse.Tests.CompaniesHouseDocumentMetadataClientTests { public class DocumentMetadataTestCase { - public string CompanyNumber { get; set; } - public string Barcode { get; set; } + public string CompanyNumber { get; set; } = null!; + public string Barcode { get; set; } = null!; public DateTime SignificantDate { get; set; } - public string SignificantDateType { get; set; } - public string Category { get; set; } + public string SignificantDateType { get; set; } = null!; + public string Category { get; set; } = null!; public int Pages { get; set; } public DateTime CreatedAt { get; set; } - public string Etag { get; set; } - public Dictionary Resources { get; set; } - public Links Links { get; set; } + public string Etag { get; set; } = null!; + public Dictionary Resources { get; set; } = null!; + public Links Links { get; set; } = null!; } public class ResourceContentLength @@ -24,7 +24,7 @@ public class ResourceContentLength public class Links { - public string Self { get; set; } - public string Document { get; set; } + public string Self { get; set; } = null!; + public string Document { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficerByAppointmentTestCase.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficerByAppointmentTestCase.cs index 09c8ce2..322d670 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficerByAppointmentTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficerByAppointmentTestCase.cs @@ -2,6 +2,6 @@ namespace CompaniesHouse.Tests.CompaniesHouseOfficersAppointmentClientTests { public class CompaniesHouseOfficerByAppointmentTestCase { - public string OfficerRole { get; set; } + public string OfficerRole { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs index 30d64ff..c72f367 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs @@ -13,10 +13,10 @@ namespace CompaniesHouse.Tests.CompaniesHouseOfficersTests { public class CompaniesHouseCompanyOfficersClientTests { - private CompaniesHouseOfficersClient _client; + private CompaniesHouseOfficersClient _client = null!; - private CompaniesHouseResponse _result; - private ResourceBuilders.Officers _officers; + private CompaniesHouseResponse _result = null!; + private ResourceBuilders.Officers _officers = null!; [Fact] public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile() @@ -95,8 +95,8 @@ public async Task GivenARealCapturedCorporateOfficerList_WhenGettingOfficers_The result.Data.Items.Length.ShouldBe(1); result.Data.Items[0].Identification.ShouldNotBeNull(); result.Data.Items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); - result.Data.Items[0].Identification.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); - result.Data.Items[0].Identification.RegistrationNumber.ShouldBe("3849195"); + result.Data.Items[0].Identification!.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); + result.Data.Items[0].Identification!.RegistrationNumber.ShouldBe("3849195"); result.Data.Items[0].OfficerId.ShouldBe("YwIOmduyS6PW5axJgQQrsTGyRD0"); } diff --git a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs index e1af730..bf36fd1 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs @@ -13,10 +13,10 @@ namespace CompaniesHouse.Tests.CompaniesHousePersonsWithSignificantControlTests { public class CompaniesHousePersonsWithSignificantControlTests { - private CompaniesHousePersonsWithSignificantControlClient _client; + private CompaniesHousePersonsWithSignificantControlClient _client = null!; - private CompaniesHouseResponse _result; - private ResourceBuilders.PersonsWithSignificantControl _personsWithSignificantControl; + private CompaniesHouseResponse _result = null!; + private ResourceBuilders.PersonsWithSignificantControl _personsWithSignificantControl = null!; [Fact] public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingPersonsWithSignificantControl() @@ -79,7 +79,7 @@ public async Task GivenARealCapturedCorporatePscList_WhenGettingPersonsWithSigni result.Data.Items.ShouldNotBeNull(); result.Data.Items[0].Kind.ShouldBe(new PersonWithSignificantControlKind("corporate-entity-person-with-significant-control")); result.Data.Items[0].Identification?.RegistrationNumber.ShouldBe("5786925"); - result.Data.Items[0].NaturesOfControl.ShouldContain(new PersonWithSignificantControlNatureOfControl("right-to-appoint-and-remove-directors")); + (result.Data.Items[0].NaturesOfControl ?? []).ShouldContain(new PersonWithSignificantControlNatureOfControl("right-to-appoint-and-remove-directors")); } } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTestCase.cs b/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTestCase.cs index 31ac227..7e13a84 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTestCase.cs @@ -2,6 +2,6 @@ namespace CompaniesHouse.Tests.CompaniesHouseRegisteredOfficeAddressTests { public class CompaniesHouseRegisteredOfficeAddressTestCase { - public string Country { get; set; } + public string Country { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs index 2c7ebae..4c89382 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs @@ -79,7 +79,7 @@ public async Task GivenAResponse_WhenPerformingAnAdvancedCompanySearch_ThenTheTy company.CompanySubtype.ShouldBe(CompanySubtype.CommunityInterestCompany); company.CompanyType.ShouldBe(CompanyType.Ltd); company.Links.CompanyProfile.ShouldBe("/company/01234567"); - company.RegisteredOfficeAddress.Country.ShouldBe("England"); + company.RegisteredOfficeAddress?.Country.ShouldBe("England"); company.SicCodes.ShouldBe(new[] { "62012", "62020" }); result.Data.TopHit.CompanyNumber.ShouldBe("01234567"); } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs index 6a7d763..f935b5d 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs @@ -45,7 +45,7 @@ public CompaniesHouseSearchClientTestsForCompanySearch() .With(x => x.CompanyType, "private-unlimited").With(x => x.Kind, "searchresults#company").Create(), fixture.Build().With(x => x.CompanyStatus, "closed-on") .With(x => x.CompanyType, "private-unlimited").With(x => x.Kind, "searchresults#company").Create(), - fixture.Build().With(x => x.CompanyStatus, () => (string)null) + fixture.Build().With(x => x.CompanyStatus, () => null!) .With(x => x.CompanyType, "private-unlimited").With(x => x.Kind, "searchresults#company").Create(), }; diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs index 3f2e291..a5655ad 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs @@ -97,9 +97,9 @@ public async Task GivenAResponse_WhenSearchingDissolvedCompanies_ThenTheTypedPay var company = result.Data.Items[0]; company.CompanyStatus.ShouldBe(CompanyStatus.Dissolved); company.DateOfCessation.ShouldBe(new DateTime(2023, 01, 20)); - company.MatchedPreviousCompanyName.Name.ShouldBe("OLD ABC LIMITED"); - company.PreviousCompanyNames[0].CompanyNumber.ShouldBe("01234567"); - company.RegisteredOfficeAddress.Locality.ShouldBe("Cardiff"); + company.MatchedPreviousCompanyName?.Name.ShouldBe("OLD ABC LIMITED"); + company.PreviousCompanyNames?[0].CompanyNumber.ShouldBe("01234567"); + company.RegisteredOfficeAddress?.Locality.ShouldBe("Cardiff"); result.Data.TopHit.OrderedAlphaKeyWithId.ShouldBe("ABC DISSOLVED LIMITED:01234567"); } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs index 339f970..c4c8e2b 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs @@ -15,9 +15,9 @@ namespace CompaniesHouse.Tests.CompaniesHouseSearchClientTests { public class CompaniesHouseSearchClientTestsForOfficerSearch : IAsyncLifetime { - private CompaniesHouseSearchClient _client; - private CompaniesHouseResponse _result; - private ResourceDetails _resourceDetails; + private CompaniesHouseSearchClient _client = null!; + private CompaniesHouseResponse _result = null!; + private ResourceDetails _resourceDetails = null!; public async Task InitializeAsync() { diff --git a/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs b/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs index 2ef03a8..9c6f605 100644 --- a/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs +++ b/tests/CompaniesHouse.Tests/DelegatingHandlers/CompaniesHouseAuthorizationHandlerTests.cs @@ -11,9 +11,9 @@ namespace CompaniesHouse.Tests.DelegatingHandlers { public class CompaniesHouseAuthorizationHandlerTests { - private CompaniesHouseAuthorizationHandler _handler; - private string _apiKey; - private HttpRequestMessage _actual; + private CompaniesHouseAuthorizationHandler _handler = null!; + private string _apiKey = null!; + private HttpRequestMessage _actual = null!; public CompaniesHouseAuthorizationHandlerTests() { @@ -35,6 +35,7 @@ public CompaniesHouseAuthorizationHandlerTests() [Fact] public void ThenAuthorizationHeaderIsCorrect() { + _actual.Headers.Authorization.ShouldNotBeNull(); _actual.Headers.Authorization.Scheme.ShouldBe("Basic"); _actual.Headers.Authorization.Parameter.ShouldBe("NDJjODE1NGUtOTgyZC00YTYyLTkxM2QtODlhYWZiMzIwZGJj"); } diff --git a/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs index 1750362..8e0a55d 100644 --- a/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs +++ b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs @@ -29,7 +29,7 @@ public static void ShouldBeEquivalentTo(this T actual, T expected, params str } } - private static void Compare(object actual, object expected, string path, string[] excluding, List differences) + private static void Compare(object? actual, object? expected, string path, string[] excluding, List differences) { if (ReferenceEquals(actual, expected)) { @@ -151,7 +151,7 @@ private static void Compare(object actual, object expected, string path, string[ } } - private static bool ValuesEqual(object actual, object expected) + private static bool ValuesEqual(object? actual, object? expected) { if (actual is Enum actualEnum && expected is string expectedString) { @@ -166,18 +166,18 @@ private static bool ValuesEqual(object actual, object expected) return Equals(actual, expected); } - private static string Describe(object value) => value?.ToString() ?? "null"; + private static string Describe(object? value) => value?.ToString() ?? "null"; private static string GetEnumMemberValue(Enum enumValue) { var type = enumValue.GetType(); var info = type.GetField(enumValue.ToString()); - var enumMember = (EnumMemberAttribute[])info?.GetCustomAttributes(typeof(EnumMemberAttribute), false); + var enumMember = (EnumMemberAttribute[]?)info?.GetCustomAttributes(typeof(EnumMemberAttribute), false); - return enumMember is { Length: > 0 } ? enumMember[0].Value : enumValue.ToString(); + return enumMember is { Length: > 0 } ? enumMember[0].Value ?? enumValue.ToString() : enumValue.ToString(); } - private static bool TryGetStringBackedValue(object value, out string rawValue) + private static bool TryGetStringBackedValue(object? value, out string rawValue) { rawValue = string.Empty; diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Accounts.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Accounts.cs index 124f627..fb907cd 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Accounts.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Accounts.cs @@ -6,11 +6,11 @@ public class Accounts { public DateTime NextDue { get; set; } - public AccountingReferenceDate AccountingReferenceDate { get; set; } + public AccountingReferenceDate AccountingReferenceDate { get; set; } = null!; - public LastAccounts LastAccounts { get; set; } + public LastAccounts LastAccounts { get; set; } = null!; - public NextAccounts NextAccounts { get; set; } + public NextAccounts NextAccounts { get; set; } = null!; public DateTime NextMadeUpTo { get; set; } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Charge.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Charge.cs index 8c0c48c..bda5451 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Charge.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Charge.cs @@ -6,13 +6,13 @@ public class Charge { public DateTime? AcquiredOn { get; set; } - public string AssetsCeasedReleased { get; set; } + public string AssetsCeasedReleased { get; set; } = null!; - public string ChargeCode { get; set; } + public string ChargeCode { get; set; } = null!; public int? ChargeNumber { get; set; } - public Classification Classification { get; set; } + public Classification Classification { get; set; } = null!; public DateTime? CoveringInstrumentDate { get; set; } @@ -20,30 +20,30 @@ public class Charge public DateTime? DeliveredOn { get; set; } - public string Etag { get; set; } + public string Etag { get; set; } = null!; - public string Id { get; set; } + public string Id { get; set; } = null!; - public InsolvencyCase[] InsolvencyCases { get; set; } + public InsolvencyCase[] InsolvencyCases { get; set; } = null!; - public Links Links { get; set; } + public Links Links { get; set; } = null!; public bool? MoreThanFourPersonsEntitled { get; set; } - public Particular Particular { get; set; } + public Particular Particular { get; set; } = null!; - public PersonEntitled[] PersonsEntitled { get; set; } + public PersonEntitled[] PersonsEntitled { get; set; } = null!; public DateTime? ResolvedOn { get; set; } public DateTime? SatisfiedOn { get; set; } - public ScottishAlterations ScottishAlterations { get; set; } + public ScottishAlterations ScottishAlterations { get; set; } = null!; - public SecuredDetail SecuredDetail { get; set; } + public SecuredDetail SecuredDetail { get; set; } = null!; - public string Status { get; set; } + public string Status { get; set; } = null!; - public Transaction[] Transactions { get; set; } + public Transaction[] Transactions { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Classification.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Classification.cs index dab500b..cead08c 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Classification.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Classification.cs @@ -2,8 +2,8 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class Classification { - public string Description { get; set; } + public string Description { get; set; } = null!; - public string Type { get; set; } + public string Type { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyChargesResourceBuilder.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyChargesResourceBuilder.cs index 3fdf6e8..4646e90 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyChargesResourceBuilder.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyChargesResourceBuilder.cs @@ -13,7 +13,7 @@ public string Create() return $@"{{ ""etag"" : ""{_charges.Etag}"", ""items"" : [ - {string.Join(",", _charges.Items.Select(GetChargesJson))} + {string.Join(",", (_charges.Items ?? []).Select(GetChargesJson))} ], ""part_satisfied_count"" : {_charges.PartSatisfiedCount}, ""satisfied_count"" : {_charges.SatisfiedCount}, diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFilingHistory.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFilingHistory.cs index 6cc65cc..72d203c 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFilingHistory.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFilingHistory.cs @@ -1,10 +1,10 @@ -namespace CompaniesHouse.Tests.ResourceBuilders +namespace CompaniesHouse.Tests.ResourceBuilders { public class CompanyFilingHistory { - public string HistoryStatus { get; set; } + public string HistoryStatus { get; set; } = null!; - public string ETag { get; set; } + public string ETag { get; set; } = null!; public int TotalCount { get; set; } @@ -12,8 +12,8 @@ public class CompanyFilingHistory public int StartIndex { get; set; } - public FilingHistoryItem[] Items { get; set; } + public FilingHistoryItem[] Items { get; set; } = null!; - public string Kind { get; set; } + public string Kind { get; set; } = null!; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFillingLinks.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFillingLinks.cs index 2c0109a..ce91eea 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFillingLinks.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyFillingLinks.cs @@ -1,9 +1,9 @@ -namespace CompaniesHouse.Tests.ResourceBuilders +namespace CompaniesHouse.Tests.ResourceBuilders { public class CompanyFillingLinks { - public string Self { get; set; } + public string Self { get; set; } = null!; - public string DocumentMetaData { get; set; } + public string DocumentMetaData { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfile.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfile.cs index 70526f4..e3076b7 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfile.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfile.cs @@ -4,19 +4,19 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class CompanyProfile { - public string Type { get; set; } + public string Type { get; set; } = null!; public bool HasBeenLiquidated { get; set; } - public RegisteredOfficeAddress RegisteredOfficeAddress { get; set; } + public RegisteredOfficeAddress RegisteredOfficeAddress { get; set; } = null!; - public Accounts Accounts { get; set; } + public Accounts Accounts { get; set; } = null!; - public AnnualReturn AnnualReturn { get; set; } + public AnnualReturn AnnualReturn { get; set; } = null!; - public string Jurisdiction { get; set; } + public string Jurisdiction { get; set; } = null!; - public string[] SicCodes { get; set; } + public string[] SicCodes { get; set; } = null!; public DateTime DateOfCreation { get; set; } @@ -26,15 +26,15 @@ public class CompanyProfile public DateTime LastFullMembersListDate { get; set; } - public string CompanyName { get; set; } + public string CompanyName { get; set; } = null!; - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; - public string ETag { get; set; } + public string ETag { get; set; } = null!; - public string CompanyStatus { get; set; } + public string CompanyStatus { get; set; } = null!; - public string CompanyStatusDetail { get; set; } + public string CompanyStatusDetail { get; set; } = null!; public bool HasInsolvencyHistory { get; set; } @@ -42,18 +42,18 @@ public class CompanyProfile public bool HasCharges { get; set; } - public PreviousCompanyName[] PreviousCompanyNames { get; set; } + public PreviousCompanyName[] PreviousCompanyNames { get; set; } = null!; - public ConfirmationStatement ConfirmationStatement { get; set; } + public ConfirmationStatement ConfirmationStatement { get; set; } = null!; public bool CanFile { get; set; } - public OfficerSummary OfficerSummary { get; set; } + public OfficerSummary OfficerSummary { get; set; } = null!; public bool RegisteredOfficeIsInDispute { get; set; } - public CompanyProfileLinks Links { get; set; } + public CompanyProfileLinks Links { get; set; } = null!; - public CompanyProfileBranchCompanyDetails BranchCompanyDetails { get; set; } + public CompanyProfileBranchCompanyDetails BranchCompanyDetails { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileBranchCompanyDetails.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileBranchCompanyDetails.cs index 62d2344..0fdd460 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileBranchCompanyDetails.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileBranchCompanyDetails.cs @@ -2,8 +2,8 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class CompanyProfileBranchCompanyDetails { - public string BusinessActivity { get; set; } - public string ParentCompanyName { get; set; } - public string ParentCompanyNumber { get; set; } + public string BusinessActivity { get; set; } = null!; + public string ParentCompanyName { get; set; } = null!; + public string ParentCompanyNumber { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileLinks.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileLinks.cs index d13f6ed..f835056 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileLinks.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileLinks.cs @@ -2,13 +2,13 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class CompanyProfileLinks { - public string Charges { get; set; } - public string FilingHistory { get; set; } - public string Insolvency { get; set; } - public string Officers { get; set; } - public string PersonsWithSignificantControl { get; set; } - public string PersonsWithSignificantControlStatements { get; set; } - public string Registers { get; set; } - public string Self { get; set; } + public string Charges { get; set; } = null!; + public string FilingHistory { get; set; } = null!; + public string Insolvency { get; set; } = null!; + public string Officers { get; set; } = null!; + public string PersonsWithSignificantControl { get; set; } = null!; + public string PersonsWithSignificantControlStatements { get; set; } = null!; + public string Registers { get; set; } = null!; + public string Self { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileResourceBuilder.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileResourceBuilder.cs index 41f6e76..cfdf7fa 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileResourceBuilder.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanyProfileResourceBuilder.cs @@ -32,7 +32,7 @@ public string Create() ""due_on"" : ""{_companyProfile.Accounts.NextAccounts.DueOn:yyyy-MM-dd}"", ""period_end_on"" : ""{_companyProfile.Accounts.NextAccounts.PeriodEndOn:yyyy-MM-dd}"", ""period_start_on"" : ""{_companyProfile.Accounts.NextAccounts.PeriodStartOn:yyyy-MM-dd}"", - ""overdue"" : ""{_companyProfile.Accounts.NextAccounts.Overdue.ToString().ToLower()}"" + ""overdue"" : ""{(_companyProfile.Accounts.NextAccounts?.Overdue ?? false).ToString().ToLower()}"" }}, ""next_due"" : ""{_companyProfile.Accounts.NextDue.ToString("yyyy-MM-dd")}"", ""next_made_up_to"" : ""{_companyProfile.Accounts.NextMadeUpTo.ToString("yyyy-MM-dd")}"", @@ -58,7 +58,7 @@ public string Create() ""last_made_up_to"" : ""{_companyProfile.ConfirmationStatement.LastMadeUpTo:yyyy-MM-dd}"", ""next_due"" : ""{_companyProfile.ConfirmationStatement.NextDue:yyyy-MM-dd}"", ""next_made_up_to"" : ""{_companyProfile.ConfirmationStatement.NextMadeUpTo:yyyy-MM-dd}"", - ""overdue"" : ""{_companyProfile.ConfirmationStatement.Overdue.ToString().ToLower()}"" + ""overdue"" : ""{(_companyProfile.ConfirmationStatement?.Overdue ?? false).ToString().ToLower()}"" }}, ""date_of_creation"" : ""{_companyProfile.DateOfCreation.ToString("yyyy-MM-dd")}"", ""date_of_cessation"" : ""{_companyProfile.DateOfCessation.ToString("yyyy-MM-dd")}"", diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs index 477bb06..7a4b5e9 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs @@ -4,48 +4,48 @@ namespace CompaniesHouse.Tests.ResourceBuilders.CompanySearchResource { public class CompanyDetails { - public string CompanyStatus { get; set; } + public string CompanyStatus { get; set; } = null!; - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; - public string CareOf { get; set; } + public string CareOf { get; set; } = null!; - public string Country { get; set; } + public string Country { get; set; } = null!; - public string Locality { get; set; } + public string Locality { get; set; } = null!; - public string PoBox { get; set; } + public string PoBox { get; set; } = null!; - public string Region { get; set; } + public string Region { get; set; } = null!; - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; - public string CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = null!; - public string CompanyType { get; set; } + public string CompanyType { get; set; } = null!; - public string AddressSnippet { get; set; } + public string AddressSnippet { get; set; } = null!; public DateTime DateOfCessation { get; set; } public DateTime DateOfCreation { get; set; } - public string Description { get; set; } + public string Description { get; set; } = null!; - public string ExternalRegistrationNumber { get; set; } + public string ExternalRegistrationNumber { get; set; } = null!; - public string Kind { get; set; } + public string Kind { get; set; } = null!; - public string LinksSelf { get; set; } + public string LinksSelf { get; set; } = null!; - public string Snippet { get; set; } + public string Snippet { get; set; } = null!; - public string Title { get; set; } + public string Title { get; set; } = null!; - public int[] MatchesSnippet { get; set; } + public int[] MatchesSnippet { get; set; } = null!; - public int[] MatchesTitle { get; set; } + public int[] MatchesTitle { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/ResourceDetails.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/ResourceDetails.cs index 9a9daae..3ff1b92 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/ResourceDetails.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/ResourceDetails.cs @@ -2,11 +2,11 @@ namespace CompaniesHouse.Tests.ResourceBuilders.CompanySearchResource { public class ResourceDetails { - public string ETag { get; set; } + public string ETag { get; set; } = null!; public int ItemsPerPage { get; set; } - public string Kind { get; set; } + public string Kind { get; set; } = null!; public int PageNumber { get; set; } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItem.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItem.cs index 52f5cf7..6b5d42e 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItem.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItem.cs @@ -1,36 +1,36 @@ -using System; +using System; using System.Collections.Generic; namespace CompaniesHouse.Tests.ResourceBuilders { public class FilingHistoryItem { - public string Category { get; set; } + public string Category { get; set; } = null!; - public string Subcategory { get; set; } + public string Subcategory { get; set; } = null!; - public string TransactionId { get; set; } + public string TransactionId { get; set; } = null!; - public string FilingType { get; set; } + public string FilingType { get; set; } = null!; - public string Barcode { get; set; } + public string Barcode { get; set; } = null!; public DateTime DateOfProcessing { get; set; } - public string Description { get; set; } + public string Description { get; set; } = null!; - public Dictionary DescriptionValues { get; set; } + public Dictionary DescriptionValues { get; set; } = null!; public int PageCount { get; set; } public bool PaperFiled { get; set; } - public FilingHistoryItemAnnotation[] Annotations { get; set; } + public FilingHistoryItemAnnotation[] Annotations { get; set; } = null!; - public FilingHistoryItemAssociatedFiling[] AssociatedFilings { get; set; } + public FilingHistoryItemAssociatedFiling[] AssociatedFilings { get; set; } = null!; - public FilingHistoryItemResolution[] Resolutions { get; set; } + public FilingHistoryItemResolution[] Resolutions { get; set; } = null!; - public CompanyFillingLinks Links { get; set; } + public CompanyFillingLinks Links { get; set; } = null!; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAnnotation.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAnnotation.cs index 4f5ad64..ed75d9d 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAnnotation.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAnnotation.cs @@ -1,16 +1,16 @@ -using System; +using System; using System.Collections.Generic; namespace CompaniesHouse.Tests.ResourceBuilders { public class FilingHistoryItemAnnotation { - public string Annotation { get; set; } + public string Annotation { get; set; } = null!; public DateTime DateOfAnnotation { get; set; } - public string Description { get; set; } + public string Description { get; set; } = null!; - public Dictionary DescriptionValues { get; set; } + public Dictionary DescriptionValues { get; set; } = null!; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAssociatedFiling.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAssociatedFiling.cs index a7ef6ee..0e35581 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAssociatedFiling.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemAssociatedFiling.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class FilingHistoryItemAssociatedFiling { - public string FilingType { get; set; } + public string FilingType { get; set; } = null!; public DateTime Date { get; set; } - public string Description { get; set; } + public string Description { get; set; } = null!; - public Dictionary DescriptionValues { get; set; } + public Dictionary DescriptionValues { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemResolution.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemResolution.cs index 0670c6d..7025c8b 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemResolution.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/FilingHistoryItemResolution.cs @@ -1,22 +1,22 @@ -using System; +using System; using System.Collections.Generic; namespace CompaniesHouse.Tests.ResourceBuilders { public class FilingHistoryItemResolution { - public string Category { get; set; } + public string Category { get; set; } = null!; - public string Subcategory { get; set; } + public string Subcategory { get; set; } = null!; - public string Description { get; set; } + public string Description { get; set; } = null!; - public string DocumentId { get; set; } + public string DocumentId { get; set; } = null!; public DateTime DateOfProcessing { get; set; } - public string ResolutionType { get; set; } + public string ResolutionType { get; set; } = null!; - public Dictionary DescriptionValues { get; set; } + public Dictionary DescriptionValues { get; set; } = null!; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCase.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCase.cs index 041575a..6c9215c 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCase.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/InsolvencyCase.cs @@ -2,9 +2,9 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class InsolvencyCase { - public string CaseNumber { get; set; } + public string CaseNumber { get; set; } = null!; - public InsolvencyCaseLinks Links { get; set; } + public InsolvencyCaseLinks Links { get; set; } = null!; public int? TransactionId { get; set; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/LastAccounts.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/LastAccounts.cs index a099bf7..e582cd5 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/LastAccounts.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/LastAccounts.cs @@ -1,10 +1,10 @@ -using System; +using System; namespace CompaniesHouse.Tests.ResourceBuilders { public class LastAccounts { - public string Type { get; set; } + public string Type { get; set; } = null!; public DateTime MadeUpTo { get; set; } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Links.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Links.cs index 63b58cd..4f5fb8a 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Links.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Links.cs @@ -2,6 +2,6 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class Links { - public string Self { get; set; } + public string Self { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficeAddress.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficeAddress.cs index 4b68ee0..8ca276a 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficeAddress.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficeAddress.cs @@ -2,31 +2,31 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class OfficeAddress { - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; - public string Locality { get; set; } + public string Locality { get; set; } = null!; - public string Country { get; set; } + public string Country { get; set; } = null!; - public string PoBox { get; set; } + public string PoBox { get; set; } = null!; - public string Premises { get; set; } + public string Premises { get; set; } = null!; - public string Region { get; set; } + public string Region { get; set; } = null!; - public string Etag { get; set; } + public string Etag { get; set; } = null!; - public string Kind { get; set; } + public string Kind { get; set; } = null!; - public RegisteredOfficeAddressLinks Links { get; set; } + public RegisteredOfficeAddressLinks Links { get; set; } = null!; } public class RegisteredOfficeAddressLinks { - public string Self { get; set; } + public string Self { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Officer.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Officer.cs index 89ecf48..3febeb4 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Officer.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Officer.cs @@ -10,24 +10,24 @@ public class Officer public DateTime ResignedOn { get; set; } - public DateOfBirth DateOfBirth { get; set; } + public DateOfBirth DateOfBirth { get; set; } = null!; - public string Name { get; set; } + public string Name { get; set; } = null!; - public string OfficerRole { get; set; } + public string OfficerRole { get; set; } = null!; - public string Nationality { get; set; } + public string Nationality { get; set; } = null!; - public string Occupation { get; set; } + public string Occupation { get; set; } = null!; - public Address Address { get; set; } + public Address Address { get; set; } = null!; - public string CountryOfResidence { get; set; } + public string CountryOfResidence { get; set; } = null!; - public OfficerFormerName[] FormerNames { get; set; } + public OfficerFormerName[] FormerNames { get; set; } = null!; - public OfficerIdentification Identification { get; set; } + public OfficerIdentification Identification { get; set; } = null!; - public OfficerLinks Links { get; set; } + public OfficerLinks Links { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Address.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Address.cs index 4c64bc9..8d6b5de 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Address.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Address.cs @@ -1,23 +1,23 @@ -namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource +namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource { public class Address { - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; - public string CareOf { get; set; } + public string CareOf { get; set; } = null!; - public string Country { get; set; } + public string Country { get; set; } = null!; - public string Locality { get; set; } + public string Locality { get; set; } = null!; - public string PoBox { get; set; } + public string PoBox { get; set; } = null!; - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; - public string Premises { get; set; } + public string Premises { get; set; } = null!; - public string Region { get; set; } + public string Region { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Item.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Item.cs index ffed98c..bd30d74 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Item.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Item.cs @@ -1,27 +1,27 @@ -namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource +namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource { public class Item { - public string Description { get; set; } + public string Description { get; set; } = null!; - public string Snippet { get; set; } + public string Snippet { get; set; } = null!; - public DateOfBirth DateOfBirth { get; set; } + public DateOfBirth DateOfBirth { get; set; } = null!; - public string AddressSnippet { get; set; } + public string AddressSnippet { get; set; } = null!; - public Address Address { get; set; } + public Address Address { get; set; } = null!; - public string[] DescriptionIdentifiers { get; set; } + public string[] DescriptionIdentifiers { get; set; } = null!; public int AppointmentCount { get; set; } - public Links Links { get; set; } + public Links Links { get; set; } = null!; - public string Title { get; set; } + public string Title { get; set; } = null!; - public string Kind { get; set; } + public string Kind { get; set; } = null!; - public Matches Matches { get; set; } + public Matches Matches { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Links.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Links.cs index 3f76bf1..e57d7fc 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Links.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Links.cs @@ -1,7 +1,7 @@ -namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource +namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource { public class Links { - public string Self { get; set; } + public string Self { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Matches.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Matches.cs index eabdb7f..0b5e4ca 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Matches.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/Matches.cs @@ -1,11 +1,11 @@ -namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource +namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource { public class Matches { - public int[] Snippet { get; set; } + public int[] Snippet { get; set; } = null!; - public int[] Title { get; set; } + public int[] Title { get; set; } = null!; - public int[] AddressSnippet { get; set; } + public int[] AddressSnippet { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/ResourceDetails.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/ResourceDetails.cs index a329604..7f80eb6 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/ResourceDetails.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSearchResource/ResourceDetails.cs @@ -1,4 +1,4 @@ -namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource +namespace CompaniesHouse.Tests.ResourceBuilders.OfficerSearchResource { public class ResourceDetails { @@ -10,8 +10,8 @@ public class ResourceDetails public int TotalResults { get; set; } - public string Kind { get; set; } + public string Kind { get; set; } = null!; - public Item[] Officers { get; set; } + public Item[] Officers { get; set; } = null!; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSummary.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSummary.cs index a629f2c..5c00fff 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSummary.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficerSummary.cs @@ -4,7 +4,7 @@ public class OfficerSummary { public int ActiveCount { get; set; } - public Officer[] Officers { get; set; } + public Officer[] Officers { get; set; } = null!; public int ResignedCount { get; set; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Officers.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Officers.cs index 7837874..a285383 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Officers.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Officers.cs @@ -4,7 +4,7 @@ public class Officers { public int ActiveCount { get; set; } - public Officer[] Items { get; set; } + public Officer[] Items { get; set; } = null!; public int ResignedCount { get; set; } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs index c58de21..76165fb 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/OfficersResourceBuilder.cs @@ -43,7 +43,7 @@ public static string CreateSingle(Officer officer) ""links"" : {{ {selfProperty} ""officer"" : {{ - ""appointments"" : ""{officer.Links.Officer.AppointmentsResource}"" + ""appointments"" : ""{officer.Links?.Officer?.AppointmentsResource}"" }} }}, ""name"" : ""{officer.Name}"", diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Particular.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Particular.cs index 7a356f0..1261729 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Particular.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Particular.cs @@ -10,10 +10,10 @@ public class Particular public bool? ContainsNegativePledge { get; set; } - public string Description { get; set; } + public string Description { get; set; } = null!; public bool? FloatingChargeCoversAll { get; set; } - public string Type { get; set; } + public string Type { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonEntitled.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonEntitled.cs index d9e5565..ab11978 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonEntitled.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonEntitled.cs @@ -2,6 +2,6 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class PersonEntitled { - public string Name { get; set; } + public string Name { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControl.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControl.cs index 1fde053..b27ca9b 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControl.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControl.cs @@ -1,4 +1,4 @@ -using CompaniesHouse.Response.PersonsWithSignificantControl; +using CompaniesHouse.Response.PersonsWithSignificantControl; using System; using CompaniesHouse.Response; using CompaniesHouse.Response.Appointments; @@ -7,30 +7,30 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class PersonWithSignificantControl { - public Address Address { get; set; } + public Address Address { get; set; } = null!; public DateTime CeasedOn { get; set; } - public string CountryOfResidence { get; set; } + public string CountryOfResidence { get; set; } = null!; - public DateOfBirth DateOfBirth { get; set; } + public DateOfBirth DateOfBirth { get; set; } = null!; - public string ETag { get; set; } + public string ETag { get; set; } = null!; public PersonWithSignificantControlKind Kind { get; set; } - public PersonWithSignificantControlLinks Links { get; set; } + public PersonWithSignificantControlLinks Links { get; set; } = null!; - public string Name { get; set; } + public string Name { get; set; } = null!; - public NameElements NameElements { get; set; } + public NameElements NameElements { get; set; } = null!; - public string Nationality { get; set; } + public string Nationality { get; set; } = null!; - public PersonWithSignificantControlNatureOfControl[] NaturesOfControl { get; set; } + public PersonWithSignificantControlNatureOfControl[] NaturesOfControl { get; set; } = null!; public DateTime NotifiedOn { get; set; } - public PersonWithSignificantControlIdentification Identification { get; set; } + public PersonWithSignificantControlIdentification Identification { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlIdentification.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlIdentification.cs index d83c2b7..7bf58bd 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlIdentification.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlIdentification.cs @@ -2,14 +2,14 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class PersonWithSignificantControlIdentification { - public string LegalAuthority { get; set; } + public string LegalAuthority { get; set; } = null!; - public string LegalForm { get; set; } + public string LegalForm { get; set; } = null!; - public string PlaceRegistered { get; set; } + public string PlaceRegistered { get; set; } = null!; - public string RegistrationNumber { get; set; } + public string RegistrationNumber { get; set; } = null!; - public string CountryRegistered { get; set; } + public string CountryRegistered { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlLinks.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlLinks.cs index d36fae1..598ba0b 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlLinks.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonWithSignificantControlLinks.cs @@ -2,8 +2,8 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class PersonWithSignificantControlLinks { - public string Self { get; set; } + public string Self { get; set; } = null!; - public string Statement { get; set; } + public string Statement { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControl.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControl.cs index cf88a29..ad8559e 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControl.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControl.cs @@ -1,10 +1,10 @@ -namespace CompaniesHouse.Tests.ResourceBuilders +namespace CompaniesHouse.Tests.ResourceBuilders { public class PersonsWithSignificantControl { public int? ActiveCount { get; set; } - public PersonWithSignificantControl[] Items { get; set; } + public PersonWithSignificantControl[] Items { get; set; } = null!; public int? CeasedCount { get; set; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/PreviousCompanyName.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/PreviousCompanyName.cs index ec34ab3..9cd144d 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PreviousCompanyName.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PreviousCompanyName.cs @@ -6,7 +6,7 @@ public class PreviousCompanyName { public DateTime CeasedOn { get; set; } - public string Name { get; set; } + public string Name { get; set; } = null!; public DateTime EffectiveFrom { get; set; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/RegisteredOfficeAddress.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/RegisteredOfficeAddress.cs index 3453231..d336737 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/RegisteredOfficeAddress.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/RegisteredOfficeAddress.cs @@ -2,22 +2,22 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class RegisteredOfficeAddress { - public string AddressLine1 { get; set; } + public string AddressLine1 { get; set; } = null!; - public string AddressLine2 { get; set; } + public string AddressLine2 { get; set; } = null!; - public string PostalCode { get; set; } + public string PostalCode { get; set; } = null!; - public string Locality { get; set; } + public string Locality { get; set; } = null!; - public string CareOf { get; set; } + public string CareOf { get; set; } = null!; - public string Country { get; set; } + public string Country { get; set; } = null!; - public string PoBox { get; set; } + public string PoBox { get; set; } = null!; - public string Premises { get; set; } + public string Premises { get; set; } = null!; - public string Region { get; set; } + public string Region { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/SecuredDetail.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/SecuredDetail.cs index 69c1f2e..27d9734 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/SecuredDetail.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/SecuredDetail.cs @@ -2,8 +2,8 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class SecuredDetail { - public string Description { get; set; } + public string Description { get; set; } = null!; - public string Type { get; set; } + public string Type { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/Transaction.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/Transaction.cs index eeec8f4..c343e9e 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Transaction.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Transaction.cs @@ -6,11 +6,11 @@ public class Transaction { public DateTime? DeliveredOn { get; set; } - public string FilingType { get; set; } + public string FilingType { get; set; } = null!; public int? InsolvencyCaseNumber { get; set; } - public TransactionLinks Links { get; set; } + public TransactionLinks Links { get; set; } = null!; public int? TransactionId { get; set; } } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/TransactionLinks.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/TransactionLinks.cs index db428ea..936ddd8 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/TransactionLinks.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/TransactionLinks.cs @@ -2,8 +2,8 @@ namespace CompaniesHouse.Tests.ResourceBuilders { public class TransactionLinks { - public string Filing { get; set; } + public string Filing { get; set; } = null!; - public string InsolvencyCase { get; set; } + public string InsolvencyCase { get; set; } = null!; } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/StubHttpMessageHandler.cs b/tests/CompaniesHouse.Tests/StubHttpMessageHandler.cs index 9291139..7151648 100644 --- a/tests/CompaniesHouse.Tests/StubHttpMessageHandler.cs +++ b/tests/CompaniesHouse.Tests/StubHttpMessageHandler.cs @@ -21,7 +21,10 @@ public StubHttpMessageHandler(Uri catchUri, string response, string mediaType = protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - if (request.RequestUri.GetLeftPart(UriPartial.Path) != _catchUri.GetLeftPart(UriPartial.Path)) throw new Exception("Uri did not match"); + if (request.RequestUri is null || request.RequestUri.GetLeftPart(UriPartial.Path) != _catchUri.GetLeftPart(UriPartial.Path)) + { + throw new Exception("Uri did not match"); + } return Task.FromResult(new HttpResponseMessage { From 01d116394967040b973d0fd0e9467b86ba40fcb6 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 22:24:52 +0100 Subject: [PATCH 31/38] Replace blanket null-forgiving DTO defaults with nullable contracts --- samples/SampleProject/Program.cs | 7 ++- src/CompaniesHouse/Response/Address.cs | 18 +++---- .../Response/Appointments/NameElements.cs | 8 +-- src/CompaniesHouse/Response/Charges/Charge.cs | 2 +- .../CompanyFiling/FilingHistoryItem.cs | 2 +- .../FilingHistoryItemAnnotation.cs | 2 +- .../FilingHistoryItemResolution.cs | 2 +- .../Response/CompanyProfile/Accounts.cs | 6 +-- .../CompanyProfile/BranchCompanyDetails.cs | 6 +-- .../Response/CompanyProfile/CompanyProfile.cs | 22 ++++---- .../CompanyProfile/PreviousCompanyName.cs | 2 +- .../Response/Document/DocumentDownload.cs | 4 +- .../Response/Officers/OfficerFormerName.cs | 4 +- .../Response/Officers/Officers.cs | 2 +- .../AdvancedCompanySearch.cs | 8 +-- .../Search/AdvancedCompanySearch/Company.cs | 8 +-- .../Response/Search/AllSearch/Address.cs | 16 +++--- .../Response/Search/AllSearch/AllSearch.cs | 6 +-- .../Response/Search/AllSearch/Item.cs | 18 +++---- .../Response/Search/AllSearch/Links.cs | 2 +- .../Response/Search/AllSearch/Matches.cs | 6 +-- .../CompaniesAlphabeticallySearch.cs | 6 +-- .../CompaniesAlphabeticallySearch/Company.cs | 10 ++-- .../Response/Search/CompanyProfileLinks.cs | 2 +- .../Response/Search/CompanySearch/Company.cs | 14 ++--- .../Search/CompanySearch/CompanySearch.cs | 6 +-- .../DisqualifiedOfficersSearch/Address.cs | 14 ++--- .../DisqualifiedOfficer.cs | 14 ++--- .../DisqualifiedOfficerSearch.cs | 4 +- .../DisqualifiedOfficersSearch/Match.cs | 6 +-- .../DissolvedCompaniesSearch/Company.cs | 6 +-- .../DissolvedCompaniesSearch.cs | 8 +-- .../PreviousCompanyName.cs | 4 +- src/CompaniesHouse/Response/Search/Links.cs | 2 +- .../Response/Search/OfficerSearch/Address.cs | 18 +++---- .../Response/Search/OfficerSearch/Match.cs | 6 +-- .../Response/Search/OfficerSearch/Officer.cs | 30 +++++++---- .../Search/OfficerSearch/OfficerSearch.cs | 4 +- .../Response/Search/SearchItem.cs | 4 +- .../CompanyProfileTestsValid.cs | 4 +- .../DocumentTests/DocumentDownloadTests.cs | 3 +- .../Tests/OfficerTests/OfficersSchemaTests.cs | 10 ++-- .../AdvancedCompanySearchTests.cs | 8 +-- .../Tests/SearchingTests/AllSearchTests.cs | 8 +-- .../CompaniesAlphabeticalSearchTests.cs | 6 +-- .../SearchingTests/CompanySearchTests.cs | 6 +-- .../DisqualifiedOfficersSearchTests.cs | 6 ++- .../DissolvedCompaniesSearchTests.cs | 9 ++-- .../SearchingTests/OfficersSearchTests.cs | 4 +- ...panyProfileDeserializationScenarioTests.cs | 6 +-- .../OfficersDeserializationScenarioTests.cs | 22 ++++---- ...dFetchCorrespondingCompanyScenarioTests.cs | 3 +- ...rchResponseDeserializationScenarioTests.cs | 54 ++++++++++--------- ...CompaniesHouseCompanyProfileClientTests.cs | 2 +- .../CompaniesHouseDocumentClientTests.cs | 3 +- ...ompaniesHouseCompanyOfficersClientTests.cs | 18 ++++--- ...archClientTestsForAdvancedCompanySearch.cs | 7 +-- ...ntTestsForCompaniesAlphabeticallySearch.cs | 9 ++-- ...sHouseSearchClientTestsForCompanySearch.cs | 52 +++++++++--------- ...hClientTestsForDissolvedCompaniesSearch.cs | 5 +- 60 files changed, 293 insertions(+), 261 deletions(-) diff --git a/samples/SampleProject/Program.cs b/samples/SampleProject/Program.cs index 5ead716..43aa2ab 100644 --- a/samples/SampleProject/Program.cs +++ b/samples/SampleProject/Program.cs @@ -82,7 +82,7 @@ private static void DisplaySearchResults(CompaniesHouseResponse resul } Console.WriteLine($"Companies matching '{query}':"); - foreach (var item in data.Items.OfType()) + foreach (var item in (data.Items ?? []).OfType()) { // CompanyStatus is a string-backed value type — it never throws on an // unrecognised wire value, so we can always describe it safely. @@ -90,11 +90,11 @@ private static void DisplaySearchResults(CompaniesHouseResponse resul } Console.WriteLine($"\nOfficers matching '{query}':"); - foreach (var item in data.Items.OfType()) + foreach (var item in (data.Items ?? []).OfType()) Console.WriteLine($" * {item.Title} — {item.Description}"); Console.WriteLine($"\nDisqualified officers matching '{query}':"); - foreach (var item in data.Items.OfType()) + foreach (var item in (data.Items ?? []).OfType()) Console.WriteLine($" * {item.Title}"); } @@ -153,4 +153,3 @@ private static void DisplayCompanyProfile(CompaniesHouseResponse _ => $"unknown status ({status.Value})", }; } - diff --git a/src/CompaniesHouse/Response/Address.cs b/src/CompaniesHouse/Response/Address.cs index 69c77a0..f48fe02 100644 --- a/src/CompaniesHouse/Response/Address.cs +++ b/src/CompaniesHouse/Response/Address.cs @@ -5,30 +5,30 @@ namespace CompaniesHouse.Response public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } = null!; + public string? AddressLine1 { get; set; } [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } = null!; + public string? AddressLine2 { get; set; } [JsonPropertyName("care_of")] - public string CareOf { get; set; } = null!; + public string? CareOf { get; set; } [JsonPropertyName("country")] - public string Country { get; set; } = null!; + public string? Country { get; set; } [JsonPropertyName("locality")] - public string Locality { get; set; } = null!; + public string? Locality { get; set; } [JsonPropertyName("po_box")] - public string PoBox { get; set; } = null!; + public string? PoBox { get; set; } [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } = null!; + public string? PostalCode { get; set; } [JsonPropertyName("premises")] - public string Premises { get; set; } = null!; + public string? Premises { get; set; } [JsonPropertyName("region")] - public string Region { get; set; } = null!; + public string? Region { get; set; } } } diff --git a/src/CompaniesHouse/Response/Appointments/NameElements.cs b/src/CompaniesHouse/Response/Appointments/NameElements.cs index 2a66f87..4579439 100644 --- a/src/CompaniesHouse/Response/Appointments/NameElements.cs +++ b/src/CompaniesHouse/Response/Appointments/NameElements.cs @@ -5,16 +5,16 @@ namespace CompaniesHouse.Response.Appointments public class NameElements { [JsonPropertyName("title")] - public string Title { get; set; } = null!; + public string? Title { get; set; } [JsonPropertyName("forename")] - public string Forename { get; set; } = null!; + public string? Forename { get; set; } [JsonPropertyName("surname")] - public string Surname { get; set; } = null!; + public string? Surname { get; set; } [JsonPropertyName("other_forenames")] - public string OtherForenames { get; set; } = null!; + public string? OtherForenames { get; set; } } } diff --git a/src/CompaniesHouse/Response/Charges/Charge.cs b/src/CompaniesHouse/Response/Charges/Charge.cs index 6d02f63..94e144d 100644 --- a/src/CompaniesHouse/Response/Charges/Charge.cs +++ b/src/CompaniesHouse/Response/Charges/Charge.cs @@ -19,7 +19,7 @@ public class Charge public int? ChargeNumber { get; set; } [JsonPropertyName("classification")] - public Classification Classification { get; set; } = null!; + public Classification? Classification { get; set; } [JsonPropertyName("covering_instrument_date")] public DateTime? CoveringInstrumentDate { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs index 90ad0ed..193ce08 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs @@ -12,7 +12,7 @@ public class FilingHistoryItem : IDescriptable public FilingCategory Category { get; set; } [JsonPropertyName("subcategory")] - public FilingSubcategory[] Subcategory { get; set; } = null!; + public FilingSubcategory[]? Subcategory { get; set; } [JsonPropertyName("transaction_id")] public string? TransactionId { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs index 22cb3e1..cdd0d44 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs @@ -8,7 +8,7 @@ namespace CompaniesHouse.Response.CompanyFiling public class FilingHistoryItemAnnotation : IDescriptable { [JsonPropertyName("annotation")] - public string Annotation { get; set; } = null!; + public string? Annotation { get; set; } [JsonPropertyName("date")] public DateTime? DateOfAnnotation { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs index 9fcc2ad..36c6bc1 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs @@ -12,7 +12,7 @@ public class FilingHistoryItemResolution : IDescriptable public ResolutionCategory Category { get; set; } [JsonPropertyName("subcategory")] - public FilingSubcategory[] Subcategory { get; set; } = null!; + public FilingSubcategory[]? Subcategory { get; set; } [JsonPropertyName("description")] public string? Description { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs index f60f6a8..71a9dc3 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs @@ -7,13 +7,13 @@ namespace CompaniesHouse.Response.CompanyProfile public class Accounts { [JsonPropertyName("accounting_reference_date")] - public AccountingReferenceDate AccountingReferenceDate { get; set; } = null!; + public AccountingReferenceDate? AccountingReferenceDate { get; set; } [JsonPropertyName("last_accounts")] - public LastAccounts LastAccounts { get; set; } = null!; + public LastAccounts? LastAccounts { get; set; } [JsonPropertyName("next_accounts")] - public NextAccounts NextAccounts { get; set; } = null!; + public NextAccounts? NextAccounts { get; set; } [JsonPropertyName("next_due")] [JsonConverter(typeof(OptionalDateJsonConverter))] diff --git a/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs b/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs index 19179df..ee38013 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/BranchCompanyDetails.cs @@ -5,10 +5,10 @@ namespace CompaniesHouse.Response.CompanyProfile public class BranchCompanyDetails { [JsonPropertyName("business_activity")] - public string BusinessActivity { get; set; } = null!; + public string? BusinessActivity { get; set; } [JsonPropertyName("parent_company_name")] - public string ParentCompanyName { get; set; } = null!; + public string? ParentCompanyName { get; set; } [JsonPropertyName("parent_company_number")] - public string ParentCompanyNumber { get; set; } = null!; + public string? ParentCompanyNumber { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs index b8553fe..9d68426 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs @@ -10,25 +10,25 @@ public class CompanyProfile public CompanyType Type { get; set; } [JsonPropertyName("etag")] - public string ETag { get; set; } = null!; + public string? ETag { get; set; } [JsonPropertyName("accounts")] - public Accounts Accounts { get; set; } = null!; + public Accounts? Accounts { get; set; } [JsonPropertyName("annual_return")] - public AnnualReturn AnnualReturn { get; set; } = null!; + public AnnualReturn? AnnualReturn { get; set; } [JsonPropertyName("confirmation_statement")] - public ConfirmationStatement ConfirmationStatement { get; set; } = null!; + public ConfirmationStatement? ConfirmationStatement { get; set; } [JsonPropertyName("can_file")] public bool? CanFile { get; set; } [JsonPropertyName("company_name")] - public string CompanyName { get; set; } = null!; + public string? CompanyName { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } = null!; + public string? CompanyNumber { get; set; } [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -75,24 +75,24 @@ public class CompanyProfile public DateTime? LastFullMembersListDate { get; set; } [JsonPropertyName("links")] - public CompanyProfileLinks Links { get; set; } = null!; + public CompanyProfileLinks? Links { get; set; } [JsonPropertyName("previous_company_names")] - public PreviousCompanyName[] PreviousCompanyNames { get; set; } = null!; + public PreviousCompanyName[]? PreviousCompanyNames { get; set; } [JsonPropertyName("registered_office_address")] - public Address RegisteredOfficeAddress { get; set; } = null!; + public Address? RegisteredOfficeAddress { get; set; } [JsonPropertyName("registered_office_is_in_dispute")] public bool? RegisteredOfficeIsInDispute { get; set; } [JsonPropertyName("sic_codes")] - public string[] SicCodes { get; set; } = null!; + public string[]? SicCodes { get; set; } [JsonPropertyName("undeliverable_registered_office_address")] public bool? UndeliverableRegisteredOfficeAddress { get; set; } [JsonPropertyName("branch_company_details")] - public BranchCompanyDetails BranchCompanyDetails { get; set; } = null!; + public BranchCompanyDetails? BranchCompanyDetails { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs index b5688e5..4b99c08 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs @@ -9,7 +9,7 @@ namespace CompaniesHouse.Response.CompanyProfile public class PreviousCompanyName { [JsonPropertyName("name")] - public string Name { get; set; } = null!; + public string? Name { get; set; } [JsonPropertyName("ceased_on")] public DateTime CeasedOn { get; set; } diff --git a/src/CompaniesHouse/Response/Document/DocumentDownload.cs b/src/CompaniesHouse/Response/Document/DocumentDownload.cs index 9f9ad12..a7e6fce 100644 --- a/src/CompaniesHouse/Response/Document/DocumentDownload.cs +++ b/src/CompaniesHouse/Response/Document/DocumentDownload.cs @@ -4,8 +4,8 @@ namespace CompaniesHouse.Response.Document { public class DocumentDownload { - public Stream Content { get; set; } = null!; - public string ContentType { get; set; } = null!; + public Stream? Content { get; set; } + public string? ContentType { get; set; } public long? ContentLength { get; set; } } } diff --git a/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs b/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs index 1dd1a75..fd873b6 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerFormerName.cs @@ -5,9 +5,9 @@ namespace CompaniesHouse.Response.Officers public class OfficerFormerName { [JsonPropertyName("forenames")] - public string ForeNames { get; set; } = null!; + public string? ForeNames { get; set; } [JsonPropertyName("surname")] - public string Surname { get; set; } = null!; + public string? Surname { get; set; } } } diff --git a/src/CompaniesHouse/Response/Officers/Officers.cs b/src/CompaniesHouse/Response/Officers/Officers.cs index 11c2cd6..7fe978a 100644 --- a/src/CompaniesHouse/Response/Officers/Officers.cs +++ b/src/CompaniesHouse/Response/Officers/Officers.cs @@ -14,7 +14,7 @@ public class Officers public int? InactiveCount { get; set; } [JsonPropertyName("items")] - public Officer[] Items { get; set; } = null!; + public Officer[]? Items { get; set; } [JsonPropertyName("items_per_page")] public int? ItemsPerPage { get; set; } diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs index f347490..6d82d83 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs @@ -5,18 +5,18 @@ namespace CompaniesHouse.Response.Search.AdvancedCompanySearch public class AdvancedCompanySearch { [JsonPropertyName("etag")] - public string ETag { get; set; } = null!; + public string? ETag { get; set; } [JsonPropertyName("hits")] public int? Hits { get; set; } [JsonPropertyName("items")] - public Company[] Items { get; set; } = null!; + public Company[]? Items { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("top_hit")] - public Company TopHit { get; set; } = null!; + public Company? TopHit { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs index 1245cca..4e9ff35 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs @@ -8,10 +8,10 @@ namespace CompaniesHouse.Response.Search.AdvancedCompanySearch public class Company { [JsonPropertyName("company_name")] - public string CompanyName { get; set; } = null!; + public string? CompanyName { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } = null!; + public string? CompanyNumber { get; set; } [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -30,10 +30,10 @@ public class Company public DateTime? DateOfCreation { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("links")] - public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } = null!; + public global::CompaniesHouse.Response.Search.CompanyProfileLinks? Links { get; set; } [JsonPropertyName("registered_office_address")] public Address? RegisteredOfficeAddress { get; set; } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Address.cs b/src/CompaniesHouse/Response/Search/AllSearch/Address.cs index 5455181..4cc6074 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Address.cs @@ -5,27 +5,27 @@ namespace CompaniesHouse.Response.Search.AllSearch public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } = null!; + public string? AddressLine1 { get; set; } [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } = null!; + public string? AddressLine2 { get; set; } [JsonPropertyName("care_of")] - public string CareOf { get; set; } = null!; + public string? CareOf { get; set; } [JsonPropertyName("country")] - public string Country { get; set; } = null!; + public string? Country { get; set; } [JsonPropertyName("locality")] - public string Locality { get; set; } = null!; + public string? Locality { get; set; } [JsonPropertyName("po_box")] - public string PoBox { get; set; } = null!; + public string? PoBox { get; set; } [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } = null!; + public string? PostalCode { get; set; } [JsonPropertyName("region")] - public string Region { get; set; } = null!; + public string? Region { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs index e92e6a6..66af41a 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs @@ -6,16 +6,16 @@ namespace CompaniesHouse.Response.Search.AllSearch public class AllSearch { [JsonPropertyName("etag")] - public string Etag { get; set; } = null!; + public string? Etag { get; set; } [JsonPropertyName("items")] - public SearchItem[] Items { get; set; } = null!; + public SearchItem[]? Items { get; set; } [JsonPropertyName("items_per_page")] public int? ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Item.cs b/src/CompaniesHouse/Response/Search/AllSearch/Item.cs index 1fc47c0..61b1113 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Item.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Item.cs @@ -2,14 +2,14 @@ namespace CompaniesHouse.Response.Search.AllSearch { public class Item { - public Address address { get; set; } = null!; - public string address_snippet { get; set; } = null!; - public string description { get; set; } = null!; - public string[] description_identifier { get; set; } = null!; - public string kind { get; set; } = null!; - public Links links { get; set; } = null!; - public Matches matches { get; set; } = null!; - public string snippet { get; set; } = null!; - public string title { get; set; } = null!; + public Address? address { get; set; } + public string? address_snippet { get; set; } + public string? description { get; set; } + public string[]? description_identifier { get; set; } + public string? kind { get; set; } + public Links? links { get; set; } + public Matches? matches { get; set; } + public string? snippet { get; set; } + public string? title { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Links.cs b/src/CompaniesHouse/Response/Search/AllSearch/Links.cs index 6dd0da4..d99b587 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Links.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Links.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Search.AllSearch public class Links { [JsonPropertyName("self")] - public string Self { get; set; } = null!; + public string? Self { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs b/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs index 4854b25..58fca43 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/Matches.cs @@ -5,10 +5,10 @@ namespace CompaniesHouse.Response.Search.AllSearch public class Matches { [JsonPropertyName("address_snippet")] - public string[] AddressSnippet { get; set; } = null!; + public string[]? AddressSnippet { get; set; } [JsonPropertyName("snippet")] - public string[] Snippet { get; set; } = null!; + public string[]? Snippet { get; set; } [JsonPropertyName("title")] - public string[] Title { get; set; } = null!; + public string[]? Title { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs index ea318b2..a56bed4 100644 --- a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs +++ b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch public class CompaniesAlphabeticallySearch { [JsonPropertyName("items")] - public Company[] Items { get; set; } = null!; + public Company[]? Items { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("top_hit")] - public Company TopHit { get; set; } = null!; + public Company? TopHit { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs index 7833c51..7c1c041 100644 --- a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs @@ -6,10 +6,10 @@ namespace CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch public class Company { [JsonPropertyName("company_name")] - public string CompanyName { get; set; } = null!; + public string? CompanyName { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } = null!; + public string? CompanyNumber { get; set; } [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -18,12 +18,12 @@ public class Company public CompanyType CompanyType { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("links")] - public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } = null!; + public global::CompaniesHouse.Response.Search.CompanyProfileLinks? Links { get; set; } [JsonPropertyName("ordered_alpha_key_with_id")] - public string OrderedAlphaKeyWithId { get; set; } = null!; + public string? OrderedAlphaKeyWithId { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs b/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs index a8b524b..30d067a 100644 --- a/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs +++ b/src/CompaniesHouse/Response/Search/CompanyProfileLinks.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Search public class CompanyProfileLinks { [JsonPropertyName("company_profile")] - public string CompanyProfile { get; set; } = null!; + public string? CompanyProfile { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs index 60d3da7..b6c18ee 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs @@ -7,13 +7,13 @@ namespace CompaniesHouse.Response.Search.CompanySearch public class Company : SearchItem { [JsonPropertyName("address")] - public Address Address { get; set; } = null!; + public Address? Address { get; set; } [JsonPropertyName("address_snippet")] public string? AddressSnippet { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } = null!; + public string? CompanyNumber { get; set; } [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -29,21 +29,21 @@ public class Company : SearchItem public DateTime? DateOfCreation { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } = null!; + public string? Description { get; set; } [JsonPropertyName("description_identifier")] - public string[] DescriptionIdentifier { get; set; } = null!; + public string[]? DescriptionIdentifier { get; set; } [JsonPropertyName("external_registration_number")] public string? ExternalRegistrationNumber { get; set; } [JsonPropertyName("matches")] - public Matches Matches { get; set; } = null!; + public Matches? Matches { get; set; } [JsonPropertyName("snippet")] - public string Snippet { get; set; } = null!; + public string? Snippet { get; set; } [JsonPropertyName("title")] - public string Title { get; set; } = null!; + public string? Title { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs b/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs index 4998ff2..e7a8667 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/CompanySearch.cs @@ -5,16 +5,16 @@ namespace CompaniesHouse.Response.Search.CompanySearch public class CompanySearch { [JsonPropertyName("etag")] - public string ETag { get; set; } = null!; + public string? ETag { get; set; } [JsonPropertyName("items")] - public Company[] Companies { get; set; } = null!; + public Company[]? Companies { get; set; } [JsonPropertyName("items_per_page")] public int? ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs index 0f59bee..41c3d4e 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Address.cs @@ -5,24 +5,24 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } = null!; + public string? AddressLine1 { get; set; } [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } = null!; + public string? AddressLine2 { get; set; } [JsonPropertyName("country")] - public string Country { get; set; } = null!; + public string? Country { get; set; } [JsonPropertyName("locality")] - public string Locality { get; set; } = null!; + public string? Locality { get; set; } [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } = null!; + public string? PostalCode { get; set; } [JsonPropertyName("premises")] - public string Premises { get; set; } = null!; + public string? Premises { get; set; } [JsonPropertyName("region")] - public string Region { get; set; } = null!; + public string? Region { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs index 2b8d9fe..87f4915 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs @@ -6,27 +6,27 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class DisqualifiedOfficer : SearchItem { [JsonPropertyName("address")] - public Address Address { get; set; } = null!; + public Address? Address { get; set; } [JsonPropertyName("address_snippet")] - public string AddressSnippet { get; set; } = null!; + public string? AddressSnippet { get; set; } [JsonPropertyName("date_of_birth")] public DateTime DateOfBirth { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } = null!; + public string? Description { get; set; } [JsonPropertyName("description_identifiers")] - public string[] DescriptionIdentifiers { get; set; } = null!; + public string[]? DescriptionIdentifiers { get; set; } [JsonPropertyName("matches")] - public Match Matches { get; set; } = null!; + public Match? Matches { get; set; } [JsonPropertyName("snippet")] - public string Snippet { get; set; } = null!; + public string? Snippet { get; set; } [JsonPropertyName("title")] - public string Title { get; set; } = null!; + public string? Title { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs index e2ccadb..ce3fab8 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs @@ -5,13 +5,13 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class DisqualifiedOfficerSearch { [JsonPropertyName("items")] - public DisqualifiedOfficer[] DisqualifiedOfficers { get; set; } = null!; + public DisqualifiedOfficer[]? DisqualifiedOfficers { get; set; } [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs index 0090ef0..19f0379 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/Match.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class Match { [JsonPropertyName("address_snippet")] - public string[] AddressSnippet { get; set; } = null!; + public string[]? AddressSnippet { get; set; } [JsonPropertyName("snippet")] - public string[] Snippet { get; set; } = null!; + public string[]? Snippet { get; set; } [JsonPropertyName("title")] - public string[] Title { get; set; } = null!; + public string[]? Title { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs index 198e79c..9cbc07f 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs @@ -8,10 +8,10 @@ namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch public class Company { [JsonPropertyName("company_name")] - public string CompanyName { get; set; } = null!; + public string? CompanyName { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } = null!; + public string? CompanyNumber { get; set; } [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -24,7 +24,7 @@ public class Company public DateTime? DateOfCreation { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("matched_previous_company_name")] public PreviousCompanyName? MatchedPreviousCompanyName { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs index 1f2794d..c0002dd 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/DissolvedCompaniesSearch.cs @@ -5,18 +5,18 @@ namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch public class DissolvedCompaniesSearch { [JsonPropertyName("etag")] - public string ETag { get; set; } = null!; + public string? ETag { get; set; } [JsonPropertyName("hits")] public int? Hits { get; set; } [JsonPropertyName("items")] - public Company[] Items { get; set; } = null!; + public Company[]? Items { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("top_hit")] - public Company TopHit { get; set; } = null!; + public Company? TopHit { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs index f46a468..4b85107 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/PreviousCompanyName.cs @@ -9,12 +9,12 @@ public class PreviousCompanyName public DateTime? CeasedOn { get; set; } [JsonPropertyName("company_number")] - public string CompanyNumber { get; set; } = null!; + public string? CompanyNumber { get; set; } [JsonPropertyName("effective_from")] public DateTime? EffectiveFrom { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } = null!; + public string? Name { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/Links.cs b/src/CompaniesHouse/Response/Search/Links.cs index f1a1eea..93abc51 100644 --- a/src/CompaniesHouse/Response/Search/Links.cs +++ b/src/CompaniesHouse/Response/Search/Links.cs @@ -5,6 +5,6 @@ namespace CompaniesHouse.Response.Search public class Links { [JsonPropertyName("self")] - public string Self { get; set; } = null!; + public string? Self { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs index 8769539..163469a 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Address.cs @@ -5,30 +5,30 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class Address { [JsonPropertyName("address_line_1")] - public string AddressLine1 { get; set; } = null!; + public string? AddressLine1 { get; set; } [JsonPropertyName("address_line_2")] - public string AddressLine2 { get; set; } = null!; + public string? AddressLine2 { get; set; } [JsonPropertyName("care_of")] - public string CareOf { get; set; } = null!; + public string? CareOf { get; set; } [JsonPropertyName("country")] - public string Country { get; set; } = null!; + public string? Country { get; set; } [JsonPropertyName("locality")] - public string Locality { get; set; } = null!; + public string? Locality { get; set; } [JsonPropertyName("po_box")] - public string PoBox { get; set; } = null!; + public string? PoBox { get; set; } [JsonPropertyName("postal_code")] - public string PostalCode { get; set; } = null!; + public string? PostalCode { get; set; } [JsonPropertyName("premises")] - public string Premises { get; set; } = null!; + public string? Premises { get; set; } [JsonPropertyName("region")] - public string Region { get; set; } = null!; + public string? Region { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs index 39b1cf1..d1a9f9e 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Match.cs @@ -5,12 +5,12 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class Match { [JsonPropertyName("address_snippet")] - public int[] AddressSnippet { get; set; } = null!; + public int[]? AddressSnippet { get; set; } [JsonPropertyName("snippet")] - public int[] Snippet { get; set; } = null!; + public int[]? Snippet { get; set; } [JsonPropertyName("title")] - public int[] Title { get; set; } = null!; + public int[]? Title { get; set; } } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs index 870e865..a36edce 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs @@ -5,35 +5,45 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class Officer : SearchItem { [JsonPropertyName("address")] - public Address Address { get; set; } = null!; + public Address? Address { get; set; } [JsonPropertyName("address_snippet")] - public string AddressSnippet { get; set; } = null!; + public string? AddressSnippet { get; set; } [JsonPropertyName("appointment_count")] public int AppointmentCount { get; set; } [JsonPropertyName("date_of_birth")] - public DateOfBirth DateOfBirth { get; set; } = null!; + public DateOfBirth? DateOfBirth { get; set; } [JsonPropertyName("description")] - public string Description { get; set; } = null!; + public string? Description { get; set; } [JsonPropertyName("description_identifiers")] - public string[] DescriptionIdentifiers { get; set; } = null!; + public string[]? DescriptionIdentifiers { get; set; } [JsonPropertyName("matches")] - public Match Matches { get; set; } = null!; + public Match? Matches { get; set; } [JsonPropertyName("snippet")] - public string Snippet { get; set; } = null!; + public string? Snippet { get; set; } [JsonPropertyName("title")] - public string Title { get; set; } = null!; + public string? Title { get; set; } - public string OfficerId + public string? OfficerId { - get { return Links.Self.Split('/')[2]; } + get + { + var self = Links?.Self; + if (string.IsNullOrWhiteSpace(self)) + { + return null; + } + + var parts = self.Split('/'); + return parts.Length > 2 ? parts[2] : null; + } } } } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs index f5c53f8..cdfb511 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs @@ -6,13 +6,13 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class OfficerSearch { [JsonPropertyName("items")] - public Officer[] Officers { get; set; } = null!; + public Officer[]? Officers { get; set; } [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/SearchItem.cs b/src/CompaniesHouse/Response/Search/SearchItem.cs index e830e85..7165184 100644 --- a/src/CompaniesHouse/Response/Search/SearchItem.cs +++ b/src/CompaniesHouse/Response/Search/SearchItem.cs @@ -8,9 +8,9 @@ namespace CompaniesHouse.Response.Search public abstract class SearchItem { [JsonPropertyName("kind")] - public string Kind { get; set; } = null!; + public string? Kind { get; set; } [JsonPropertyName("links")] - public Links Links { get; set; } = null!; + public Links? Links { get; set; } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs index fde4c40..ed9fb8b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs @@ -34,7 +34,7 @@ public async Task ThenAPlainCompanyProfileIncludesExemptionsAndHasSuperSecurePsc result.Data.CompanyStatus.ShouldBe(CompanyStatus.Active); result.Data.Type.ShouldBe(CompanyType.Plc); result.Data.Links.ShouldNotBeNull(); - result.Data.Links.Exemptions.ShouldNotBeNullOrWhiteSpace(); + result.Data.Links?.Exemptions.ShouldNotBeNullOrWhiteSpace(); result.Data.HasSuperSecurePscs.ShouldBe(false); } @@ -53,7 +53,7 @@ public async Task ThenAForeignCompanyProfileIncludesForeignCompanyDetails() result.Data.ForeignCompanyDetails.AccountingRequirement.TermsOfAccountPublication.ShouldBe( TermsOfAccountPublication.AccountsPublicationDateSuppliedByCompany); result.Data.ForeignCompanyDetails.IsACreditFinancialInstitution.ShouldBe(true); - result.Data.Links.UkEstablishments.ShouldNotBeNullOrWhiteSpace(); + result.Data.Links?.UkEstablishments.ShouldNotBeNullOrWhiteSpace(); } [IntegrationFact] diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs index ac4d976..f20e5a4 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs @@ -20,8 +20,9 @@ public class DocumentDownloadTests : DocumentTestBase [IntegrationFact] public async Task ThenDocumentContentIsNotEmpty() { + _result.Data.Content.ShouldNotBeNull(); using var memoryStream = new MemoryStream(); - await _result.Data.Content.CopyToAsync(memoryStream); + await _result.Data.Content!.CopyToAsync(memoryStream); _result.Data.ContentLength.ShouldNotBeNull(); _result.Data.ContentLength.Value.ShouldBe(memoryStream.Length); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs index c72b6bc..f9f9434 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs @@ -22,7 +22,8 @@ public async Task GetOfficersAsync_DeserializesConfirmedTescoFields() result.Data.Kind.ShouldBe("officer-list"); result.Data.Links?.Self.ShouldBe("/company/00445790/officers"); - var melissaBethell = result.Data.Items.Single(x => x.PersonNumber == "248450070003"); + var items = result.Data.Items ?? []; + var melissaBethell = items.Single(x => x.PersonNumber == "248450070003"); melissaBethell.OfficerRole.ShouldBe(OfficerRole.Director); melissaBethell.OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); melissaBethell.IdentityVerificationDetails.ShouldNotBeNull(); @@ -38,12 +39,15 @@ public async Task GetOfficersAsync_DeserializesCorporateIdentificationAndAppoint var tescoResult = await client.GetOfficersAsync("00445790", pageSize: 100); var informaResult = await client.GetOfficersAsync("03610056", pageSize: 100); - var pre1992Officer = tescoResult.Data.Items.Single( + var tescoItems = tescoResult.Data.Items ?? []; + var informaItems = informaResult.Data.Items ?? []; + + var pre1992Officer = tescoItems.Single( x => x.Links?.Self == "/company/00445790/appointments/MyVEHTFfF_vmr04twNlBb1DmQFY"); pre1992Officer.AppointedBefore.ShouldBe(new DateTime(1991, 06, 07)); pre1992Officer.IsPre1992Appointment.ShouldBe(true); - var corporateSecretary = informaResult.Data.Items.Single( + var corporateSecretary = informaItems.Single( x => x.Links?.Self == "/company/03610056/appointments/4F3DS_j7LgOTlBEE2xIfmM7wGhs"); corporateSecretary.OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); corporateSecretary.Identification.ShouldNotBeNull(); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs index dae3f14..d35ee0a 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs @@ -27,7 +27,7 @@ public async Task ThenCompaniesAreReturned() }); result.Data.ShouldNotBeNull(); - result.Data.Items.ShouldNotBeEmpty(); + (result.Data.Items ?? []).ShouldNotBeEmpty(); } [IntegrationFact] @@ -40,7 +40,7 @@ public async Task ThenCompanySubtypeCanBeUsedAsALiveFilter() }); result.Data.ShouldNotBeNull(); - result.Data.Items.ShouldContain(x => x.CompanySubtype == CompanySubtype.CommunityInterestCompany); + (result.Data.Items ?? []).ShouldContain(x => x.CompanySubtype == CompanySubtype.CommunityInterestCompany); } [IntegrationFact] @@ -55,8 +55,8 @@ public async Task ThenLocationAndSicCodeFiltersCanBeUsedTogether() }); result.Data.ShouldNotBeNull(); - result.Data.Items.ShouldNotBeEmpty(); - result.Data.Items.ShouldContain(x => x.SicCodes != null && x.SicCodes.Contains("62012")); + (result.Data.Items ?? []).ShouldNotBeEmpty(); + (result.Data.Items ?? []).ShouldContain(x => x.SicCodes != null && x.SicCodes.Contains("62012")); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs index 5087c90..c42b44b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs @@ -25,7 +25,7 @@ public async Task ThenItemsAreReturned(string query) { var result = await _client.SearchAllAsync(new SearchAllRequest { Query = query }); - result.Data.Items.ShouldNotBeEmpty(); + (result.Data.Items ?? []).ShouldNotBeEmpty(); } [IntegrationFact] @@ -34,8 +34,8 @@ public async Task ThenPagingAndMixedItemTypesAreReturned() var result = await _client.SearchAllAsync(new SearchAllRequest { Query = "john", ItemsPerPage = 20 }); result.Data.PageNumber.ShouldBe(1); - result.Data.Items.ShouldContain(x => x is Company); - result.Data.Items.ShouldContain(x => x is Officer); + (result.Data.Items ?? []).ShouldContain(x => x is Company); + (result.Data.Items ?? []).ShouldContain(x => x is Officer); } [IntegrationFact] @@ -43,7 +43,7 @@ public async Task ThenCompanySpecificFieldsRoundTripFromSearchAll() { var result = await _client.SearchAllAsync(new SearchAllRequest { Query = "absa uk permanent establishment", ItemsPerPage = 20 }); - var company = result.Data.Items.OfType().Single(x => x.CompanyNumber == "FC040879"); + var company = (result.Data.Items ?? []).OfType().Single(x => x.CompanyNumber == "FC040879"); company.CompanyNumber.ShouldBe("FC040879"); company.AddressSnippet.ShouldBe("Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa"); company.ExternalRegistrationNumber.ShouldBe("198600479406"); diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs index d583d00..1d5ecc6 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs @@ -26,7 +26,7 @@ public async Task ThenCompaniesAreReturned(string query) }); result.Data.ShouldNotBeNull(); - result.Data.Items.ShouldNotBeEmpty(); + (result.Data.Items ?? []).ShouldNotBeEmpty(); } [IntegrationFact] @@ -42,12 +42,12 @@ public async Task ThenAlphabeticalPagingParametersCanBeSent() { Query = "tesco", Size = 5, - SearchAbove = firstPage.Data.Items[^1].OrderedAlphaKeyWithId, + SearchAbove = (firstPage.Data.Items ?? [])[^1].OrderedAlphaKeyWithId, }); firstPage.Data.Kind.ShouldBe("search#alphabetical-search"); secondPage.Data.ShouldNotBeNull(); - secondPage.Data.Items.ShouldNotBeEmpty(); + (secondPage.Data.Items ?? []).ShouldNotBeEmpty(); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs index 4ea41d0..282f117 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs @@ -24,7 +24,7 @@ public async Task ThenCompaniesAreReturned(string query) { var result = await _client.SearchCompanyAsync(new SearchCompanyRequest { Query = query, StartIndex = 0, ItemsPerPage = 100 }); - result.Data.Companies.ShouldNotBeEmpty(); + (result.Data.Companies ?? []).ShouldNotBeEmpty(); } [IntegrationFact] @@ -37,7 +37,7 @@ public async Task ThenForeignCompanyFieldsAreReturned() ItemsPerPage = 20, }); - var company = result.Data.Companies.Single(x => x.CompanyNumber == "FC040879"); + var company = (result.Data.Companies ?? []).Single(x => x.CompanyNumber == "FC040879"); result.Data.PageNumber.ShouldBe(1); company.AddressSnippet.ShouldBe("Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa"); company.ExternalRegistrationNumber.ShouldBe("198600479406"); @@ -55,7 +55,7 @@ public async Task ThenRestrictionsCanBeSentToTheLiveApi() }); result.Data.ShouldNotBeNull(); - result.Data.Companies.ShouldNotBeEmpty(); + (result.Data.Companies ?? []).ShouldNotBeEmpty(); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs index efcae75..a66e798 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs @@ -20,7 +20,7 @@ public async Task ThenDisqualifiedOfficersAreReturned() { var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "Kevin" }); - result.Data.DisqualifiedOfficers.ShouldNotBeEmpty(); + (result.Data.DisqualifiedOfficers ?? []).ShouldNotBeEmpty(); } [IntegrationFact] @@ -29,7 +29,9 @@ public async Task ThenPagingMetadataAndDateOfBirthAreReturned() var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "john", ItemsPerPage = 20 }); result.Data.PageNumber.ShouldBe(1); - result.Data.DisqualifiedOfficers[0].DateOfBirth.Year.ShouldBeGreaterThan(1900); + var officers = result.Data.DisqualifiedOfficers ?? []; + officers.ShouldNotBeEmpty(); + officers[0].DateOfBirth.Year.ShouldBeGreaterThan(1900); } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs index 7ac94dd..194c2b0 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs @@ -27,7 +27,7 @@ public async Task ThenCompaniesAreReturned(string query) }); result.Data.ShouldNotBeNull(); - result.Data.Items.ShouldNotBeEmpty(); + (result.Data.Items ?? []).ShouldNotBeEmpty(); } [IntegrationFact] @@ -42,8 +42,9 @@ public async Task ThenPreviousNameSearchReturnsMatchedPreviousCompanyName() } result.Data.Kind.ShouldBe("search#previous-name-dissolved"); - result.Data.TopHit.MatchedPreviousCompanyName.ShouldNotBeNull(); - result.Data.TopHit.MatchedPreviousCompanyName.Name.ShouldContain("RADIO RENTALS"); + result.Data.TopHit?.MatchedPreviousCompanyName.ShouldNotBeNull(); + result.Data.TopHit?.MatchedPreviousCompanyName?.Name.ShouldNotBeNull(); + result.Data.TopHit?.MatchedPreviousCompanyName?.Name!.ShouldContain("RADIO RENTALS"); } [IntegrationFact] @@ -57,7 +58,7 @@ public async Task ThenAlphabeticalSearchReturnsOrderedAlphaKeys() }); result.Data.Kind.ShouldBe("search#alphabetical-dissolved"); - result.Data.Items.ShouldContain(x => !string.IsNullOrWhiteSpace(x.OrderedAlphaKeyWithId)); + (result.Data.Items ?? []).ShouldContain(x => !string.IsNullOrWhiteSpace(x.OrderedAlphaKeyWithId)); } private Task> SearchPreviousNamesAsync() => diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs index 365af67..5575fce 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs @@ -21,7 +21,7 @@ public async Task ThenOfficersAreReturned() { var result = await _client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Kevin" }); - result.Data.Officers.ShouldNotBeEmpty(); + (result.Data.Officers ?? []).ShouldNotBeEmpty(); } [IntegrationFact] @@ -29,7 +29,7 @@ public async Task ThenLiveOfficerBirthMonthAndPagingMetadataAreReturned() { var result = await _client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Alan Sugar", ItemsPerPage = 20 }); - var officer = result.Data.Officers.First(x => x.Title == "Lord Alan Michael SUGAR" && x.DateOfBirth?.Year == 1947); + var officer = (result.Data.Officers ?? []).First(x => x.Title == "Lord Alan Michael SUGAR" && x.DateOfBirth?.Year == 1947); result.Data.PageNumber.ShouldBe(1); officer.DateOfBirth.ShouldNotBeNull(); officer.DateOfBirth.Month.ShouldBe(3); diff --git a/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs index 6beeb15..435769f 100644 --- a/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs @@ -19,8 +19,8 @@ public void PlainCompanyProfile_DeserializesKnownFields() profile.Type.ShouldBe(CompanyType.Plc); profile.Jurisdiction.ShouldBe(Jurisdiction.EnglandWales); profile.HasSuperSecurePscs.ShouldBe(false); - profile.Links.Exemptions.ShouldBe("/company/00445790/exemptions"); - profile.PreviousCompanyNames.Length.ShouldBe(2); + profile.Links?.Exemptions.ShouldBe("/company/00445790/exemptions"); + profile.PreviousCompanyNames?.Length.ShouldBe(2); profile.SicCodes.ShouldBe(["47110"]); } @@ -42,7 +42,7 @@ public void ForeignCompanyProfile_DeserializesForeignCompanyDetails() profile.ForeignCompanyDetails.Accounts.AccountPeriodTo!.Month.ShouldBe(12); profile.ForeignCompanyDetails.Accounts.MustFileWithin!.Months.ShouldBe("12"); profile.ForeignCompanyDetails.IsACreditFinancialInstitution.ShouldBe(true); - profile.Links.UkEstablishments.ShouldBe("/company/FC040879/uk-establishments"); + profile.Links?.UkEstablishments.ShouldBe("/company/FC040879/uk-establishments"); } [Fact] diff --git a/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs index 4307e92..aae1b5f 100644 --- a/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs @@ -19,11 +19,12 @@ public void OfficerList_DeserializesConfirmedListEnvelopeAndIdentityVerification officers.Kind.ShouldBe("officer-list"); officers.Links?.Self.ShouldBe("/company/00445790/officers"); officers.TotalResults.ShouldBe(74); - officers.Items.Length.ShouldBe(2); - officers.Items[1].OfficerRole.ShouldBe(OfficerRole.Director); - officers.Items[1].PersonNumber.ShouldBe("248450070003"); - officers.Items[1].OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); - officers.Items[1].IdentityVerificationDetails?.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); + var items = officers.Items ?? []; + items.Length.ShouldBe(2); + items[1].OfficerRole.ShouldBe(OfficerRole.Director); + items[1].PersonNumber.ShouldBe("248450070003"); + items[1].OfficerId.ShouldBe("aqrS_F-2zIvSaMNtl1opqDV4-w0"); + items[1].IdentityVerificationDetails?.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); } [Fact] @@ -46,11 +47,12 @@ public void CorporateOfficerList_DeserializesIdentificationType() var officers = JsonSerializer.Deserialize(CorporateOfficerListJson, CompaniesHouseJsonSerializerOptions.Default); officers.ShouldNotBeNull(); - officers.Items.Length.ShouldBe(1); - officers.Items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); - officers.Items[0].Identification.ShouldNotBeNull(); - officers.Items[0].Identification!.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); - officers.Items[0].Identification!.RegistrationNumber.ShouldBe("3849195"); + var items = officers.Items ?? []; + items.Length.ShouldBe(1); + items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); + items[0].Identification.ShouldNotBeNull(); + items[0].Identification!.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); + items[0].Identification!.RegistrationNumber.ShouldBe("3849195"); } private const string OfficerListJson = """ diff --git a/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs index 811d324..ee3ee71 100644 --- a/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs @@ -23,8 +23,9 @@ public async Task RunScenario() var officers = officersSearch.Data.Officers ?? []; var foundOfficer = officers.Single(x => x.DateOfBirth?.Year == 1950 && x.DateOfBirth?.Month == 7); + foundOfficer.OfficerId.ShouldNotBeNullOrWhiteSpace(); - var officerAppointments = await _client.GetAppointmentsAsync(foundOfficer.OfficerId); + var officerAppointments = await _client.GetAppointmentsAsync(foundOfficer.OfficerId!); var appointments = officerAppointments.Data.Items ?? []; var companyNumber = appointments diff --git a/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs index f4ae204..db2db15 100644 --- a/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs +++ b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs @@ -21,9 +21,10 @@ public void SearchAllPayload_DeserializesMixedCompanyAndOfficerItems() payload.ShouldNotBeNull(); payload.PageNumber.ShouldBe(1); payload.TotalResults.ShouldBe(10000); - payload.Items.Length.ShouldBe(3); - payload.Items[0].ShouldBeOfType(); - payload.Items[2].ShouldBeOfType(); + var allItems = payload.Items ?? []; + allItems.Length.ShouldBe(3); + allItems[0].ShouldBeOfType(); + allItems[2].ShouldBeOfType(); } [Fact] @@ -33,11 +34,12 @@ public void CompanySearchPayload_DeserializesAddressSnippetAndExternalRegistrati payload.ShouldNotBeNull(); payload.PageNumber.ShouldBe(1); - payload.Companies.Length.ShouldBe(1); - payload.Companies[0].AddressSnippet.ShouldBe("Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa"); - payload.Companies[0].ExternalRegistrationNumber.ShouldBe("198600479406"); - payload.Companies[0].DescriptionIdentifier.ShouldBe(["first-uk-establishment-opened-on"]); - payload.Companies[0].Matches.Snippet.ShouldBeEmpty(); + var companies = payload.Companies ?? []; + companies.Length.ShouldBe(1); + companies[0].AddressSnippet.ShouldBe("Absa Towers West, 15 Troye Street, Johannesburg, Gauteng 2000, South Africa"); + companies[0].ExternalRegistrationNumber.ShouldBe("198600479406"); + companies[0].DescriptionIdentifier.ShouldBe(["first-uk-establishment-opened-on"]); + companies[0].Matches?.Snippet.ShouldBeEmpty(); } [Fact] @@ -47,11 +49,12 @@ public void OfficerSearchPayload_DeserializesPageNumberAndOptionalDateOfBirth() payload.ShouldNotBeNull(); payload.PageNumber.ShouldBe(1); - payload.Officers.Length.ShouldBe(3); - payload.Officers[0].DateOfBirth.ShouldNotBeNull(); - payload.Officers[0].DateOfBirth.Month.ShouldBe(3); - payload.Officers[0].DateOfBirth.Year.ShouldBe(1947); - payload.Officers[2].DateOfBirth.ShouldBeNull(); + var officers = payload.Officers ?? []; + officers.Length.ShouldBe(3); + officers[0].DateOfBirth.ShouldNotBeNull(); + officers[0].DateOfBirth!.Month.ShouldBe(3); + officers[0].DateOfBirth!.Year.ShouldBe(1947); + officers[2].DateOfBirth.ShouldBeNull(); } [Fact] @@ -60,12 +63,13 @@ public void AdvancedCompanySearchPayload_DeserializesOptionalSubtypeAndSicCodes( var payload = JsonSerializer.Deserialize(AdvancedCompanySearchJson, CompaniesHouseJsonSerializerOptions.Default); payload.ShouldNotBeNull(); - payload.TopHit.CompanySubtype.ShouldBeNull(); - payload.Items[0].RegisteredOfficeAddress.ShouldNotBeNull(); - payload.Items[0].RegisteredOfficeAddress?.AddressLine1.ShouldBeNull(); - payload.Items[0].SicCodes.ShouldBeNull(); - payload.Items[1].CompanySubtype.ShouldBe(CompanySubtype.CommunityInterestCompany); - payload.Items[1].SicCodes.ShouldBe(["86900"]); + payload.TopHit?.CompanySubtype.ShouldBeNull(); + var items = payload.Items ?? []; + items[0].RegisteredOfficeAddress.ShouldNotBeNull(); + items[0].RegisteredOfficeAddress?.AddressLine1.ShouldBeNull(); + items[0].SicCodes.ShouldBeNull(); + items[1].CompanySubtype.ShouldBe(CompanySubtype.CommunityInterestCompany); + items[1].SicCodes.ShouldBe(["86900"]); } [Fact] @@ -76,12 +80,12 @@ public void DissolvedCompaniesPayload_DeserializesSearchTypeSpecificOptionalFiel payload.ShouldNotBeNull(); payload.Kind.ShouldBe("search#previous-name-dissolved"); payload.Hits.ShouldBe(932); - payload.TopHit.OrderedAlphaKeyWithId.ShouldBeNull(); - payload.TopHit.MatchedPreviousCompanyName.ShouldNotBeNull(); - payload.TopHit.MatchedPreviousCompanyName!.Name.ShouldBe("RADIO RENTALS VODAFONE LIMITED"); - payload.TopHit.RegisteredOfficeAddress.ShouldBeNull(); - payload.Items.Single().PreviousCompanyNames.ShouldNotBeNull(); - payload.Items.Single().PreviousCompanyNames?.Length.ShouldBe(3); + payload.TopHit?.OrderedAlphaKeyWithId.ShouldBeNull(); + payload.TopHit?.MatchedPreviousCompanyName.ShouldNotBeNull(); + payload.TopHit?.MatchedPreviousCompanyName?.Name.ShouldBe("RADIO RENTALS VODAFONE LIMITED"); + payload.TopHit?.RegisteredOfficeAddress.ShouldBeNull(); + (payload.Items ?? []).Single().PreviousCompanyNames.ShouldNotBeNull(); + (payload.Items ?? []).Single().PreviousCompanyNames?.Length.ShouldBe(3); } private const string SearchAllJson = """ diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs index aeab7ce..822ed63 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs @@ -67,7 +67,7 @@ public async Task GivenARealisticPayload_WhenGettingACompanyProfile_ThenNewField _result.Data.Type.ShouldBe(CompanyType.Plc); _result.Data.Jurisdiction.ShouldBe(Jurisdiction.EnglandWales); _result.Data.HasSuperSecurePscs.ShouldBe(false); - _result.Data.Links.Exemptions.ShouldBe("/company/00445790/exemptions"); + _result.Data.Links?.Exemptions.ShouldBe("/company/00445790/exemptions"); } [Fact] diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs index 3c62e78..462af15 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs @@ -33,7 +33,8 @@ public async Task InitializeAsync() public async Task ThenDocumentContentIsCorrect() { using var memoryStream = new MemoryStream(); - await _result.Data.Content.CopyToAsync(memoryStream); + _result.Data.Content.ShouldNotBeNull(); + await _result.Data.Content!.CopyToAsync(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); new StreamReader(memoryStream).ReadToEnd().ShouldBe(ExpectedContent); diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs index c72f367..73b7d3e 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs @@ -59,9 +59,10 @@ public async Task GivenARealCapturedOfficerList_WhenGettingOfficers_ThenMissingL result.Data.InactiveCount.ShouldBe(0); result.Data.Links?.Self.ShouldBe("/company/00445790/officers"); result.Data.TotalResults.ShouldBe(74); - result.Data.Items.Length.ShouldBe(2); + var items = result.Data.Items ?? []; + items.Length.ShouldBe(2); - var officer = result.Data.Items[1]; + var officer = items[1]; officer.ETag.ShouldBe("5ad20f5a7c2d801107af20d5f413ab70bc0a3175"); officer.PersonNumber.ShouldBe("248450070003"); officer.IsPre1992Appointment.ShouldBe(false); @@ -92,12 +93,13 @@ public async Task GivenARealCapturedCorporateOfficerList_WhenGettingOfficers_The var result = await client.GetOfficersAsync("03610056", 0, 1); result.Data.ShouldNotBeNull(); - result.Data.Items.Length.ShouldBe(1); - result.Data.Items[0].Identification.ShouldNotBeNull(); - result.Data.Items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); - result.Data.Items[0].Identification!.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); - result.Data.Items[0].Identification!.RegistrationNumber.ShouldBe("3849195"); - result.Data.Items[0].OfficerId.ShouldBe("YwIOmduyS6PW5axJgQQrsTGyRD0"); + var items = result.Data.Items ?? []; + items.Length.ShouldBe(1); + items[0].Identification.ShouldNotBeNull(); + items[0].OfficerRole.ShouldBe(OfficerRole.CorporateSecretary); + items[0].Identification!.IdentificationType.ShouldBe(IdentificationType.UkLimitedCompany); + items[0].Identification!.RegistrationNumber.ShouldBe("3849195"); + items[0].OfficerId.ShouldBe("YwIOmduyS6PW5axJgQQrsTGyRD0"); } private const string RealOfficerListJson = """ diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs index 4c89382..1e876f8 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs @@ -74,14 +74,15 @@ public async Task GivenAResponse_WhenPerformingAnAdvancedCompanySearch_ThenTheTy result.Data.ETag.ShouldBe("etag-advanced"); result.Data.Hits.ShouldBe(1); result.Data.Kind.ShouldBe("search#advanced-search"); - var company = result.Data.Items[0]; + var items = result.Data.Items ?? []; + var company = items[0]; company.CompanyStatus.ShouldBe(CompanyStatus.Active); company.CompanySubtype.ShouldBe(CompanySubtype.CommunityInterestCompany); company.CompanyType.ShouldBe(CompanyType.Ltd); - company.Links.CompanyProfile.ShouldBe("/company/01234567"); + company.Links?.CompanyProfile.ShouldBe("/company/01234567"); company.RegisteredOfficeAddress?.Country.ShouldBe("England"); company.SicCodes.ShouldBe(new[] { "62012", "62020" }); - result.Data.TopHit.CompanyNumber.ShouldBe("01234567"); + result.Data.TopHit?.CompanyNumber.ShouldBe("01234567"); } } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs index 227eb11..4ab92aa 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs @@ -50,13 +50,14 @@ public async Task GivenAResponse_WhenSearchingCompaniesAlphabetically_ThenTheTyp new SearchCompaniesAlphabeticallyRequest { Query = "abc" }); result.Data.Kind.ShouldBe("search#alphabetical-search"); - result.Data.Items.Length.ShouldBe(1); - result.Data.TopHit.CompanyNumber.ShouldBe("01234567"); - var company = result.Data.Items[0]; + var items = result.Data.Items ?? []; + items.Length.ShouldBe(1); + result.Data.TopHit?.CompanyNumber.ShouldBe("01234567"); + var company = items[0]; company.CompanyName.ShouldBe("ABC LIMITED"); company.CompanyStatus.ShouldBe(CompanyStatus.Active); company.CompanyType.ShouldBe(CompanyType.Ltd); - company.Links.CompanyProfile.ShouldBe("/company/01234567"); + company.Links?.CompanyProfile.ShouldBe("/company/01234567"); company.OrderedAlphaKeyWithId.ShouldBe("ABC LIMITED:01234567"); } } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs index f935b5d..51ae6ec 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs @@ -81,19 +81,20 @@ public void ThenTheRootIsCorrect() [Fact] public void ThenTheCompanyWithUnknownDateOfCessationIsReturned() { + var companies = _result.Data.Companies ?? []; var actual = - _result.Data.Companies.First(x => x.CompanyNumber == _companyWithUnknownDateOfCessation.CompanyNumber); + companies.First(x => x.CompanyNumber == _companyWithUnknownDateOfCessation.CompanyNumber); actual.CompanyNumber.ShouldBe(_companyWithUnknownDateOfCessation.CompanyNumber); - actual.Address.AddressLine1.ShouldBe(_companyWithUnknownDateOfCessation.AddressLine1); - actual.Address.AddressLine2.ShouldBe(_companyWithUnknownDateOfCessation.AddressLine2); - actual.Address.CareOf.ShouldBe(_companyWithUnknownDateOfCessation.CareOf); - actual.Address.Country.ShouldBe(_companyWithUnknownDateOfCessation.Country); - actual.Address.Locality.ShouldBe(_companyWithUnknownDateOfCessation.Locality); - actual.Address.PoBox.ShouldBe(_companyWithUnknownDateOfCessation.PoBox); - actual.Address.PostalCode.ShouldBe(_companyWithUnknownDateOfCessation.PostalCode); - actual.Address.Region.ShouldBe(_companyWithUnknownDateOfCessation.Region); + actual.Address?.AddressLine1.ShouldBe(_companyWithUnknownDateOfCessation.AddressLine1); + actual.Address?.AddressLine2.ShouldBe(_companyWithUnknownDateOfCessation.AddressLine2); + actual.Address?.CareOf.ShouldBe(_companyWithUnknownDateOfCessation.CareOf); + actual.Address?.Country.ShouldBe(_companyWithUnknownDateOfCessation.Country); + actual.Address?.Locality.ShouldBe(_companyWithUnknownDateOfCessation.Locality); + actual.Address?.PoBox.ShouldBe(_companyWithUnknownDateOfCessation.PoBox); + actual.Address?.PostalCode.ShouldBe(_companyWithUnknownDateOfCessation.PostalCode); + actual.Address?.Region.ShouldBe(_companyWithUnknownDateOfCessation.Region); actual.AddressSnippet.ShouldBe(_companyWithUnknownDateOfCessation.AddressSnippet); actual.CompanyStatus.ShouldBe(ExpectedCompanyStatus[_companyWithUnknownDateOfCessation.CompanyStatus]); @@ -104,9 +105,9 @@ public void ThenTheCompanyWithUnknownDateOfCessationIsReturned() actual.DescriptionIdentifier.ShouldBe(["incorporated-on"]); actual.ExternalRegistrationNumber.ShouldBe(_companyWithUnknownDateOfCessation.ExternalRegistrationNumber); actual.Kind.ShouldBe(_companyWithUnknownDateOfCessation.Kind); - actual.Links.Self.ShouldBe(_companyWithUnknownDateOfCessation.LinksSelf); - actual.Matches.Snippet.ShouldBe(_companyWithUnknownDateOfCessation.MatchesSnippet); - actual.Matches.Title.ShouldBe(_companyWithUnknownDateOfCessation.MatchesTitle); + actual.Links?.Self.ShouldBe(_companyWithUnknownDateOfCessation.LinksSelf); + actual.Matches?.Snippet.ShouldBe(_companyWithUnknownDateOfCessation.MatchesSnippet); + actual.Matches?.Title.ShouldBe(_companyWithUnknownDateOfCessation.MatchesTitle); actual.Snippet.ShouldBe(_companyWithUnknownDateOfCessation.Snippet); actual.Title.ShouldBe(_companyWithUnknownDateOfCessation.Title); } @@ -114,26 +115,27 @@ public void ThenTheCompanyWithUnknownDateOfCessationIsReturned() [Fact] public void ThenTheNumberOfReturnedCompaniesIsCorrect() { - _result.Data.Companies.Length.ShouldBe(13); + (_result.Data.Companies ?? []).Length.ShouldBe(13); } [Fact] public void ThenTheCompaniesAreCorrect() { + var companies = _result.Data.Companies ?? []; foreach (var companyDetails in _expectedCompanies) { - var actual = _result.Data.Companies.First(x => x.CompanyNumber == companyDetails.CompanyNumber); + var actual = companies.First(x => x.CompanyNumber == companyDetails.CompanyNumber); actual.CompanyNumber.ShouldBe(companyDetails.CompanyNumber); - actual.Address.AddressLine1.ShouldBe(companyDetails.AddressLine1); - actual.Address.AddressLine2.ShouldBe(companyDetails.AddressLine2); - actual.Address.CareOf.ShouldBe(companyDetails.CareOf); - actual.Address.Country.ShouldBe(companyDetails.Country); - actual.Address.Locality.ShouldBe(companyDetails.Locality); - actual.Address.PoBox.ShouldBe(companyDetails.PoBox); - actual.Address.PostalCode.ShouldBe(companyDetails.PostalCode); - actual.Address.Region.ShouldBe(companyDetails.Region); + actual.Address?.AddressLine1.ShouldBe(companyDetails.AddressLine1); + actual.Address?.AddressLine2.ShouldBe(companyDetails.AddressLine2); + actual.Address?.CareOf.ShouldBe(companyDetails.CareOf); + actual.Address?.Country.ShouldBe(companyDetails.Country); + actual.Address?.Locality.ShouldBe(companyDetails.Locality); + actual.Address?.PoBox.ShouldBe(companyDetails.PoBox); + actual.Address?.PostalCode.ShouldBe(companyDetails.PostalCode); + actual.Address?.Region.ShouldBe(companyDetails.Region); actual.AddressSnippet.ShouldBe(companyDetails.AddressSnippet); actual.CompanyStatus.ShouldBe(ExpectedCompanyStatus[companyDetails.CompanyStatus ?? ""]); @@ -144,9 +146,9 @@ public void ThenTheCompaniesAreCorrect() actual.DescriptionIdentifier.ShouldBe(["incorporated-on"]); actual.ExternalRegistrationNumber.ShouldBe(companyDetails.ExternalRegistrationNumber); actual.Kind.ShouldBe(companyDetails.Kind); - actual.Links.Self.ShouldBe(companyDetails.LinksSelf); - actual.Matches.Snippet.ShouldBe(companyDetails.MatchesSnippet); - actual.Matches.Title.ShouldBe(companyDetails.MatchesTitle); + actual.Links?.Self.ShouldBe(companyDetails.LinksSelf); + actual.Matches?.Snippet.ShouldBe(companyDetails.MatchesSnippet); + actual.Matches?.Title.ShouldBe(companyDetails.MatchesTitle); actual.Snippet.ShouldBe(companyDetails.Snippet); actual.Title.ShouldBe(companyDetails.Title); } diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs index a5655ad..6bdc7ac 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs @@ -94,13 +94,14 @@ public async Task GivenAResponse_WhenSearchingDissolvedCompanies_ThenTheTypedPay result.Data.ETag.ShouldBe("etag-1"); result.Data.Hits.ShouldBe(2); result.Data.Kind.ShouldBe("search#dissolved"); - var company = result.Data.Items[0]; + var items = result.Data.Items ?? []; + var company = items[0]; company.CompanyStatus.ShouldBe(CompanyStatus.Dissolved); company.DateOfCessation.ShouldBe(new DateTime(2023, 01, 20)); company.MatchedPreviousCompanyName?.Name.ShouldBe("OLD ABC LIMITED"); company.PreviousCompanyNames?[0].CompanyNumber.ShouldBe("01234567"); company.RegisteredOfficeAddress?.Locality.ShouldBe("Cardiff"); - result.Data.TopHit.OrderedAlphaKeyWithId.ShouldBe("ABC DISSOLVED LIMITED:01234567"); + result.Data.TopHit?.OrderedAlphaKeyWithId.ShouldBe("ABC DISSOLVED LIMITED:01234567"); } } } From 9402ed90850386b50f192b8b373f2132cb0e88ff Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 23:25:46 +0100 Subject: [PATCH 32/38] Vendor OpenAPI specs and tighten required nullability --- AGENTS.md | 7 +- spec/swagger.json | 197 ++ .../swagger-2.0/models/errors.json | 107 ++ .../swagger-2.0/models/filings.json | 48 + .../swagger-2.0/models/genericModels.json | 17 + .../swagger-2.0/models/insolvency.json | 539 ++++++ .../swagger-2.0/models/officerChanges.json | 306 ++++ .../models/registeredOfficeAddress.json | 126 ++ .../swagger-2.0/spec/charges.json | 460 +++++ .../swagger-2.0/spec/companyAddress.json | 255 +++ .../swagger-2.0/spec/companyOfficerList.json | 541 ++++++ .../swagger-2.0/spec/companyProfile.json | 822 +++++++++ .../swagger-2.0/spec/companyRegisters.json | 454 +++++ .../spec/companyUKEstablishments.json | 125 ++ .../swagger-2.0/spec/disqualifications.json | 408 +++++ .../swagger-2.0/spec/errorModel.json | 68 + .../swagger-2.0/spec/exemptions.json | 241 +++ .../swagger-2.0/spec/filingHistory.json | 348 ++++ .../swagger-2.0/spec/insolvency.json | 1316 ++++++++++++++ .../spec/officerAppointmentList.json | 452 +++++ .../swagger-2.0/spec/psc.json | 538 ++++++ .../swagger-2.0/spec/pscModels.json | 1598 +++++++++++++++++ .../swagger-2.0/spec/pscNotificationList.json | 414 +++++ .../swagger-2.0/spec/search-companies.json | 688 +++++++ .../swagger-2.0/spec/search.json | 986 ++++++++++ .../Response/CompanyProfile/Accounts.cs | 6 +- .../Response/CompanyProfile/CompanyProfile.cs | 8 +- .../CompanyProfile/CompanyProfileLinks.cs | 2 +- .../CompanyProfile/PreviousCompanyName.cs | 2 +- src/CompaniesHouse/Response/DateOfBirth.cs | 4 +- .../Response/Officers/Officer.cs | 6 +- .../Response/Officers/OfficerDateOfBirth.cs | 4 +- .../Response/Officers/Officers.cs | 14 +- .../AdvancedCompanySearch.cs | 8 +- .../Search/AdvancedCompanySearch/Company.cs | 8 +- .../CompaniesAlphabeticallySearch/Company.cs | 6 +- .../Response/Search/CompanySearch/Company.cs | 10 +- .../DisqualifiedOfficer.cs | 8 +- .../DisqualifiedOfficerSearch.cs | 2 +- .../DissolvedCompaniesSearch/Company.cs | 10 +- .../Response/Search/OfficerSearch/Officer.cs | 8 +- .../Search/OfficerSearch/OfficerSearch.cs | 2 +- swagger.json | 195 -- 43 files changed, 11113 insertions(+), 251 deletions(-) create mode 100644 spec/swagger.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/errors.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/filings.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/genericModels.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/insolvency.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/officerChanges.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/registeredOfficeAddress.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/errorModel.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscModels.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json create mode 100644 spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json delete mode 100644 swagger.json diff --git a/AGENTS.md b/AGENTS.md index 8604806..8c7862f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,11 +77,16 @@ tests/ CompaniesHouse.Extensions.*.Tests/ DI tests samples/SampleProject/ runnable usage sample external/api-enumerations/ (planned) git submodule -swagger.json partial CH OpenAPI 2.0 spec +spec/swagger.json local CH OpenAPI 2.0 entrypoint +spec/upstream/developer-specs.company-information.service.gov.uk/... vendored $ref specs CompaniesHouse.slnx solution (XML .slnx format) .plans/ the work breakdown (read this) ``` +`spec/swagger.json` is now the root local spec file. Its `$ref` graph is +rewritten to local paths under `spec/upstream/` so spec-driven work (for +example, model nullability decisions) can run offline and deterministically. + ## Conventions - **File-scoped namespaces**, `ImplicitUsings` enabled, `LangVersion` latest. diff --git a/spec/swagger.json b/spec/swagger.json new file mode 100644 index 0000000..9728af3 --- /dev/null +++ b/spec/swagger.json @@ -0,0 +1,197 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Companies House Public Data API", + "description": "An API suite providing read only access to search and retrieve public company data" + }, + "host": "api.company-information.service.gov.uk", + "schemes": [ + "https", + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [{ + "name": "registeredOfficeAddress", + "description": "Registered office address" + }, + { + "name": "companyProfile", + "description": "Company profile" + }, + { + "name": "search", + "description": "Search" + }, + { + "name": "officers", + "description": "Officers" + }, + { + "name": "registers", + "description": "Registers" + }, + { + "name": "charges", + "description": "Charges" + }, + { + "name": "filingHistory", + "description": "Filing history" + }, + { + "name": "insolvency", + "description": "Insolvency" + }, + { + "name": "exemptions", + "description": "Exemptions" + }, + { + "name": "officerDisqualifications", + "description": "Officer disqualifications" + }, + { + "name": "officerAppointments", + "description": "Officer appointments" + }, + { + "name": "UKEstablishments", + "description": "UK Establishments" + }, + { + "name": "personsWithSignificantControl", + "description": "Persons with significant control" + }, + { + "name": "pscDiscrepancies", + "description": "PSC discrepancies" + }, + { + "name": "personsWithSignificantControlNotifications", + "description": "Persons with significant control notifications" + } + ], + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "security": [{ + "api_key": [] + } + ], + "paths": { + "/company/{companyNumber}/registered-office-address": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json#/getCompanyAddress" + }, + "/company/{companyNumber}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json" + }, + "/search": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchAll" + }, + "/search/companies": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchCompanies" + }, + "/search/officers": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchOfficers" + }, + "/search/disqualified-officers": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchDisqualified-officers" + }, + "/dissolved-search/companies": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchDissolved" + }, + "/alphabetical-search/companies": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAlphabetic" + }, + "/advanced-search/companies": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAdvanced" + }, + "/company/{company_number}/officers": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/listCompanyOfficers" + }, + "/company/{company_number}/appointments/{appointment_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/getCompanyOfficerAppointment" + }, + "/company/{company_number}/registers": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json" + }, + "/company/{company_number}/filing-history/{transaction_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/getFilingHistory" + }, + "/company/{company_number}/filing-history": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/listFilingHistory" + }, + "/company/{company_number}/exemptions": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json" + }, + "/disqualified-officers/natural/{officer_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getNatural" + }, + "/disqualified-officers/corporate/{officer_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getCorporate" + }, + "/officers/{officer_id}/appointments": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json" + }, + "/company/{company_number}/charges": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeList" + }, + "/company/{company_number}/charges/{charge_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeDetails" + }, + "/company/{company_number}/insolvency": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json#/insolvencyCase" + }, + "/company/{company_number}/uk-establishments": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json" + }, + "/company/{company_number}/persons-with-significant-control": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSC" + }, + "/company/{company_number}/persons-with-significant-control/individual/{notification_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualPSC" + }, + "/company/{company_number}/persons-with-significant-control/individual-beneficial-owner/{notification_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualBO" + }, + "/company/{company_number}/persons-with-significant-control/corporate-entity/{notification_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityPSC" + }, + "/company/{company_number}/persons-with-significant-control/corporate-entity-beneficial-owner/{notification_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityBO" + }, + "/company/{company_number}/persons-with-significant-control/legal-person/{notification_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonPSC" + }, + "/company/{company_number}/persons-with-significant-control/legal-person-beneficial-owner/{notification_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonBO" + }, + "/company/{company_number}/persons-with-significant-control-statements": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSCStatements" + }, + "/company/{company_number}/persons-with-significant-control-statements/{statement_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getPSCStatement" + }, + "/company/{company_number}/persons-with-significant-control/super-secure/{super_secure_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecurePSC" + }, + "/company/{company_number}/persons-with-significant-control/super-secure-beneficial-owner/{super_secure_id}": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecureBO" + }, + "/company/{company_number}/persons-with-significant-control/{psc_id}/notifications": { + "$ref": "upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json" + } + } +} + + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/errors.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/errors.json new file mode 100644 index 0000000..91e35fb --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/errors.json @@ -0,0 +1,107 @@ +{ + "definitions": { + "apiError": { + "title": "apiError", + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of error.", + "enum": [ + "ch:validation", + "ch:service" + ], + "readOnly": true + }, + "error": { + "type": "string", + "description": "The enumerated error being returned. See github for valid error enumeration types.", + "readOnly": true + }, + "location_type": { + "type": "string", + "description": "Describes the type of location returned so that it may be parsed appropriately.", + "enum": [ + "json-path", + "query-parameter" + ], + "readOnly": true + }, + "location": { + "type": "string", + "description": "The location in the submitted request in which the error relates. This parameter is only provided when errors[].type is set to \"ch:validation\".", + "readOnly": true + }, + "error_values": { + "type": "object", + "description": "A collection of argument name and value pairs which, when substituted into the error string, provide the full description of the error. As many name/value pairs as necessary to complete the error description are returned. See example above.", + "additionalProperties": { + "type": "string", + "readOnly": true, + "description": "key / value string pair." + }, + "readOnly": true + } + } + }, + "apiErrors": { + "title": "apiErrors", + "properties": { + "errors": { + "type": "array", + "description": "List of errors.", + "items": { + "$ref": "errors.json#/definitions/apiError" + }, + "readOnly": true + } + + } + }, + "validationStatus": { + "title": "validationStatus", + "type": "object", + "required": [ + "is_valid" + ], + "allOf": [{ + "$ref": "errors.json#/definitions/apiErrors" + }, + { + "type": "object", + "properties": { + "is_valid": { + "type": "boolean", + "description": "Indicates whether the resource is valid in its current state. If `false` the resource is invalid and `ch:validation` errors will be included in the `errors` array.", + "readOnly": true + } + } + } + ] + + }, + "companyValidation": { + "title": "companyValidation", + "type": "object", + "required": [ + "eligibility_status_code" + ], + "allOf": [{ + "type": "object", + "properties": { + "eligibility_status_code": { + "type": "string", + "enum": ["INVALID_NO_REGISTERED_EMAIL_ADDRESS_EXISTS", "INVALID_COMPANY_STATUS", "INVALID_COMPANY_TYPE", "COMPANY_NOT_FOUND", "COMPANY_VALID_FOR_SERVICE"], + "readOnly": true + } + } + } + ] + + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/filings.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/filings.json new file mode 100644 index 0000000..8672ae9 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/filings.json @@ -0,0 +1,48 @@ +{ + "definitions": { + "filing": { + "title": "Filing", + "description": "Filing resource", + "required": [ + "company_number", + "description_identifier", + "description", + "kind", + "description_values", + "data" + ], + "properties": { + "company_number": { + "type": "string", + "description": "The company registration / incorporation number of the company." + }, + "description_identifier": { + "type": "string", + "description": "An array of enumeration types that make up the description." + }, + "description": { + "type": "string", + "description": "Description of the filing." + }, + "kind": { + "type": "string", + "description": "Type of filing." + }, + "description_values": { + "description": "Data required for the filing description.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "data": { + "description": "Data for the filing.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/genericModels.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/genericModels.json new file mode 100644 index 0000000..ccd3eeb --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/genericModels.json @@ -0,0 +1,17 @@ +{ + "definitions": { + "selfLink": { + "required": [ + "self" + ], + "properties": { + "self": { + "description": "URL to this resource.", + "readOnly": true, + "type": "string", + "format": "uri" + } + } + } + } +} \ No newline at end of file diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/insolvency.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/insolvency.json new file mode 100644 index 0000000..0147d0c --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/insolvency.json @@ -0,0 +1,539 @@ +{ + "definitions": { + "insolvencyResourceWritable": { + "title": "writeable insolvency", + "type": "object", + "required": [ + "company_number", + "company_name", + "case_type" + ], + "properties": { + "company_number": { + "type": "string" + }, + "company_name": { + "type": "string" + }, + "case_type": { + "type": "string", + "enum": [ + "creditors-voluntary-liquidation" + ] + } + } + }, + + "createdInsolvencyResource": { + "title": "CreatedInsolvency", + "type": "object", + "properties": { + "company_number": { + "type": "string" + }, + "case_type": { + "type": "string", + "enum": [ + "creditors-voluntary-liquidation" + ] + }, + "etag": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "insolvency-resource#insolvency-resource" + ] + }, + "company_name": { + "type": "string" + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri", + "example": "/transactions/{transaction_id}/insolvency" + }, + "transaction": { + "type": "string", + "format": "uri", + "example": "/transactions/{transaction_id}" + }, + "validation_status": { + "type": "string", + "format": "uri", + "example": "/transactions/{transaction_id}/insolvency/validation-status" + } + } + } + } + }, + "practitionerWritable": { + "title": "writeable practitioner", + "type": "object", + "required": [ + "first_name", + "last_name", + "ip_code", + "email", + "telephone_number", + "address" + ], + "properties": { + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "ip_code": { + "type": "string" + }, + "address": { + "type": "object", + "$ref": "insolvency.json#/definitions/address" + }, + "role": { + "type": "string", + "enum": [ + "final-liquidator", + "receiver", + "receiver-manager", + "proposed-liquidator", + "provisional-liquidator", + "administrative-receiver", + "practitioner", + "interim-liquidator" + ] + }, + "email": { + "type": "string", + "format": "email", + "description": "At least one of email or telephone_number must be supplied." + }, + "telephone_number": { + "type": "string", + "description": "At least one of email or telephone_number must be supplied." + } + } + }, + "practitioner": { + "title": "practitioner", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/practitionerWritable" + }, + {"properties": { + "appointed_on": { + "type": "string", + "format": "date" + }, + "etag": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "insolvency-resource#liquidator" + ] + }, + "links": { + "type": "object", + "$ref": "genericModels.json#/definitions/selfLink" + } + } + } + + ] + }, + "allPractitioners": { + "title": "allPractitioners", + "type": "array", + "items": { + "$ref": "#/definitions/practitioner" + } + }, + + "address": { + "title": "address", + "type": "object", + "required": [ + "premises", + "address_line_1", + "locality", + "postal_code" + ], + "properties": { + "premises": { + "type": "string" + }, + "address_line_1": { + "type": "string" + }, + "address_line_2": { + "type": "string" + }, + "country": { + "type": "string" + }, + "locality": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "po_box": { + "type": "string" + } + } + }, + "appointment": { + "title": "appointment", + "type": "object", + "required": [ + "appointed_on", + "made_by" + ], + "properties": { + "appointed_on": { + "type": "string", + "format": "date" + }, + "made_by": { + "type": "string", + "enum": [ + "creditors" + ] + } + } + }, + "practitionerAppointment": { + "title": "practitionerAppointment", + "type": "object", + "properties": { + "appointed_on": { + "type": "string", + "format": "date" + }, + "made_by": { + "type": "string", + "enum": [ + "creditors" + ] + }, + "links": { + "type": "object", + "$ref": "genericModels.json#/definitions/selfLink" + } + } + }, + "createdAttachment": { + "title": "created attachment", + "type": "object", + "properties": { + "attachment_type": { + "type": "string", + "enum": [ + "resolution", + "statement-of-affairs-director", + "statement-of-concurrence", + "progress-report" + ] + }, + "file": { + "type": "object", + "description": "The file name, size and content type", + "items": { + "$ref": "insolvency.json#/definitions/file" + } + }, + "etag": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "insolvency-resources#attachment" + ] + }, + "status": { + "type": "string" + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri", + "description": "URL to this resource", + "example": "/transactions/010276-506416-629750/insolvency/attachments/b303f91d-bc28-469a-b325-6c9030eec26f" + }, + "download": { + "type": "string", + "format": "uri", + "description": "URL to download the file", + "example": "/transactions/010276-506416-629750/insolvency/attachments/b303f91d-bc28-469a-b325-6c9030eec26f/download" + } + } + } + } + }, + "attachmentWriteable": { + "title": "writeable attachment", + "type": "object", + "required": [ + "attachment_type", + "file" + ], + "content": "multipart/formdata", + "properties": { + "attachment_type": { + "type": "string", + "enum": [ + "resolution", + "statement-of-affairs-director", + "statement-of-concurrence", + "progress-report" + ] + }, + "file": { + "type": "array", + "items":{ + "type": "string", + "format": "binary", + "description": "Files attached in request can be a maximum of 4MB in size" + } + } + } + }, + "file": { + "title": "file", + "required": [ + "name", + "size", + "content_type" + ], + "properties": { + "name": { + "type": "string", + "description": "The file name" + }, + "size": { + "type": "string", + "description": "The size of the file" + }, + "content_type": { + "type": "string", + "description": "The media type being consumed", + "enum": [ + "application/pdf" + ] + } + } + }, + "downloadedAttachment": { + "title": "attachment download", + "required": [ + "content_type" + ], + "properties": { + "content_type": { + "type": "string", + "format": "binary", + "description": "The media type being consumed", + "enum": [ + "application/pdf" + ] + } + } + }, + "resolutionResourceWriteable": { + "title": "writeable resolution", + "required": [ + "date_of_resolution", + "attachments" + ], + "properties": { + "date_of_resolution": { + "type": "string", + "format": "date" + }, + "attachments": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "Resolution": { + "title": "Resolution", + "required": [ + "date_of_resolution", + "attachments", + "etag", + "kind", + "links" + ], + "type":"object", + "properties": { + "date_of_resolution": { + "type": "string", + "format":"date" + }, + "attachments": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "etag": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "insolvency-resource#resolution" + ] + }, + "links": { + "type": "object", + "$ref": "genericModels.json#/definitions/selfLink" + } + } + }, + "statementOfAffairsWriteable": { + "title": "writeable statement of affairs", + "required": [ + "statement_date", + "attachments" + ], + "properties": { + "statement_date": { + "type": "string", + "format": "date" + }, + "attachments": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "statementOfAffairs": { + "title": "Statement Of Affairs", + "required": [ + "statement_date", + "attachments", + "etag", + "kind", + "links" + ], + "type":"object", + "properties": { + "statement_date": { + "type": "string", + "format":"date" + }, + "attachments": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "etag": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "insolvency-resource#statement-of-affairs" + ] + }, + "links": { + "type": "object", + "$ref": "genericModels.json#/definitions/selfLink" + } + } + }, + "progressReportWriteable": { + "title": "writeable progress report", + "required": [ + "from_date", + "to_date", + "attachments" + ], + "properties": { + "from_date": { + "type": "string", + "format": "date" + }, + "to_date": { + "type": "string", + "format": "date" + }, + "attachments": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "progressReport": { + "title": "Progress Report", + "required": [ + "from_date", + "to_date", + "attachments", + "etag", + "kind", + "links" + ], + "type":"object", + "properties": { + "from_date": { + "type": "string", + "format":"date" + }, + "to_date": { + "type": "string", + "format":"date" + }, + "attachments": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "etag": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "insolvency-resource#progress-report" + ] + }, + "links": { + "type": "object", + "$ref": "genericModels.json#/definitions/selfLink" + } + } + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/officerChanges.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/officerChanges.json new file mode 100644 index 0000000..253a431 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/officerChanges.json @@ -0,0 +1,306 @@ +{ + "definitions": { + "officerChange": { + "title": "officerChange", + "required": [ + "etag", + "kind", + "links" + ], + "properties": { + "etag": { + "type": "string", + "description": "The ETag of the resource.", + "readOnly": true + }, + "kind": { + "type": "string", + "description": "The type of resource.", + "enum": [ + "officer-change#officer-change" + ], + "readOnly": true + }, + "reference_appointment_id": { + "type": "string", + "description": "Required for officer change and termination. The id of the current company officer appointment resource being changed or terminated (`/company/{company_number}/appointments/{officer_appointment_id}`) on the public register." + }, + "reference_etag": { + "type": "string", + "description": "The latest etag read from the current company officer appointment resource (`/company/{company_number}/officer/{officer_id}`) on the public register. If this reference etag does not match the current register the request will be rejected." + }, + "address": { + "description": "The correspondence address of the officer. Required for officer appointment.", + "items": { + "$ref": "officerChanges.json#/definitions/address" + }, + "type": "object" + }, + "appointed_on": { + "description": "The date on which the officer was appointed. Required for officer appointment.", + "type": "string", + "format": "date" + }, + "country_of_residence": { + "description": "The officer's country of residence. Required for officer appointment.", + "type": "string" + }, + "date_of_birth": { + "description": "Details of director date of birth. Required for officer appointment.", + "items": { + "$ref": "officerChanges.json#/definitions/dateOfBirth" + }, + "type": "object" + }, + "links": { + "description": "Links to other resources associated with this officer change resource.", + "items": { + "$ref": "officerChanges.json#/definitions/itemLinkTypes" + }, + "type": "object" + }, + "name": { + "description": "Corporate or natural officer name. Required for officer appointment.", + "type": "string" + }, + "nationality": { + "description": "The officer's nationality. Required for officer appointment.", + "type": "string" + }, + "occupation": { + "description": "The officer's job title. Required for officer appointment.", + "type": "string" + }, + "officer_role": { + "description": "The officer's role. Required for officer appointment.", + "enum": [ + "cic-manager", + "corporate-director", + "corporate-llp-designated-member", + "corporate-llp-member", + "corporate-manager-of-an-eeig", + "corporate-member-of-a-management-organ", + "corporate-member-of-a-supervisory-organ", + "corporate-member-of-an-administrative-organ", + "corporate-nominee-director", + "corporate-nominee-secretary", + "corporate-secretary", + "director", + "general-partner-in-a-limited-partnership", + "judicial-factor", + "limited-partner-in-a-limited-partnership", + "llp-designated-member", + "llp-member", + "manager-of-an-eeig", + "member-of-a-management-organ", + "member-of-a-supervisory-organ", + "member-of-an-administrative-organ", + "nominee-director", + "nominee-secretary", + "person-authorised-to-accept", + "person-authorised-to-represent", + "person-authorised-to-represent-and-accept", + "receiver-and-manager", + "secretary" + ], + "type": "string" + }, + "resigned_on": { + "description": "The date on which the officer resigned.", + "type": "string", + "format": "date" + }, + "former_names": { + "description": "Former names for the officer.", + "items": { + "$ref": "officerChanges.json#/definitions/formerNames" + }, + "type": "array" + }, + "identification": { + "description": "Only one from `eea`, `non-eea`, `uk-limited` or `other-corporate-body-or-firm` can be supplied, not multiples of them. Required for officer appointment.", + "items": { + "$ref": "officerChanges.json#/definitions/corporateIdent" + }, + "type": "object" + }, + "identity_verification_details": { + "description": "Information relating to the identity verification of the officer", + "items": { + "$ref": "officerChanges.json#/definitions/identityVerificationDetails" + }, + "type": "object" + } + } + }, + "address": { + "title": "address", + "required": [ + "address_line_1", + "locality" + ], + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "country": { + "description": "The country e.g. United Kingdom.", + "type": "string" + }, + "locality": { + "description": "The locality e.g. London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code e.g. CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region e.g. Surrey.", + "type": "string" + } + } + }, + "itemLinkTypes": { + "title": "itemLinkTypes", + "required": [ + "self", + "validation_status" + ], + "properties": { + "self": { + "description": "Link to this individual company officer appointment resource.", + "type": "string", + "readOnly": true + }, + "validation_status": { + "type": "string", + "description": "The URL of the validation status resource for the resource.", + "readOnly": true + } + } + }, + "formerNames": { + "title": "formerNames", + "properties": { + "forenames": { + "description": "Former forenames of the officer.", + "type": "string" + }, + "surname": { + "description": "Former surnames of the officer.", + "type": "string" + } + } + }, + "corporateIdent": { + "title": "corporateIdent", + "properties": { + "identification_type": { + "description": "The officer's identity type", + "enum": [ + "eea", + "non-eea", + "uk-limited", + "other-corporate-body-or-firm" + ], + "type": "string" + }, + "legal_authority": { + "description": "The legal authority supervising the company.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the company as defined by its country of registration.", + "type": "string" + }, + "place_registered": { + "description": "Place registered.", + "type": "string" + }, + "registration_number": { + "description": "Company registration number.", + "type": "string" + } + } + }, + "dateOfBirth": { + "title": "dateOfBirth", + "properties": { + "day": { + "description": "The day of the date of birth.", + "type": "integer" + }, + "month": { + "description": "The month of date of birth.", + "type": "integer" + }, + "year": { + "description": "The year of date of birth.", + "type": "integer" + } + }, + "required": [ + "month", + "year" + ] + }, + "identityVerificationDetails": { + "title": "identityVerificationDetails", + "properties": { + "anti_money_laundering_supervisory_bodies": { + "description": "The Anti-Money Laundering supervisory bodies that the authorised corporate service provider was registered with when verifying the officer.", + "type": "array", + "items": { + "type": "string" + } + }, + "appointment_verification_end_on": { + "description": "The date on which the identity verification statement was removed for the appointment.", + "type": "string", + "format": "date" + }, + "appointment_verification_statement_due_on": { + "description": "The date by which an identity verification statement must be supplied for the appointment.", + "type": "string", + "format": "date" + }, + "appointment_verification_start_on": { + "description": "The date on which the identity verification statement was supplied for the appointment.", + "type": "string", + "format": "date" + }, + "authorised_corporate_service_provider_name": { + "description": "The name of the authorised corporate service provider that verified the identity of the officer.", + "type": "string" + }, + "identity_verified_on": { + "description": "The date on which the authorised corporate service provider verified the identity of the officer.", + "type": "string", + "format": "date" + }, + "preferred_name": { + "description": "The name provided to the authorised corporate service provider by which the officer prefers to be known.", + "type": "string" + } + } + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/registeredOfficeAddress.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/registeredOfficeAddress.json new file mode 100644 index 0000000..7e17fd2 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/models/registeredOfficeAddress.json @@ -0,0 +1,126 @@ +{ + "definitions": { + "registeredOfficeAddress": { + "title": "registeredOfficeAddress", + "type": "object", + "required": [ + "premises", + "address_line_1", + "locality", + "country", + "accept_appropriate_office_address_statement", + "postal_code" + ], + "properties": { + "etag": { + "type": "string", + "description": "The ETag of the resource.", + "readOnly": true + }, + "kind": { + "type": "string", + "description": "The type of resource.", + "enum": [ + "registered-office-address" + ], + "readOnly": true + }, + "links": { + "type": "object", + "description": "Links to the related resources", + "items": { + "$ref": "genericModels.json#/definitions/selfLink" + }, + "readOnly": true + }, + "premises": { + "type": "string", + "description": "The property name or number." + }, + "address_line_1": { + "type": "string", + "description": "The first line of the address." + }, + "address_line_2": { + "type": "string", + "description": "The second line of the address." + }, + "locality": { + "type": "string", + "description": "The locality e.g London." + }, + "region": { + "type": "string", + "description": "The region e.g Surrey." + }, + "postal_code": { + "type": "string", + "description": "The postal code e.g CF14 3UZ." + }, + "country": { + "type": "string", + "description": "The country.", + "enum": [ + "England", + "Wales", + "Scotland", + "Northern Ireland", + "Great Britain", + "United Kingdom", + "Not specified" + ] + }, + "accept_appropriate_office_address_statement": { + "type": "boolean", + "description": "Setting this to true confirms that the new registered office address is an appropriate address as outlined in section 86(2) of the Companies Act 2006." + } + } + }, + "registeredOfficeAddressChange": { + "title": "registeredOfficeAddressChange", + "type": "object", + "required": [ + "reference_etag" + ], + "allOf": [{ + "$ref": "registeredOfficeAddress.json#/definitions/registeredOfficeAddress" + }, + { + "type": "object", + "properties": { + "reference_etag": { + "type": "string", + "description": "The latest etag read from the current ROA API resource (`/company/{company_number}/registered-office-address`) on the public register. If this reference etag does not match the current register the request will be rejected." + }, + "links": { + "type": "object", + "description": "Links to the related resources", + "items": { + "$ref": "registeredOfficeAddress.json#/definitions/registeredOfficeAddressChangeLinks" + }, + "readOnly": true + } + } + } + ] + }, + "registeredOfficeAddressChangeLinks": { + "title": "registeredOfficeAddressChangeLinks", + "type": "object", + "allOf": [{ + "$ref": "genericModels.json#/definitions/selfLink" + }, + { + "type": "object", + "properties": { + "validation_status": { + "type": "string", + "description": "The URL of the validation status resource for the resource.", + "readOnly": true + } + } + } + ] + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json new file mode 100644 index 0000000..34eedc8 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json @@ -0,0 +1,460 @@ +{ + "chargeList": { + "get": { + "summary": "Charges", + "description": "List of charges for a company.", + "x-operationName": "list", + "tags": [ + "charges" + ], + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number that the charge list is required for.", + "paramType": "path", + "required": true, + "type": "string" + }, + { + "title": "items_per_page", + "description": "The number of charges to return per page.", + "type": "integer", + "paramType": "query", + "required": false + }, + { + "title": "start_index", + "description": "The index into the entire result set that this result page starts.", + "type": "integer", + "paramType": "query", + "required": false + } + ], + "responses": { + "200": { + "description": "Resource returned", + "schema": { + "$ref": "charges.json#/definitions/chargeList" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "chargeDetails": { + "get": { + "description": "Individual charge information for company.", + "tags": [ + "charges" + ], + "parameters": [ + { + "name": "company_number", + "description": "The company number that the charge is required for.", + "paramType": "path", + "required": true, + "type": "string" + }, + { + "name": "charge_id", + "description": "The id of the charge details that are required.", + "paramType": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Resource returned", + "schema": { + "$ref": "charges.json#/definitions/chargeDetails" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "definitions": { + "chargeList": { + "title": "chargeList", + "required": [ + "etag", + "items" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "total_count": { + "type": "integer", + "description": "Total number of charges returned by the API (filtering applies)." + }, + "unfiletered_count": { + "type": "integer", + "description": "Number of satisfied charges" + }, + "satisfied_count": { + "type": "integer", + "description": "Number of satisfied charges" + }, + "part_satisfied_count": { + "type": "integer", + "description": "Number of satisfied charges" + }, + "items": { + "type": "array", + "description": "List of charges", + "items": { + "$ref": "charges.json#/definitions/chargeDetails" + } + } + } + }, + "chargeDetails": { + "title": "chargeDetails", + "required": [ + "etag", + "status", + "classification", + "charge_number", + "id" + ], + "properties": { + "etag": { + "type": "string" + }, + "id": { + "type": "string", + "description": "The id of the charge" + }, + "charge_code": { + "type": "string", + "description": "The charge code is a replacement of the mortgage description" + }, + "classification": { + "type": "array", + "description": "Classification information", + "items": { + "$ref": "charges.json#/definitions/classificationDesc" + } + }, + "charge_number": { + "type": "integer", + "description": "The charge number is used to reference an individual charge" + }, + "status": { + "enum": [ + "outstanding", + "fully-satisfied", + "part-satisfied", + "satisfied" + ], + "type": "string", + "description": "The status of the charge.\n For enumeration descriptions see `status` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)" + }, + "assests_ceased_released": { + "enum": [ + "property-ceased-to-belong", + "part-property-release-and-ceased-to-belong", + "part-property-released", + "part-property-ceased-to-belong", + "whole-property-released", + "multiple-filings", + "whole-property-released-and-ceased-to-belong" + ], + "type": "string", + "description": "Cease/release information about the charge.\n For enumeration descriptions see `assets-ceased-released` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)" + }, + "acquired_on": { + "type": "string", + "format": "date", + "description": "The date the property or undertaking was acquired on" + }, + "delivered_on": { + "type": "string", + "format": "date", + "description": "The date the charge was submitted to Companies House" + }, + "resolved_on": { + "type": "string", + "format": "date", + "description": "The date the issue was resolved on" + }, + "covering_instrument_date": { + "type": "string", + "format": "date", + "description": "The date by which the series of debentures were created" + }, + "created_on": { + "type": "string", + "format": "date", + "description": "The date the charge was created" + }, + "satisfied_on": { + "type": "string", + "format": "date", + "description": "The date the charge was satisfied" + }, + "particulars": { + "type": "array", + "description": "Details of charge or undertaking", + "items": { + "$ref": "charges.json#/definitions/particularDesc" + } + }, + "secured_details": { + "type": "array", + "description": "Information about what is secured against this charge", + "items": { + "$ref": "charges.json#/definitions/securedDetailsDesc" + } + }, + "scottish_alterations": { + "type": "array", + "items": { + "$ref": "charges.json#/definitions/alterationsDesc" + }, + "description": "Information about alterations for Scottish companies" + }, + "more_than_four_persons_entitled": { + "type": "boolean", + "description": "Charge has more than four person entitled" + }, + "persons_entitled": { + "type": "array", + "description": "People that are entitled to the charge", + "items": { + "$ref": "charges.json#/definitions/persons_entitled" + } + }, + "transactions": { + "type": "array", + "description": "Transactions that have been filed for the charge.", + "items": { + "$ref": "charges.json#/definitions/transactions" + } + }, + "insolvency_cases": { + "type": "array", + "description": "Transactions that have been filed for the charge.", + "items": { + "$ref": "charges.json#/definitions/insolvency_cases" + } + }, + "links": { + "type": "array", + "description": "The resources related to this charge", + "items": { + "$ref": "charges.json#/definitions/charge_links" + } + } + } + }, + "alterationsDesc": { + "title": "alterationsDesc", + "required": [ + "type", + "description" + ], + "properties": { + "has_alterations_to_order": { + "type": "boolean", + "description": "The charge has alterations to order" + }, + "has_alterations_to_prohibitions": { + "type": "boolean", + "description": "The charge has alterations to prohibitions" + }, + "has_alterations_to_provisions": { + "type": "boolean", + "description": "The charge has provisions restricting the creation of further charges" + } + } + }, + "securedDetailsDesc": { + "title": "securedDetailsDesc", + "required": [ + "type", + "description" + ], + "properties": { + "type": { + "enum": [ + "amount-secured", + "obligations-secured" + ], + "type": "string", + "description": "The type of secured details.\n For enumeration descriptions see `secured-details-description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)" + }, + "description": { + "type": "string", + "description": "Details of the amount or obligation secured by the charge" + } + } + }, + "particularDesc": { + "title": "particularDesc", + "required": [ + "type", + "description" + ], + "properties": { + "type": { + "enum": [ + "short-particulars", + "charged-property-description", + "charged-property-or-undertaking-description", + "brief-description" + ], + "type": "string", + "description": "The type of charge particulars.\n For enumeration descriptions see `particular-description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)" + }, + "description": { + "type": "string", + "description": "Details of charge particulars" + }, + "contains_floating_charge": { + "type": "boolean", + "description": "The charge contains a floating charge" + }, + "contains_fixed_charge": { + "type": "boolean", + "description": "The charge contains a fixed charge" + }, + "floating_charge_covers_all": { + "type": "boolean", + "description": "The floating charge covers all the property or undertaking or the company" + }, + "contains_negative_pledge": { + "type": "boolean", + "description": "The charge contains a negative pledge" + }, + "chargor_acting_as_bare_trustee": { + "type": "boolean", + "description": "The chargor is acting as a bare trustee for the property" + } + } + }, + "classificationDesc": { + "title": "classificationDesc", + "required": [ + "type", + "description" + ], + "properties": { + "type": { + "enum": [ + "charge-description", + "nature-of-charge" + ], + "type": "string", + "description": "The type of charge classication.\n For enumeration descriptions see `classificationDesc` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/mortgage_descriptions.yml)" + }, + "description": { + "type": "string", + "description": "Details of the charge classification" + } + } + }, + "persons_entitled": { + "title": "persons_entitled", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the person entitled." + } + } + }, + "transactions": { + "title": "transactions", + "properties": { + "filing_type": { + "type": "string", + "description": "Filing type which created, updated or satisfied the charge" + }, + "delivered_on": { + "type": "string", + "format": "date", + "description": "The date the filing was submitted to Companies House" + }, + "insolvency_case_number": { + "type": "string", + "description": "The insolvency case related to this filing" + }, + "links": { + "type": "array", + "description": "The resources related to this filing", + "items": { + "$ref": "charges.json#/definitions/transaction_links" + } + } + } + }, + "transaction_links": { + "title": "transaction_links", + "properties": { + "filing": { + "type": "string", + "description": "Link to the charge filing data" + }, + "insolvency_case": { + "type": "string", + "description": "Link to the insolvency case related to this filing" + } + } + }, + "insolvency_cases": { + "title": "insolvency_cases", + "properties": { + "case_number": { + "type": "string", + "description": "The number of this insolvency case" + }, + "links": { + "type": "array", + "description": "The resources related to this insolvency case", + "items": { + "$ref": "charges.json#/definitions/insolvency_case_links" + } + } + } + }, + "insolvency_case_links": { + "title": "insolvency_case_links", + "properties": { + "case": { + "type": "string", + "description": "Link to the insolvency case data" + } + } + }, + "charge_links": { + "title": "charge_links", + "required": [ + "self" + ], + "properties": { + "self": { + "type": "string", + "description": "Link to the this charge data" + } + } + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json new file mode 100644 index 0000000..e24eedb --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json @@ -0,0 +1,255 @@ +{ + "getCompanyAddress": { + "get": { + "summary": "Registered Office Address", + "description": "Get the current address of a company", + "parameters": [{ + "name": "company_number", + "in": "path", + "description": "Company number for registered office address", + "required": true, + "type": "string" + }], + "tags": [ + "registeredOfficeAddress" + ], + "security": [{ + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update" + ] + }], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddress" + } + }, + "401": { + "description": "Not authorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "addressTransactions": { + "get": { + "summary": "Get a registered office address resource", + "description": "Get registered office address resource", + "tags": [ + "registeredOfficeAddress" + ], + "produces": [ + "application/json" + ], + "parameters": [{ + "name": "transaction_id", + "in": "path", + "type": "string", + "description": "transaction id", + "required": true + }], + "security": [{ + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update" + ] + }], + "responses": { + "200": { + "description": "Registered office address resource", + "schema": { + "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange" + } + }, + "401": { + "description": "Not authorised to get the registered office address resource" + }, + "404": { + "description": "Resource not found" + } + } + }, + "put": { + "summary": "Replace a registered office address resource", + "description": "Replace a registered office address resource. If filing with the Insolvency scope “company_number” must be provided in the request body.", + "tags": [ + "registeredOfficeAddress" + ], + "consumes": [ + "application/json" + ], + "parameters": [{ + "name": "transaction_id", + "in": "path", + "type": "string", + "description": "transaction id", + "required": true + }, + { + "name": "request", + "in": "body", + "description": "The writable fields of the registered office address resource", + "required": true, + "schema": { + "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange" + } + } + ], + "security": [{ + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update" + ] + }], + "responses": { + "200": { + "description": "Registered office address resource updated", + "schema": { + "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange" + } + }, + "401": { + "description": "Not authorised to update this transaction" + }, + "403": { + "description": "Registered office address resource cannot be updated as it's containing transaction has been closed" + }, + "404": { + "description": "Transaction not found" + } + } + }, + "post": { + "summary": "Create a registered office address resource", + "description": "Effective 15 September 2025, the postcode of the registered office address will be a mandatory field for the \nCompanies House Service (CHS) Filed AD01 form used to change a company’s registered office address.\n\nCreate a registered office address resource will require the “postal_code” field in the request body from this date. \nTo prepare, please ensure your implementation of the registered office address resource includes a “postal_code” field in all relevant requests.", + "tags": [ + "registeredOfficeAddress" + ], + "x-operationName": "create", + "parameters": [{ + "name": "transaction_id", + "in": "path", + "type": "string", + "description": "transaction id", + "required": true + }, + { + "name": "request", + "in": "body", + "description": "The writable fields of the registered office address resource", + "required": false, + "schema": { + "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange" + } + } + ], + "security": [{ + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update" + ] + }], + "responses": { + "201": { + "description": "Registered office address resource created within transaction envelope", + "schema": { + "$ref": "../models/registeredOfficeAddress.json#/definitions/registeredOfficeAddressChange" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Not authorised to create a registered office address resource within this transaction" + }, + "403": { + "description": "Registered office address resource cannot be created as it's containing transaction has been closed" + }, + "404": { + "description": "Transaction not found" + }, + "409": { + "description": "Conflict. Occurs when a registered office address already exists for the given transaction" + } + } + } + }, + "addressTransactionsValidation": { + "get": { + "summary": "Get validation status for a registered office address resource", + "description": "Get validation status for registered office address resource", + "tags": [ + "registeredOfficeAddress" + ], + "x-operationName": "validate", + "produces": [ + "application/json" + ], + "parameters": [{ + "name": "transaction_id", + "in": "path", + "type": "string", + "description": "transaction id", + "required": true + }], + "security": [{ + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update" + ] + }], + "responses": { + "200": { + "description": "Validation status for a registered office address resource.", + "schema": { + "$ref": "../models/errors.json#/definitions/validationStatus" + } + }, + "401": { + "description": "Not authorised to get the registered office address resource" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "filingGenerator": { + "get": { + "summary": "Get registered office address filing", + "description": "Generate and return registered office address filing", + "tags": [ + "filing-generator" + ], + "x-operationName": "generate registered office address filing", + "security": [{ + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/{company_number}/registered-office-address.update" + ] + }], + "responses": { + "200": { + "description": "Generated filings successfully returned", + "schema": { + "type": "array", + "items": { + "$ref": "../models/filings.json#/definitions/filing" + } + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "errorModel.json#/definitions/error" + } + } + } + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json new file mode 100644 index 0000000..3718d12 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json @@ -0,0 +1,541 @@ +{ + "listCompanyOfficers": { + "get": { + "summary": "Company Officers", + "description": "List of all company officers", + "x-operationName": "list", + "tags": [ + "officers" + ], + "parameters": [{ + "name": "company_number", + "in": "path", + "description": "The company number of the officer list being requested.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of officers to return per page.", + "type": "integer" + }, + { + "name": "register_type", + "in": "query", + "description": "The register_type determines which officer type is returned for the registers view.The register_type field will only work if registers_view is set to true", + "type": "string", + "enum": [ + "directors", + "secretaries", + "llp_members" + ] + }, + { + "name": "register_view", + "in": "query", + "description": "Display register specific information. If given register is held at Companies House, registers_view set to true and correct register_type specified, only active officers will be returned. Defaults to false", + "type": "string", + "enum": [ + "true", + "false" + ] + }, + { + "name": "start_index", + "in": "query", + "description": "The offset into the entire result set that this page starts.", + "type": "integer" + }, + { + "name": "order_by", + "in": "query", + "description": "The field by which to order the result set.", + "type": "string", + "enum": [ + "appointed_on", + "resigned_on", + "surname" + ] + } + ], + "responses": { + "200": { + "description": "List the company officers", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "companyOfficerList.json#/definitions/officerList" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "errorModel.json#/definitions/error" + } + }, + "401": { + "description": "Unauthorised" + } + } + } + }, + "getCompanyOfficerAppointment": { + "get": { + "summary": "Get a company officer appointment", + "description": "Get details of an individual company officer appointment", + "tags": [ + "officers" + ], + "parameters": [{ + "name": "company_number", + "in": "path", + "description": "The company number of the officer list being requested.", + "required": true, + "type": "string" + }, + { + "name": "appointment_id", + "in": "path", + "description": "The appointment id of the company officer appointment being requested.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Get a company officer appointment", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "companyOfficerList.json#/definitions/officerSummary" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "errorModel.json#/definitions/error" + } + }, + "401": { + "description": "Unauthorised" + } + } + } + }, + "definitions": { + "address": { + "title": "address", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "country": { + "description": "The country e.g. United Kingdom.", + "type": "string" + }, + "locality": { + "description": "The locality e.g. London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code e.g. CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region e.g. Surrey.", + "type": "string" + } + } + }, + "contactDetails": { + "title": "contactDetails", + "properties": { + "contact_name": { + "description": "The name of the contact.", + "type": "string" + } + } + }, + "officerList": { + "title": "officerList", + "properties": { + "active_count": { + "description": "The number of active officers in this result set.", + "type": "integer" + }, + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "items": { + "description": "The list of officers.", + "items": { + "$ref": "companyOfficerList.json#/definitions/officerSummary" + }, + "type": "array" + }, + "items_per_page": { + "description": "The number of officers to return per page.", + "type": "integer" + }, + "kind": { + "enum": [ + "officer-list" + ], + "type": "string" + }, + "links": { + "description": "Links to other resources associated with this officer list resource.", + "items": { + "$ref": "companyOfficerList.json#/definitions/linkTypes" + }, + "type": "object" + }, + "resigned_count": { + "description": "The number of resigned officers in this result set.", + "type": "integer" + }, + "start_index": { + "description": "The offset into the entire result set that this page starts.", + "type": "integer" + }, + "total_results": { + "description": "The total number of officers in this result set.", + "type": "integer" + } + }, + "required": [ + "etag", + "items_per_page", + "kind", + "links", + "items", + "start_index", + "total_results", + "active_count", + "resigned_count" + ] + }, + "officerSummary": { + "title": "officerSummary", + "properties": { + "address": { + "description": "The correspondence address of the officer.", + "items": { + "$ref": "companyOfficerList.json#/definitions/address" + }, + "type": "object" + }, + "appointed_on": { + "description": "The date on which the officer was appointed. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officer.", + "type": "string", + "format": "date" + }, + "contact_details": { + "description": "The contact at the `corporate-managing-officer` of a `registered-overseas-entity`.", + "items": { + "$ref": "companyOfficerList.json#/definitions/contactDetails" + }, + "type": "object" + }, + "country_of_residence": { + "description": "The officer's country of residence.", + "type": "string" + }, + "date_of_birth": { + "description": "Details of director date of birth.", + "items": { + "$ref": "companyOfficerList.json#/definitions/dateOfBirth" + }, + "type": "object" + }, + "links": { + "description": "Links to other resources associated with this officer list item.", + "items": { + "$ref": "companyOfficerList.json#/definitions/itemLinkTypes" + }, + "type": "object" + }, + "name": { + "description": "Corporate or natural officer name.", + "type": "string" + }, + "nationality": { + "description": "The officer's nationality.", + "type": "string" + }, + "occupation": { + "description": "The officer's job title.", + "type": "string" + }, + "officer_role": { + "enum": [ + "cic-manager", + "corporate-director", + "corporate-llp-designated-member", + "corporate-llp-member", + "corporate-manager-of-an-eeig", + "corporate-managing-officer", + "corporate-member-of-a-management-organ", + "corporate-member-of-a-supervisory-organ", + "corporate-member-of-an-administrative-organ", + "corporate-nominee-director", + "corporate-nominee-secretary", + "corporate-secretary", + "director", + "general-partner-in-a-limited-partnership", + "judicial-factor", + "limited-partner-in-a-limited-partnership", + "llp-designated-member", + "llp-member", + "manager-of-an-eeig", + "managing-officer", + "member-of-a-management-organ", + "member-of-a-supervisory-organ", + "member-of-an-administrative-organ", + "nominee-director", + "nominee-secretary", + "person-authorised-to-accept", + "person-authorised-to-represent", + "person-authorised-to-represent-and-accept", + "receiver-and-manager", + "secretary" + ], + "type": "string" + }, + "person_number" : { + "description" : "Unique person identifier as displayed in bulk products 195, 198, 208, 209 and 216.", + "type" : "string" + }, + "principal_office_address": { + "description": "The principal/registered office address of a `corporate-managing-officer` of a `registered-overseas-entity`.", + "items": { + "$ref": "companyOfficerList.json#/definitions/principalOfficeAddress" + }, + "type": "object" + }, + "resigned_on": { + "description": "The date the officer was resigned. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officers cessation.", + "type": "string", + "format": "date" + }, + "responsibilities": { + "description": "The responsibilities of the managing officer of a `registered-overseas-entity`.", + "type": "string" + }, + "former_names": { + "description": "Former names for the officer.", + "items": { + "$ref": "companyOfficerList.json#/definitions/formerNames" + }, + "type": "array" + }, + "identification": { + "description": "Only one from `eea`, `non-eea`, `uk-limited-company`, `other-corporate-body-or-firm` or `registered-overseas-entity-corporate-managing-officer` can be supplied, not multiples of them.", + "items": { + "$ref": "companyOfficerList.json#/definitions/corporateIdent" + }, + "type": "object" + }, + "identity_verification_details": { + "description": "Information relating to the identity verification of the officer", + "items": { + "$ref": "../models/officerChanges.json#/definitions/identityVerificationDetails" + }, + "type": "object" + }, + "appointed_before": { + "description": "The date the officer was appointed before. Only present when the is_pre_1992_appointment attribute is true.", + "type": "string" + }, + "etag": { + "description": "The Etag of the resource", + "type": "string" + }, + "is_pre_1992_appointment": { + "description": "Indicator representing if the officer was appointed before their appointment date.", + "type": "boolean" + } + }, + "required": [ + "links", + "name", + "officer_role" + ] + }, + "itemLinkTypes": { + "title": "itemLinkTypes", + "required": [ + "self", + "officer" + ], + "properties": { + "self": { + "description": "Link to this individual company officer appointment resource.", + "type": "string" + }, + "officer": { + "description": "Links to other officer resources associated with this officer list item.", + "items": { + "$ref": "companyOfficerList.json#/definitions/officerLinkTypes" + }, + "type": "object" + } + } + }, + "linkTypes": { + "title": "linkTypes", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "Link to this officer list resource.", + "type": "string" + } + } + }, + "officerLinkTypes": { + "title": "officerLinkTypes", + "required": [ + "appointments" + ], + "properties": { + "appointments": { + "description": "Link to the officer appointment resource that this appointment is associated with.", + "type": "string" + } + } + }, + "formerNames": { + "title": "formerNames", + "properties": { + "forenames": { + "description": "Former forenames of the officer.", + "type": "string" + }, + "surname": { + "description": "Former surnames of the officer.", + "type": "string" + } + } + }, + "corporateIdent": { + "title": "corporateIdent", + "properties": { + "identification_type": { + "description": "The officer's identity type", + "enum": [ + "eea", + "non-eea", + "uk-limited-company", + "other-corporate-body-or-firm", + "registered-overseas-entity-corporate-managing-officer" + ], + "type": "string" + }, + "legal_authority": { + "description": "The legal authority supervising the company.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the company as defined by its country of registration.", + "type": "string" + }, + "place_registered": { + "description": "Place registered.", + "type": "string" + }, + "registration_number": { + "description": "Company registration number.", + "type": "string" + } + } + }, + "dateOfBirth": { + "title": "dateOfBirth", + "properties": { + "month": { + "description": "The month of date of birth.", + "type": "integer" + }, + "year": { + "description": "The year of date of birth.", + "type": "integer" + } + }, + "required": [ + "month", + "year" + ] + }, + "principalOfficeAddress": { + "title": "principalOfficeAddress", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "country": { + "description": "The country e.g. United Kingdom.", + "type": "string" + }, + "locality": { + "description": "The locality e.g. London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code e.g. CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region e.g. Surrey.", + "type": "string" + } + } + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json new file mode 100644 index 0000000..df963fe --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json @@ -0,0 +1,822 @@ +{ + "get": { + "summary": "Company profile", + "description": "Get the basic company information", + "parameters": [{ + "name": "company_number", + "in": "path", + "description": "The company number of the basic information to return.", + "required": true, + "type": "string" + }], + "tags": [ + "companyProfile" + ], + "responses": { + "200": { + "description": "readCompanyProfile", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "companyProfile.json#/definitions/companyProfile" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + }, + "definitions": { + "accountingReferenceDate": { + "title": "accountingReferenceDate", + "type": "object", + "required": [ + "day", + "month" + ], + "properties": { + "day": { + "type": "integer", + "description": "The Accounting Reference Date (ARD) day." + }, + "month": { + "type": "integer", + "description": "The Accounting Reference Date (ARD) month." + } + } + }, + "accountsInformation": { + "title": "accountsInformation", + "type": "object", + "required": [ + "overdue", + "next_made_up_to", + "accounting_reference_date" + ], + "properties": { + "accounting_reference_date": { + "description": "The Accounting Reference Date (ARD) of the company.", + "type": "object", + "items": { + "$ref": "companyProfile.json#/definitions/accountingReferenceDate" + } + }, + "last_accounts": { + "description": "The last company accounts filed.", + "type": "object", + "items": { + "$ref": "companyProfile.json#/definitions/lastAccounts" + } + }, + "next_due": { + "description": "Deprecated. Please use accounts.next_accounts.due_on", + "type": "string", + "format": "date" + }, + "next_made_up_to": { + "description": "Deprecated. Please use accounts.next_accounts.period_end_on", + "type": "string", + "format": "date" + }, + "overdue": { + "type": "boolean", + "description": "Deprecated. Please use accounts.next_accounts.overdue" + }, + "next_accounts": { + "description": "The next company accounts filed.", + "type": "object", + "items": { + "$ref": "companyProfile.json#/definitions/nextAccounts" + } + } + + } + }, + "annualReturnInformation": { + "title": "annualReturnInformation", + "type": "object", + "properties": { + "last_made_up_to": { + "description": "The date the last annual return was made up to.", + "type": "string", + "format": "date" + }, + "next_due": { + "description": "The date the next annual return is due. This member will only be returned if a confirmation statement has not been filed and the date is before 28th July 2016, otherwise refer to `confirmation_statement.next_due`", + "type": "string", + "format": "date" + }, + "next_made_up_to": { + "description": "The date the next annual return should be made up to. This member will only be returned if a confirmation statement has not been filed and the date is before 30th July 2016, otherwise refer to `confirmation_statement.next_made_up_to`", + "type": "string", + "format": "date" + }, + "overdue": { + "description": "Flag indicating if the annual return is overdue.", + "type": "boolean" + } + } + }, + "confirmationOfStatementInformation": { + "title": "confirmationOfStatementInformation", + "required": [ + "next_made_up_to", + "next_due" + ], + "properties": { + "last_made_up_to": { + "description": "The date to which the company last made a confirmation statement.", + "type": "string", + "format": "date" + }, + "next_due": { + "description": "The date by which the next confimation statement must be received.", + "type": "string", + "format": "date" + }, + "next_made_up_to": { + "description": "The date to which the company must next make a confirmation statement.", + "type": "string", + "format": "date" + }, + "overdue": { + "description": "Flag indicating if the confirmation statement is overdue", + "type": "boolean" + } + } + }, + "companyProfile": { + "title": "companyProfile", + "required": [ + "company_name", + "company_number", + "type", + "can_file", + "links" + ], + "properties": { + "accounts": { + "description": "Company accounts information.", + "items": { + "$ref": "companyProfile.json#/definitions/accountsInformation" + }, + "type": "object" + }, + "annual_return": { + "description": "Annual return information. This member is only returned if a confirmation statement has not be filed.", + "items": { + "$ref": "companyProfile.json#/definitions/annualReturnInformation" + }, + "type": "object" + }, + "can_file": { + "description": "Flag indicating whether this company can file.", + "type": "boolean" + }, + "confirmation_statement": { + "description": "Confirmation statement information (N.B. refers to the Annual Statement where type is registered-overseas-entity)", + "items": { + "$ref": "companyProfile.json#/definitions/confirmationOfStatementInformation" + }, + "type": "object" + }, + "company_name": { + "description": "The name of the company.", + "type": "string" + }, + "jurisdiction": { + "description": "The jurisdiction specifies the political body responsible for the company.", + "type": "string", + "enum": [ + "england-wales", + "wales", + "scotland", + "northern-ireland", + "european-union", + "united-kingdom", + "england", + "noneu" + ] + }, + "company_number": { + "description": "The number of the company.", + "type": "string" + }, + "date_of_creation": { + "description": "The date when the company was created.", + "type": "string", + "format": "date" + }, + "date_of_cessation": { + "description": "The date which the company was converted/closed, dissolved or removed. Please refer to company status to determine which.", + "type": "string", + "format": "date" + }, + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "has_been_liquidated": { + "description": "Deprecated. Please use links.insolvency", + "type": "boolean" + }, + "has_charges": { + "description": "Deprecated. Please use links.charges", + "type": "boolean" + }, + "is_community_interest_company": { + "description": "Deprecated. Please use subtype", + "type": "boolean" + }, + "subtype": { + "description": "The subtype of the company. Possible values are:", + "enum":[ + "community-interest-company", + "private-fund-limited-partnership" + ], + "type": "string" + }, + "partial_data_available": { + "description": "Returned if Companies House is not the primary source of data for this company.\nFor enumeration descriptions see partial_data_available section in the enumeration mappings (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml).", + "enum":[ + "full-data-available-from-financial-conduct-authority", + "full-data-available-from-department-of-the-economy", + "full-data-available-from-the-company" + ], + "type": "string" + }, + "external_registration_number": { + "description": "The number given by an external registration body.", + "type": "string" + }, + "foreign_company_details": { + "description": "Foreign company details.", + "items": { + "$ref": "companyProfile.json#/definitions/foreignCompanyDetails" + }, + "type": "object" + }, + "last_full_members_list_date": { + "description": "The date of last full members list update.", + "type": "string", + "format": "date" + }, + "registered_office_address": { + "description": "The address of the company's registered office.", + "items": { + "$ref": "companyProfile.json#/definitions/registeredOfficeAddress" + }, + "type": "object" + }, + "service_address": { + "description": "The correspondence address of a Registered overseas entity", + "items": { + "$ref": "companyProfile.json#/definitions/serviceAddress" + }, + "type": "object" + }, + "super_secure_managing_officer_count": { + "description": "The total count of super secure managing officers for a `registered-overseas-entity`.", + "type": "integer" + }, + "sic_codes": { + "description": "SIC codes for this company.", + "type": "array", + "items": { + "type": "string" + } + }, + "previous_company_names": { + "description": "The previous names of this company.", + "items": { + "$ref": "companyProfile.json#/definitions/previousCompanyNames" + }, + "type": "array" + }, + "corporate_annotation": { + "description": "A corporate level message published by Companies House about a company, or situations affecting the company, or its information.", + "items": { + "$ref": "companyProfile.json#/definitions/corporateAnnotation" + }, + "type": "array" + }, + "company_status": { + "description": "The status of the company. \n For enumeration descriptions see `company_status` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ", + "type": "string", + "enum": [ + "active", + "dissolved", + "liquidation", + "receivership", + "administration", + "voluntary-arrangement", + "converted-closed", + "insolvency-proceedings", + "registered", + "removed", + "closed", + "open" + ] + }, + "company_status_detail": { + "description": "Extra details about the status of the company. \n For enumeration descriptions see `company_status_detail` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ", + "enum": [ + "transferred-from-uk", + "active-proposal-to-strike-off", + "petition-to-restore-dissolved", + "transformed-to-se", + "converted-to-plc" + ], + "type": "string" + }, + "type": { + "description": "The type of the company. \n For enumeration descriptions see `company_type` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ", + "enum": [ + "private-unlimited", + "ltd", + "plc", + "old-public-company", + "private-limited-guarant-nsc-limited-exemption", + "limited-partnership", + "private-limited-guarant-nsc", + "converted-or-closed", + "private-unlimited-nsc", + "private-limited-shares-section-30-exemption", + "protected-cell-company", + "assurance-company", + "oversea-company", + "eeig", + "icvc-securities", + "icvc-warrant", + "icvc-umbrella", + "registered-society-non-jurisdictional", + "industrial-and-provident-society", + "northern-ireland", + "northern-ireland-other", + "royal-charter", + "investment-company-with-variable-capital", + "unregistered-company", + "llp", + "other", + "european-public-limited-liability-company-se", + "uk-establishment", + "scottish-partnership", + "charitable-incorporated-organisation", + "scottish-charitable-incorporated-organisation", + "further-education-or-sixth-form-college-corporation", + "registered-overseas-entity" + ], + "type": "string" + }, + "has_insolvency_history": { + "description": "Deprecated. Please use links.insolvency", + "type": "boolean" + }, + "undeliverable_registered_office_address": { + "description": "Flag indicating whether post can be delivered to the registered office.", + "type": "boolean" + }, + "registered_office_is_in_dispute": { + "description": "Flag indicating registered office address as been replaced.", + "type": "boolean" + }, + "branch_company_details": { + "description": "UK branch of a foreign company.", + "items": { + "$ref": "companyProfile.json#/definitions/branchCompanyDetails" + } + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "companyProfile.json#/definitions/linksType" + }, + "type": "object" + } + } + }, + "lastAccounts": { + "title": "lastAccounts", + "properties": { + "made_up_to": { + "type": "string", + "format": "date", + "description": "Deprecated. Please use accounts.last_accounts.period_end_on" + }, + "period_end_on": { + "type": "string", + "format": "date", + "description": "The last day of the most recently filed accounting period." + }, + "period_start_on": { + "type": "string", + "format": "date", + "description": "The first day of the most recently filed accounting period." + }, + "type": { + "description": "The type of the last company accounts filed. \n For enumeration descriptions see `account_type` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ", + "enum": [ + "null", + "full", + "small", + "medium", + "group", + "dormant", + "interim", + "initial", + "total-exemption-full", + "total-exemption-small", + "partial-exemption", + "audit-exemption-subsidiary", + "filing-exemption-subsidiary", + "micro-entity", + "no-accounts-type-available", + "audited-abridged", + "unaudited-abridged" + ] + } + }, + "type": "string", + "required": [ + "type", + "made_up_to" + ] + }, + "nextAccounts": { + "title": "nextAccounts", + "type": "object", + "properties": { + "due_on": { + "description": "The date the next company accounts are due", + "type": "string", + "format": "date" + }, + "overdue": { + "description": "Flag indicating if the company accounts are overdue.", + "type": "boolean" + }, + "period_end_on": { + "description": "The last day of the next accounting period to be filed.", + "type": "string", + "format": "date" + }, + "period_start_on": { + "description": "The first day of the next accounting period to be filed.", + "type": "string", + "format": "date" + } + } + }, + "foreignCompanyDetails": { + "title": "foreignCompanyDetails", + "properties": { + "originating_registry": { + "description": "Company origin informations", + "type": "object", + "items": { + "$ref": "companyProfile.json#/definitions/originatingRegistry" + } + }, + "registration_number": { + "description": "Registration number in company of incorporation.", + "type": "string" + }, + "governed_by": { + "description": "Law governing the company in country of incorporation.", + "type": "string" + }, + "company_type": { + "description": "Legal form of the company in the country of incorporation.", + "type": "string" + }, + "is_a_credit_finance_institution": { + "description": "Is it a financial or credit institution.", + "type": "boolean" + }, + "accounts": { + "description": "Foreign company account information.", + "items": { + "$ref": "companyProfile.json#/definitions/accountInformation" + }, + "type": "object" + }, + "business_activity": { + "description": "Type of business undertaken by the company.", + "type": "string" + }, + "accounting_requirement": { + "description": "Accounts requirement.", + "items": { + "$ref": "companyProfile.json#/definitions/accountsRequired" + }, + "type": "object" + } + } + }, + "originatingRegistry": { + "title": "originatingRegistry", + "properties": { + "country": { + "description": "Country in which company was incorporated.", + "type": "string" + }, + "name": { + "description": "Identity of register in country of incorporation.", + "type": "string" + } + } + }, + "previousCompanyNames": { + "title": "previousCompanyNames", + "properties": { + "name": { + "description": "The previous company name", + "type": "string" + }, + "effective_from": { + "description": "The date from which the company name was effective.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date on which the company name ceased.", + "type": "string", + "format": "date" + } + }, + "required": [ + "name", + "effective_from", + "ceased_on" + ] + }, + "corporateAnnotation": { + "title": "corporateAnnotation", + "properties": { + "created_on": { + "description": "The date on which the corporate annotation was created.", + "type": "string", + "format": "date" + }, + "description": { + "description": "The details of a corporate annotation which has a corporate_annotation.type of “other”.", + "type": "string" + }, + "type": { + "description": "The type of corporate annotation. \n For enumeration descriptions see `corporate_annotation_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ", + "type": "string" + } + }, + "required": [ + "created_on", + "type" + ] + }, + "accountInformation": { + "title": "accountInformation", + "properties": { + "account_period_from:": { + "description": "Date account period starts under parent law.", + "items": { + "$ref": "companyProfile.json#/definitions/accountPeriodFrom" + }, + "type": "object" + }, + "account_period_to": { + "description": "Date account period ends under parent law.", + "items": { + "$ref": "companyProfile.json#/definitions/accountPeriodTo" + }, + "type": "object" + }, + "must_file_within": { + "description": "Time allowed from period end for disclosure of accounts under parent law.", + "items": { + "$ref": "companyProfile.json#/definitions/fileWithin" + }, + "type": "object" + } + } + }, + "accountPeriodFrom": { + "title": "accountPeriodFrom", + "properties": { + "day": { + "description": "Day on which accounting period starts under parent law.", + "type": "integer" + }, + "month": { + "description": "Month in which accounting period starts under parent law.", + "type": "integer" + } + } + }, + "accountPeriodTo": { + "title": "accountPeriodTo", + "properties": { + "day": { + "description": "Day on which accounting period ends under parent law.", + "type": "integer" + }, + "month": { + "description": "Month in which accounting period ends under parent law.", + "type": "integer" + } + } + }, + "fileWithin": { + "title": "fileWithin", + "properties": { + "months": { + "description": "Number of months within which to file.", + "type": "integer" + } + } + }, + "accountsRequired": { + "title": "accountsRequired", + "properties": { + "foreign_account_type": { + "description": "Type of accounting requirement that applies. \n For enumeration descriptions see `foreign_account_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ", + "enum": [ + "accounting-requirements-of-originating-country-apply", + "accounting-requirements-of-originating-country-do-not-apply" + ], + "type": "string" + }, + "terms_of_account_publication": { + "description": "Describes how the publication date is derived. \n For enumeration descriptions see `terms_of_account_publication` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml). ", + "enum": [ + "accounts-publication-date-supplied-by-company", + "accounting-publication-date-does-not-need-to-be-supplied-by-company", + "accounting-reference-date-allocated-by-companies-house" + ], + "type": "string" + } + } + }, + "registeredOfficeAddress": { + "title": "registeredOfficeAddress", + "properties": { + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "country": { + "description": "The country.", + "enum": [ + "Wales", + "England", + "Scotland", + "Great Britain", + "Not specified", + "United Kingdom", + "Northern Ireland" + ], + "type": "string" + }, + "locality": { + "description": "The locality e.g London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code e.g CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region e.g Surrey.", + "type": "string" + } + } + }, + "serviceAddress": { + "title": "serviceAddress", + "properties": { + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "country": { + "description": "The country e.g. United Kingdom.", + "type": "string" + }, + "locality": { + "description": "The locality e.g London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code e.g CF14 3UZ.", + "type": "string" + }, + "region": { + "description": "The region e.g Surrey.", + "type": "string" + } + } + }, + "branchCompanyDetails": { + "title": "branchCompanyDetails", + "properties": { + "business_activity": { + "description": "Type of business undertaken by the UK establishment.", + "type": "string" + }, + "parent_company_number": { + "description": "Parent company number.", + "type": "string" + }, + "parent_company_name": { + "description": "Parent company name.", + "type": "string" + } + } + }, + "linksType": { + "title": "linksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + }, + "persons_with_significant_control": { + "description": "The URL of the persons with significant control list resource.", + "type": "string" + }, + "persons_with_significant_control_statements": { + "description": "The URL of the persons with significant control statements list resource.", + "type": "string" + }, + "registers": { + "description": "The URL of the registers resource for this company", + "type": "string" + }, + "uk-establishments": { + "description": "The URL of the uk establishments list resource for this company.", + "type": "string" + }, + "overseas": { + "description": "The URL of the overseas details resource for this company.", + "type": "string" + }, + "officers": { + "description": "The URL of the company's officer list resource.", + "type": "string" + }, + "insolvency": { + "description": "The URL of the company's insolvency list resource.", + "type": "string" + }, + "filing_history": { + "description": "The URL of the company's filing history list resource.", + "type": "string" + }, + "charges": { + "description": "The URL of the company's charges list resource.", + "type": "string" + }, + "exemptions": { + "description": "The URL of the company's exemptions list resource.", + "type": "string" + } + } + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json new file mode 100644 index 0000000..4dc5759 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json @@ -0,0 +1,454 @@ +{ + "get": { + "summary": "Company registers", + "tags": [ + "registers" + ], + "description": "Get the company registers information", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the register information to return.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "readCompanyRegister", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "companyRegisters.json#/definitions/companyRegister" + } + }, + "401": { + "description": "Unauthorised" + } + } + }, + "definitions": { + "companyRegister": { + "title": "companyRegister", + "type": "object", + "required": [ + "links", + "company_number", + "kind", + "registers" + ], + "properties": { + "links": { + "type": "object", + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "companyRegisters.json#/definitions/linksType" + } + }, + "company_number": { + "type": "string", + "description": "The number of the company." + }, + "kind": { + "enum": [ + "registers" + ], + "type": "string" + }, + "registers": { + "description": "company registers information.", + "items": { + "$ref": "companyRegisters.json#/definitions/registers" + } + }, + "etag": { + "description": "The ETag of the resource.", + "type": "string" + } + } + }, + "registers": { + "title": "registers", + "description": "Registered company information", + "type": "object", + "required": [ + "directors", + "secretaries", + "persons_with_significant_control", + "usual_residential_address", + "members" + ], + "properties": { + "directors": { + "description": "List of registered company directors.", + "items": { + "$ref": "companyRegisters.json#/definitions/registerListDirectors" + } + }, + "secretaries": { + "description": "List of registered company secretaries.", + "items": { + "$ref": "companyRegisters.json#/definitions/registerListSecretaries" + } + }, + "persons_with_significant_control": { + "description": "List of registered company persons with significant control.", + "items": { + "$ref": "companyRegisters.json#/definitions/registerListPersonsWithSignificantControl" + } + }, + "usual_residential_address": { + "description": "List of register addresses.", + "items": { + "$ref": "companyRegisters.json#/definitions/registerListUsualResidentialAddress" + } + }, + "llp_usual_residential_address": { + "description": "List of register addresses.", + "items": { + "$ref": "companyRegisters.json#/definitions/registerListLLPUsualResidentialAddress" + } + }, + "members": { + "description": "List of registered company members..", + "items": { + "$ref": "companyRegisters.json#/definitions/registerListMembers" + } + }, + "llp_members": { + "description": "List of registered llp members.", + "items": { + "$ref": "companyRegisters.json#/definitions/registerListLLPMembers" + } + } + } + }, + "registerListDirectors": { + "title": "registerListDirectors", + "required": [ + "register_type", + "items" + ], + "properties": { + "register_type": { + "description": "The register type.", + "enum": [ + "directors" + ], + "type": "string" + }, + "items": { + "items": { + "$ref": "companyRegisters.json#/definitions/registeredItems" + }, + "type": "array" + }, + "links": { + "description": "A set of URLs related to the resource.", + "type": "object", + "items": { + "$ref": "companyRegisters.json#/definitions/linksDirectorsRegister" + } + } + } + }, + "registerListSecretaries": { + "title": "registerListSecretaries", + "required": [ + "register_type", + "items" + ], + "properties": { + "register_type": { + "description": "The register type.", + "enum": [ + "secretaries" + ], + "type": "string" + }, + "items": { + "items": { + "$ref": "companyRegisters.json#/definitions/registeredItems" + }, + "type": "array" + }, + "links": { + "description": "A set of URLs related to the resource.", + "type": "object", + "items": { + "$ref": "companyRegisters.json#/definitions/linksSecretaryRegister" + } + } + } + }, + "registerListPersonsWithSignificantControl": { + "title": "registerListPersonsWithSignificantControl", + "required": [ + "register_type", + "items" + ], + "properties": { + "register_type": { + "description": "The register type.", + "enum": [ + "persons-with-significant-control" + ], + "type": "string" + }, + "items": { + "items": { + "$ref": "companyRegisters.json#/definitions/registeredItems" + }, + "type": "array" + }, + "links": { + "description": "A set of URLs related to the resource.", + "type": "object", + "items": { + "$ref": "companyRegisters.json#/definitions/linksPersonsWithSignificantControlRegister" + } + } + } + }, + "registerListUsualResidentialAddress": { + "title": "registerListUsualResidentialAddress", + "required": [ + "register_type", + "items" + ], + "properties": { + "register_type": { + "description": "The register type.", + "enum": [ + "usual-residential-address" + ], + "type": "string" + }, + "items": { + "items": { + "$ref": "companyRegisters.json#/definitions/registeredItems" + }, + "type": "array" + }, + "links": { + "description": "A set of URLs related to the resource.", + "type": "object", + "items": { + "$ref": "companyRegisters.json#/definitions/linksListUsualResidentialAddress" + } + } + } + }, + "registerListLLPUsualResidentialAddress": { + "title": "registerListLLPUsualResidentialAddress", + "properties": { + "register_type": { + "description": "The register type.", + "enum": [ + "llp-usual-residential-address" + ], + "type": "string" + }, + "items": { + "items": { + "$ref": "companyRegisters.json#/definitions/registeredItems" + }, + "type": "array" + }, + "links": { + "description": "A set of URLs related to the resource.", + "items": { + "$ref": "companyRegisters.json#/definitions/linksListLLPUsualResidentialAddress" + }, + "type": "object" + } + }, + "required": [ + "register_type", + "items" + ] + }, + "registerListMembers": { + "title": "registerListMembers", + "required": [ + "register_type", + "items" + ], + "properties": { + "register_type": { + "description": "The register type.", + "enum": [ + "members" + ], + "type": "string" + }, + "items": { + "items": { + "$ref": "companyRegisters.json#/definitions/registeredItems" + }, + "type": "array" + }, + "links": { + "description": "A set of URLs related to the resource.", + "type": "object", + "items": { + "$ref": "companyRegisters.json#/definitions/linksListMembers" + } + } + } + }, + "registerListLLPMembers": { + "title": "registerListLLPMembers", + "properties": { + "register_type": { + "description": "The register type.", + "enum": [ + "llp_members" + ], + "type": "string" + }, + "items": { + "items": { + "$ref": "companyRegisters.json#/definitions/registeredItems" + }, + "type": "array" + }, + "links": { + "description": "A set of URLs related to the resource.", + "items": { + "$ref": "companyRegisters.json#/definitions/linksListLLPMembers" + }, + "type": "object" + } + }, + "required": [ + "register_type", + "items" + ] + }, + "registeredItems": { + "title": "registeredItems", + "required": [ + "register_moved_to", + "moved_on", + "links" + ], + "properties": { + "moved_on": { + "description": "The date registered on", + "type": "string", + "format": "date" + }, + "register_moved_to": { + "description": "Location of registration", + "type": "string", + "enum": [ + "public-register", + "registered-office", + "single-alternative-inspection-location", + "unspecified-location" + ] + }, + "links": { + "description": "A set of URLs related to the resource.", + "items": { + "$ref": "companyRegisters.json#/definitions/linksItems" + }, + "type": "object" + } + } + }, + "linksType": { + "title": "linksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + } + } + }, + "linksItems": { + "title": "linksItems", + "required": [ + "filing" + ], + "properties": { + "filing": { + "description": "The URL of the transaction for the resource.", + "type": "string" + } + } + }, + "linksDirectorsRegister": { + "title": "linksDirectorsRegister", + "properties": { + "directors_register": { + "description": "The URL for the resource.", + "type": "string" + } + } + }, + "linksSecretaryRegister": { + "title": "linksSecretaryRegister", + "properties": { + "secretaries_register": { + "description": "The URL for the resource.", + "type": "string" + } + } + }, + "linksPersonsWithSignificantControlRegister": { + "title": "linksPersonsWithSignificantControlRegister", + "properties": { + "persons_with_significant_control_register": { + "description": "The URL for the resource.", + "type": "string" + } + } + }, + "linksListUsualResidentialAddress": { + "title": "linksListUsualResidentialAddress", + "properties": { + "usual_residential_address": { + "description": "The URL for the resource.", + "type": "string" + } + } + }, + "linksListLLPUsualResidentialAddress": { + "title": "linksListLLPUsualResidentialAddress", + "properties": { + "llp_usual_residential_address": { + "description": "The URL for the resource.", + "type": "string" + } + } + }, + "linksListMembers": { + "title": "linksListMembers", + "properties": { + "members": { + "description": "The URL for the resource.", + "type": "string" + } + } + }, + "linksListLLPMembers": { + "title": "linksListLLPMembers", + "properties": { + "llp_members": { + "description": "The URL for the resource.", + "type": "string" + } + } + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json new file mode 100644 index 0000000..272f2de --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json @@ -0,0 +1,125 @@ +{ + "get": { + "summary": "Company UK Establishments", + "description": "List of uk-establishments companies", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "Company number", + "required": true, + "type": "string" + } + ], + "tags": [ + "UKEstablishments" + ], + "responses": { + "200": { + "description": "Resource returned", + "schema": { + "$ref": "companyUKEstablishments.json#/definitions/companyUKEstablishments" + } + }, + "401": { + "description": "Unauthorised" + } + } + }, + "definitions": { + "companyUKEstablishments": { + "title": "companyUKEstablishments", + "required": [ + "etag", + "kind", + "items" + ], + "properties": { + "etag": { + "type": "string", + "description": "The ETag of the resource." + }, + "kind": { + "type": "string", + "description": "UK Establishment companies.", + "enum": [ + "ukestablishment-companies" + ] + }, + "links": { + "type": "object", + "description": "UK Establishment Resources related to this company.", + "items": { + "$ref": "companyUKEstablishments.json#/definitions/self_links" + } + }, + "items": { + "type": "array", + "description": "List of UK Establishment companies.", + "items": { + "$ref": "companyUKEstablishments.json#/definitions/companyDetails" + } + } + } + }, + "companyDetails": { + "title": "companyDetails", + "required": [ + "company_number", + "company_name", + "company_status", + "links" + ], + "properties": { + "company_number": { + "type": "string", + "description": "The number of the company." + }, + "company_name": { + "type": "string", + "description": "The name of the company." + }, + "company_status": { + "type": "string", + "description": "Company status." + }, + "locality": { + "type": "string", + "description": "The locality e.g London." + }, + "links": { + "description": "Resources related to this company.", + "type": "object", + "items": { + "$ref": "companyUKEstablishments.json#/definitions/links" + } + } + } + }, + "self_links": { + "title": "self_links", + "required": [ + "self" + ], + "properties": { + "self": { + "type": "string", + "description": "Link to this company." + } + } + }, + "links": { + "title": "links", + "required": [ + "company" + ], + "properties": { + "company": { + "type": "string", + "description": "The link to the company." + } + } + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json new file mode 100644 index 0000000..569d99e --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json @@ -0,0 +1,408 @@ +{ + "getNatural": { + "get": { + "summary": "Get natural officers disqualifications", + "description": "Get a natural officer's disqualifications", + "x-operationName": "get natural officer", + "tags": [ + "officerDisqualifications" + ], + "parameters": [ + { + "name": "officer_id", + "description": "The disqualified officer's id.", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Natural officer's disqualifications returned", + "schema": { + "$ref": "disqualifications.json#/definitions/naturalDisqualification" + }, + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getCorporate": { + "get": { + "summary": "Get a corporate officers disqualifications", + "description": "Get a corporate officer's disqualifications", + "x-operationName": "get corporate officer", + "tags": [ + "officerDisqualifications" + ], + "parameters": [ + { + "description": "The disqualified officer id.", + "name": "officer_id", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Corporate officer's disqualifications returned", + "schema": { + "$ref": "disqualifications.json#/definitions/corporateDisqualification" + }, + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "definitions": { + "corporateDisqualification": { + "title": "corporateDisqualification", + "required": [ + "disqualifications", + "etag", + "kind", + "links", + "name" + ], + "properties": { + "company_number": { + "description": "The registration number of the disqualified officer.", + "type": "string" + }, + "country_of_registration": { + "description": "The country in which the disqualified officer was registered.", + "type": "string" + }, + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "corporate-disqualification" + ] + }, + "name": { + "description": "The name of the disqualified officer.", + "type": "string" + }, + "links": { + "description": "Links to other resources associated with this officer disqualification resource.", + "type": "object", + "items": { + "$ref": "disqualifications.json#/definitions/links" + } + }, + "disqualifications": { + "description": "The officer's disqualifications.", + "type": "array", + "items": { + "$ref": "disqualifications.json#/definitions/disqualification" + } + }, + "permissions_to_act": { + "description": "Permissions that the disqualified officer has to act outside of their disqualification.", + "type": "array", + "items": { + "$ref": "disqualifications.json#/definitions/permission_to_act" + } + }, + "person_number": { + "description": "The disqualified officer's person number.", + "type": "string" + } + } + }, + "naturalDisqualification": { + "title": "naturalDisqualification", + "required": [ + "disqualifications", + "etag", + "kind", + "links", + "surname" + ], + "properties": { + "date_of_birth": { + "description": "The disqualified officer's date of birth.", + "type": "string", + "format": "date" + }, + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "forename": { + "description": "The forename of the disqualified officer.", + "type": "string" + }, + "honours": { + "description": "The honours that the disqualified officer has.", + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "natural-disqualification" + ] + }, + "nationality": { + "description": "The nationality of the disqualified officer.", + "type": "string" + }, + "other_forenames": { + "description": "The other forenames of the disqualified officer.", + "type": "string" + }, + "surname": { + "description": "The surname of the disqualified officer.", + "type": "string" + }, + "title": { + "description": "The title of the disqualified officer.", + "type": "string" + }, + "links": { + "description": "Links to other resources associated with this officer disqualification resource.", + "type": "object", + "items": { + "$ref": "disqualifications.json#/definitions/links" + } + }, + "disqualifications": { + "description": "The officer's disqualifications.", + "type": "array", + "items": { + "$ref": "disqualifications.json#/definitions/disqualification" + } + }, + "permissions_to_act": { + "description": "Permissions to act that have been granted for the disqualified officer.", + "type": "array", + "items": { + "$ref": "disqualifications.json#/definitions/permission_to_act" + } + }, + "person_number": { + "description": "The disqualified officer's person number.", + "type": "string" + } + } + }, + "address": { + "title": "address", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "country": { + "description": "The country. For example, UK.", + "type": "string" + }, + "locality": { + "description": "The locality. For example London.", + "type": "string" + }, + "postal_code": { + "description": "The postal code. For example CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region. For example Surrey.", + "type": "string" + } + } + }, + "links": { + "title": "links", + "properties": { + "self": { + "description": "Link to this disqualification resource.", + "type": "string" + } + }, + "required": [ + "self" + ] + }, + "disqualification": { + "title": "disqualification", + "properties": { + "case_identifier": { + "description": "The case identifier of the disqualification.", + "type": "string" + }, + "address": { + "description": "The address of the disqualified officer as provided by the disqualifying authority.", + "type": "object", + "items": { + "$ref": "disqualifications.json#/definitions/address" + } + }, + "company_names": { + "description": "The companies in which the misconduct took place.", + "type": "array", + "items": { + "type": "string" + } + }, + "court_name": { + "description": "The name of the court that handled the disqualification case.", + "type": "string" + }, + "disqualification_type": { + "description": "An enumeration type that provides the disqualifying authority that handled the disqualification case.\n For enumeration descriptions see `disqualification_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/disqualified_officer_descriptions.yml)", + "type": "string" + }, + "disqualified_from": { + "description": "The date that the disqualification starts.", + "type": "string", + "format": "date" + }, + "disqualified_until": { + "description": "The date that the disqualification ends.", + "type": "string", + "format": "date" + }, + "heard_on": { + "description": "The date the disqualification hearing was on.", + "type": "string", + "format": "date" + }, + "undertaken_on": { + "description": "The date the disqualification undertaking was agreed on.", + "type": "string", + "format": "date" + }, + "last_variation": { + "description": "The latest variation made to the disqualification.", + "type": "array", + "items": { + "$ref": "disqualifications.json#/definitions/last_variation" + } + }, + "reason": { + "description": "The reason for the disqualification.", + "type": "object", + "items": { + "$ref": "disqualifications.json#/definitions/reason" + } + } + }, + "required": [ + "address", + "disqualification_type", + "disqualified_from", + "disqualified_until", + "reason" + ] + }, + "last_variation": { + "title": "last_variation", + "properties": { + "varied_on": { + "description": "The date the variation was made against the disqualification.", + "type": "string", + "format": "date" + }, + "case_identifier": { + "description": "The case identifier of the variation.", + "type": "string" + }, + "court_name": { + "description": "The name of the court that handled the variation case.", + "type": "string" + } + } + }, + "permission_to_act": { + "title": "permission_to_act", + "required": [ + "expires_on", + "granted_on" + ], + "properties": { + "company_names": { + "description": "The companies for which the disqualified officer has permission to act.", + "type": "array", + "items": { + "type": "string" + } + }, + "court_name": { + "description": "The name of the court that granted the permission to act.", + "type": "string" + }, + "expires_on": { + "description": "The date that the permission ends.", + "type": "string", + "format": "date" + }, + "granted_on": { + "description": "The date that the permission starts.", + "type": "string", + "format": "date" + } + } + }, + "reason": { + "title": "reason", + "properties": { + "description_identifier": { + "description": "An enumeration type that provides the description for the reason of disqualification.\n For enumeration descriptions see `description_identifier` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/disqualified_officer_descriptions.yml)", + "type": "string" + }, + "act": { + "description": "An enumeration type that provides the law under which the disqualification was made.\n For enumeration descriptions see `act` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/disqualified_officer_descriptions.yml)", + "type": "string" + }, + "article": { + "description": "The article of the act under which the disqualification was made.\n Only applicable if `reason.act` is `company-directors-disqualification-northern-ireland-order-2002`.", + "type": "string" + }, + "section": { + "description": "The section of the act under which the disqualification was made.\n Only applicable if `reason.act` is `company-directors-disqualification-act-1986` or `sanctions-anti-money-laundering-act-2018` or `sanctions-counter-terrorism-regulations-2019`.", + "type": "string" + } + }, + "required": [ + "description_identifier", + "act" + ] + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/errorModel.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/errorModel.json new file mode 100644 index 0000000..b0b16bf --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/errorModel.json @@ -0,0 +1,68 @@ +{ + "definitions": { + "error": { + "title": "error", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "description": "A list of errors found", + "items": { + "$ref": "errorModel.json#/definitions/errorDetail" + } + } + } + }, + "errorDetail": { + "title": "errorDetail", + "required": [ + "type", + "error" + ], + "properties": { + "type": { + "type": "string", + "description": "Type of error", + "enum": [ + "ch:service", + "ch:validation" + ] + }, + "location_type": { + "type": "string", + "description": "Describes the type of location returned so that it may be parsed appropriately", + "enum": [ + "json-path", + "query-parameter" + ] + }, + "location": { + "type": "string", + "description": "The location in the submitted request in which the error relates. This parameter is only provided when errors[].type is set to \"ch:validation\"." + }, + "error": { + "type": "string", + "description": "The error being returned. See github for valid [enumeration types](https://github.com/companieshouse/api-enumerations/blob/develop/errors.yml)" + }, + "error_values": { + "type": "array", + "description": "A collection of argument name and value pairs which, when substituted into the error string, provide the full description of the error. As many name/value pairs as necessary to complete the error description are returned. See example above.", + "items": { + "$ref": "errorModel.json#/definitions/error_values" + } + } + } + }, + "error_values": { + "title": "error_values", + "properties": { + "": { + "type": "string", + "description": "The element name and value pair required to complete the error description, will repeat as necessary." + } + } + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json new file mode 100644 index 0000000..1437855 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json @@ -0,0 +1,241 @@ +{ + "get": { + "description": "Company exemptions information", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number that the exemptions list is required for.", + "required": true, + "type": "string" + } + ], + "tags": [ + "exemptions" + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "exemptions.json#/definitions/companyExemptions" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + }, + "definitions": { + "companyExemptions": { + "title": "companyExemptions", + "required": [ + "links", + "kind", + "etag", + "exemptions" + ], + "properties": { + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "exemptions.json#/definitions/linksType" + }, + "type": "array" + }, + "kind": { + "type": "string", + "enum": [ + "exemptions" + ] + }, + "etag": { + "type": "string", + "description": "The ETag of the resource." + }, + "exemptions": { + "description": "Company exemptions information.", + "items": { + "$ref": "exemptions.json#/definitions/exemptions" + } + } + } + }, + "exemptions": { + "description": "Exemptions information.", + "properties": { + "psc_exempt_as_trading_on_regulated_market": { + "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on a regulated market other than the UK.", + "items": { + "$ref": "exemptions.json#/definitions/pscExemptAsTradingOnRegulatedMarketItem" + } + }, + "psc_exempt_as_shares_admitted_on_market": { + "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on a market listed in the Register of People with Significant Control Regulations 2016.", + "items": { + "$ref": "exemptions.json#/definitions/pscExemptAsSharesAdmittedOnMarketItem" + } + }, + "psc_exempt_as_trading_on_uk_regulated_market": { + "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on a UK regulated market.", + "items": { + "$ref": "exemptions.json#/definitions/pscExemptAsTradingOnUkRegualatedMarketItem" + } + }, + "psc_exempt_as_trading_on_eu_regulated_market": { + "description": "If present the company has been or is exempt from keeping a PSC register, as it has voting shares admitted to trading on an EU regulated market.", + "items": { + "$ref": "exemptions.json#/definitions/pscExemptAsTradingOnEuRegualatedMarketItem" + } + }, + "disclosure_transparency_rules_chapter_five_applies": { + "description": "If present the company has been or is exempt from keeping a PSC register, because it is a DTR issuer and the shares are admitted to trading on a regulated market.", + "items": { + "$ref": "exemptions.json#/definitions/diclosureTransparencyRulesChapterFiveAppliesItem" + } + } + } + }, + "pscExemptAsTradingOnRegulatedMarketItem": { + "properties": { + "items": { + "type": "array", + "description": "List of dates", + "items": { + "$ref": "exemptions.json#/definitions/exemptionItem" + } + }, + "exemption_type": { + "description": "The exemption type.", + "type": "string", + "enum": [ + "psc-exempt-as-trading-on-regulated-market" + ] + } + }, + "required": [ + "exemption_type", + "items" + ] + }, + "pscExemptAsSharesAdmittedOnMarketItem": { + "required": [ + "exemption_type", + "items" + ], + "properties": { + "items": { + "description": "List of dates", + "type": "array", + "items": { + "$ref": "exemptions.json#/definitions/exemptionItem" + } + }, + "exemption_type": { + "description": "The exemption type.", + "enum": [ + "psc-exempt-as-shares-admitted-on-market" + ], + "type": "string" + } + } + }, + "pscExemptAsTradingOnUkRegualatedMarketItem": { + "required": [ + "exemption_type", + "items" + ], + "properties": { + "items": { + "description": "List of dates", + "type": "array", + "items": { + "$ref": "exemptions.json#/definitions/exemptionItem" + } + }, + "exemption_type": { + "description": "The exemption type.", + "enum": [ + "psc-exempt-as-trading-on-uk-regulated-market" + ], + "type": "string" + } + } + }, + "pscExemptAsTradingOnEuRegualatedMarketItem": { + "required": [ + "exemption_type", + "items" + ], + "properties": { + "items": { + "description": "List of dates", + "type": "array", + "items": { + "$ref": "exemptions.json#/definitions/exemptionItem" + } + }, + "exemption_type": { + "description": "The exemption type.", + "enum": [ + "psc-exempt-as-trading-on-eu-regulated-market" + ], + "type": "string" + } + } + }, + "diclosureTransparencyRulesChapterFiveAppliesItem": { + "properties": { + "items": { + "description": "List of exemption periods.", + "items": { + "$ref": "exemptions.json#/definitions/exemptionItem" + }, + "type": "array" + }, + "exemption_type": { + "description": "The exemption type.", + "enum": [ + "disclosure-transparency-rules-chapter-five-applies" + ], + "type": "string" + } + }, + "required": [ + "exemption_type", + "items" + ] + }, + "exemptionItem": { + "properties": { + "exempt_from": { + "description": "Exemption valid from.", + "type": "string", + "format": "date" + }, + "exempt_to": { + "description": "Exemption valid to.", + "type": "string", + "format": "date" + } + }, + "required": [ + "exempt_from" + ] + }, + "linksType": { + "properties": { + "self": { + "description": "The URL of this resource.", + "type": "string" + } + }, + "required": [ + "self" + ] + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json new file mode 100644 index 0000000..65539f7 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json @@ -0,0 +1,348 @@ +{ + "getFilingHistory": { + "get": { + "summary": "filingHistoryItem resource", + "description": "Get the filing history item of a company", + "tags": [ + "filingHistory" + ], + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number that the single filing is required for.", + "required": true, + "type": "string" + }, + { + "name": "transaction_id", + "in": "path", + "description": "The transaction id that the filing history is required for.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Filing history items resource returned", + "schema": { + "$ref": "filingHistory.json#/definitions/filingHistoryItem" + } + }, + "401": { + "description": "Unauthorised", + "schema": { + "$ref": "errorModel.json#/definitions/error" + } + }, + "404": { + "description": "Filing history not available for this company" + } + } + } + }, + "listFilingHistory": { + "get": { + "summary": "filingHistoryList resource", + "description": "Get the filing history list of a company", + "x-operationName": "list", + "tags": [ + "filingHistory" + ], + "parameters": [ + { + "name": "category", + "in": "query", + "description": "One or more comma-separated categories to filter by (inclusive).", + "required": false, + "type": "string" + }, + { + "name": "company_number", + "in": "path", + "description": "The company number that the filing history is required for.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of filing history items to return per page.", + "required": false, + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The index into the entire result set that this result page starts.", + "required": false, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Filing history items resource returned", + "schema": { + "$ref": "filingHistory.json#/definitions/filingHistoryList" + } + }, + "401": { + "description": "Unauthorised", + "schema": { + "$ref": "errorModel.json#/definitions/error" + } + }, + "404": { + "description": "Filing history not available for this company" + } + } + } + }, + "definitions": { + "annotation": { + "title": "annotation", + "required": [ + "date", + "description" + ], + "properties": { + "annotation": { + "description": "The annotation text.", + "type": "string" + }, + "date": { + "type": "string", + "format": "date", + "description": "The date the annotation was added." + }, + "description": { + "type": "string", + "description": "A description of the annotation.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file." + } + } + }, + "associatedFiling": { + "title": "associatedFiling", + "required": [ + "date", + "description", + "type" + ], + "properties": { + "date": { + "description": "The date the associated filing was processed.", + "type": "string", + "format": "date" + }, + "description": { + "type": "string", + "description": "A description of the associated filing.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file." + }, + "type": { + "description": "The type of the associated filing.", + "type": "string" + } + } + }, + "filingHistoryItem": { + "title": "filingHistoryItem", + "required": [ + "category", + "date", + "description", + "type", + "transaction_id" + ], + "properties": { + "annotations": { + "description": "Annotations for the filing", + "items": { + "$ref": "filingHistory.json#/definitions/annotation" + }, + "type": "array" + }, + "associated_filings": { + "description": "Any filings associated with the current item", + "items": { + "$ref": "filingHistory.json#/definitions/associatedFiling" + }, + "type": "array" + }, + "barcode": { + "description": "The barcode of the document.", + "type": "string" + }, + "transaction_id": { + "description": "The transaction ID of the filing.", + "type": "string" + }, + "category": { + "description": "The category of the document filed.", + "enum": [ + "accounts", + "address", + "annual-return", + "capital", + "change-of-name", + "incorporation", + "liquidation", + "miscellaneous", + "mortgage", + "officers", + "resolution" + ], + "type": "string" + }, + "date": { + "description": "The date the filing was processed.", + "type": "string", + "format": "date" + }, + "description": { + "description": "A description of the filing.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file.", + "type": "string" + }, + "links": { + "description": "Links to other resources associated with this filing history item.", + "type": "object", + "items": { + "$ref": "filingHistory.json#/definitions/filingHistoryItemLinks" + } + }, + "pages": { + "description": "Number of pages within the PDF document (links.document_metadata)", + "type": "integer" + }, + "paper_filed": { + "description": "If true, indicates this is a paper filing.", + "type": "boolean" + }, + "resolutions": { + "description": "Resolutions for the filing", + "items": { + "$ref": "filingHistory.json#/definitions/resolution" + }, + "type": "array" + }, + "subcategory": { + "description": "The sub-category of the document filed.", + "enum": [ + "resolution" + ], + "type": "string" + }, + "type": { + "description": "The type of filing.", + "type": "string" + } + } + }, + "filingHistoryItemLinks": { + "title": "filingHistoryItemLinks", + "properties": { + "self": { + "description": "Link to this filing history item.", + "type": "string" + }, + "document_metadata": { + "description": "Link to the document metadata associated with this filing history item. See the Document API documentation for more details.", + "type": "string" + } + } + }, + "filingHistoryList": { + "title": "filingHistoryList", + "required": [ + "etag", + "items", + "items_per_page", + "kind", + "start_index", + "total_count" + ], + "properties": { + "filing_history_status": { + "description": "The status of this filing history.", + "type": "string", + "enum": [ + "filing-history-available" + ] + }, + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "items": { + "description": "The filing history items.", + "items": { + "$ref": "filingHistory.json#/definitions/filingHistoryItem" + }, + "type": "array" + }, + "items_per_page": { + "description": "The number of filing history items returned per page.", + "type": "integer" + }, + "kind": { + "description": "Indicates this resource is a filing history.", + "enum": [ + "filing-history" + ], + "type": "string" + }, + "start_index": { + "description": "The index into the entire result set that this result page starts.", + "type": "integer" + }, + "total_count": { + "description": "The total number of filing history items for this company.", + "type": "integer" + } + } + }, + "resolution": { + "title": "resolution", + "required": [ + "category", + "description", + "receive_date", + "subcategory", + "type" + ], + "properties": { + "category": { + "description": "The category of the resolution filed.", + "enum": [ + "miscellaneous" + ], + "type": "string" + }, + "description": { + "description": "A description of the associated filing.\n For enumeration descriptions see `description` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/filing_history_descriptions.yml) file.", + "type": "string" + }, + "document_id": { + "description": "The document id of the resolution.", + "type": "string" + }, + "receive_date": { + "description": "The date the resolution was processed.", + "type": "string", + "format": "date" + }, + "subcategory": { + "description": "The sub-category of the document filed.", + "enum": [ + "resolution" + ], + "type": "string" + }, + "type": { + "description": "The type of the associated filing.", + "type": "string" + } + } + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json new file mode 100644 index 0000000..f8d14e4 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json @@ -0,0 +1,1316 @@ +{ + "insolvencyCase": { + "get": { + "description": "Company insolvency information", + "tags": [ + "insolvency" + ], + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the basic information to return.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Company insolvency resource returned", + "schema": { + "$ref": "insolvency.json#/definitions/companyInsolvency" + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "createInsolvency": { + "post": { + "summary": "Create an insolvency transaction resource", + "description": "Create an insolvency transaction resource", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "create insolvency resource", + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "description": "The transaction unique reference", + "type": "string" + }, + { + "name": "request", + "in": "body", + "description": "The writable fields to create an insolvency data resource", + "required": false, + "schema": { + "$ref": "../models/insolvency.json#/definitions/insolvencyResourceWritable" + } + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "201": { + "description": "The insolvency data change resource was created.", + "schema": { + "$ref": "../models/insolvency.json#/definitions/createdInsolvencyResource" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Transaction not found" + }, + "409": { + "description": "Insolvency resource already exists." + } + } + } + }, + "insolvencyTransactionsValidation": { + "get": { + "summary": "Validate insolvency transaction resource", + "description": "Validate insolvency transaction resource", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "validate insolvency resource", + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "description": "The transaction unique reference", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "A validation status response was returned (note: this does not mean there were no validation errors)", + "schema": { + "$ref": "../models/errors.json#/definitions/validationStatus" + } + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "practitioners": { + "get": { + "summary": "Get all practitioners", + "description": "Get all practitioner resources associated with a single insolvency transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "get all practitioners", + "parameters": [{ + "name": "transaction_id", + "in": "path", + "required": true, + "description": "The transaction that this insolvency case is applied to", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "An array of all practitioner resources associated with the insolvency case", + "schema": { + "$ref": "../models/insolvency.json#/definitions/allPractitioners" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Transaction not found" + } + + } + }, + "post": { + "summary": "Create a practitioner for this insolvency resource", + "description": "Create a practitioner for this insolvency resource", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "create practitioner", + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "description": "The transaction that this insolvency case is applied to", + "type": "string" + }, + { + "name": "request", + "in": "body", + "description": "The writable fields to create practitioner resource", + "schema": { + "$ref": "../models/insolvency.json#/definitions/practitionerWritable" + } + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "201": { + "description": "Practitioner created", + "schema": { + "$ref": "../models/insolvency.json#/definitions/practitioner" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "practitionerTransactions": { + "get": { + "summary": "Get the practitioner resource", + "description": "Get the practitioner resource", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "get practitioner", + "parameters": [{ + "name": "transaction_id", + "in": "path", + "required": true, + "description": "The transaction that this insolvency case is applied to", + "type": "string" + }, + { + "name": "practitioner_id", + "in": "path", + "required": true, + "description": "The unique practitioner id", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "The practitioner resource", + "schema": { + "$ref": "../models/insolvency.json#/definitions/practitioner" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Transaction not found" + } + + } + }, + "delete": { + "summary": "Delete the practitioner from this insolvency resource", + "description": "Delete the practitioner from this insolvency resource", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "delete practitioner", + "parameters": [{ + "name": "transaction_id", + "in": "path", + "required": true, + "description": "The transaction that this insolvency case is applied to", + "type": "string" + }, + { + "name": "practitioner_id", + "in": "path", + "required": true, + "description": "The unique practitioner id", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "204": { + "description": "The practitioner was deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Transaction not found" + } + + } + } + }, + "appointmentTransactions": { + "post": { + "summary": "Appoint the practitioner", + "description": "Appoint the practitioner", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "create appointment", + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "description": "The transaction that this insolvency case is applied to", + "type": "string" + }, + { + "name": "practitioner_id", + "in": "path", + "required": true, + "description": "The unique practitioner id", + "type": "string" + }, + { + "name": "request", + "in": "body", + "required": true, + "description": "Appointment details", + "schema": { + "$ref": "../models/insolvency.json#/definitions/appointment" + } + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "201": { + "description": "Practitioner appointed", + "schema": { + "$ref": "../models/insolvency.json#/definitions/practitionerAppointment" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Transaction not found" + } + } + }, + "get": { + "summary": "Get the appointment details", + "description": "Get the appointment details", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "get appointment", + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "description": "The transaction that this insolvency case is applied to", + "type": "string" + }, + { + "name": "practitioner_id", + "in": "path", + "required": true, + "description": "The unique practitioner id", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "The appointment details", + "schema": { + "$ref": "../models/insolvency.json#/definitions/practitionerAppointment" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Transaction not found" + } + } + }, + "delete": { + "summary": "Delete the appointment resource", + "description": "Delete the appointment resource", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "delete appointment", + "parameters": [{ + "name": "transaction_id", + "in": "path", + "required": true, + "description": "The transaction that this insolvency case is applied to", + "type": "string" + }, + { + "name": "practitioner_id", + "in": "path", + "required": true, + "description": "The unique practitioner id", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "204": { + "description": "The appointment was deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Transaction not found" + } + + } + } + }, + "createAttachment": { + "post": { + "summary": "Send a file attachment for the case", + "description": "Send a file attachment for the case", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "create attachment", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + }, + { + "name": "request", + "in": "body", + "required": true, + "description": "The attachment details", + "schema": { + "$ref": "../models/insolvency.json#/definitions/attachmentWriteable" + } + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "201": { + "description": "The file was accepted for processing", + "schema": { + "$ref": "../models/insolvency.json#/definitions/createdAttachment" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Transaction not found" + } + } + } + }, + "attachmentTransactions": { + "get": { + "summary": "Get information about the attachment that was submitted", + "description": "Get information about the attachment that was submitted", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "get attachment", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + }, + { + "name": "attachment_id", + "in": "path", + "required": true, + "description": "The unique attachment id", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "the attachment resource", + "schema": { + "$ref": "../models/insolvency.json#/definitions/createdAttachment" + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "attachment not found" + } + } + }, + "delete": { + "summary": "Delete an attachment from this transaction", + "description": "Delete an attachment from this transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "delete attachment", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + }, + { + "name": "attachment_id", + "in": "path", + "required": true, + "description": "The unique attachment id", + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "204": { + "description": "The attachment was deleted" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "createResolution": { + "post": { + "summary": "Send resolution details for this transaction", + "description": "Send resolution details for this transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "create resolution", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + }, + { + "name": "request", + "in": "body", + "required": true, + "description": "The resolution details", + "schema": { + "$ref": "../models/insolvency.json#/definitions/resolutionResourceWriteable" + } + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "201": { + "description": "The resolution details was sent correctly", + "schema": { + "$ref": "../models/insolvency.json#/definitions/Resolution" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "attachment not found on transaction" + } + } + }, + "get": { + "summary": "Get the resolution details", + "description": "Get the resolution details for the transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "get resolution details", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "The resolution details", + "schema": { + "$ref": "../models/insolvency.json#/definitions/Resolution" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "resolution not found on transaction" + } + } + }, + "delete": { + "summary": "Delete the resolution date", + "description": "Delete the resolution date for the transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "delete resolution", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "204": { + "description": "The resolution date was deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + } + } + }, + "downloadAttachment": { + "get": { + "summary": "Download the attachment", + "description": "Download an attachment", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "download attachment", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + }, + { + "name": "attachment_id", + "in": "path", + "description": "The unique attachment id", + "required": true, + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "The file will begin to download", + "schema": { + "$ref": "../models/insolvency.json#/definitions/downloadedAttachment" + } + }, + "404": { + "description": "attachment not found on transaction" + } + } + } + + }, + "statementOfAffairs": { + "post": { + "summary": "Send statement of affairs details for this transaction", + "description": "Create the statement of affairs for this transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "create statement of affairs", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + }, + { + "name": "request", + "in":"body", + "required":true, + "description":"The statement of affairs date", + "schema": { + "$ref": "../models/insolvency.json#/definitions/statementOfAffairsWriteable" + } + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "201": { + "description": "The statement of affairs date was sent correctly", + "schema": { + "$ref": "../models/insolvency.json#/definitions/statementOfAffairs" + } + }, + "401": { + "description": "Unauthorized" + }, + "400": { + "description": "Bad request" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "attachment not found on transaction" + } + } + }, + "get": { + "summary": "Get Statement of Affairs", + "description": "Get the statement of affairs details for the transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "get statement of affairs", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "The statement of affairs details", + "schema": { + "$ref": "../models/insolvency.json#/definitions/statementOfAffairs" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "The statement of affairs was not found" + } + } + }, + "delete": { + "summary":"Delete the statement of affairs", + "description": "Delete the statement of affairs date for the transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "delete statement of affairs", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "204": { + "description": "The statement of affairs date was deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + } + } + }, + "progressReport": { + "post": { + "summary": "Send progress report details for this transaction", + "description": "Create the progress report for this transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "create progress report", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + }, + { + "name": "request", + "in": "body", + "required": true, + "description": "The progress report dates and attachment ID", + "schema": { + "$ref": "../models/insolvency.json#/definitions/progressReportWriteable" + } + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "201": { + "description": "Progress report resource created", + "content": "application/json", + "schema": { + "$ref": "../models/insolvency.json#/definitions/progressReport" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "attachment not found on transaction" + } + } + }, + "get": { + "summary": "Get Progress Report", + "description": "Get the progress report details for the transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "get progress report", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "200": { + "description": "The progress report details", + "schema": { + "$ref": "../models/insolvency.json#/definitions/progressReport" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "The progress report was not found" + } + } + }, + "delete": { + "summary": "Delete the progress report", + "description": "Delete the progress report dates for the transaction", + "tags": [ + "insolvencyApi" + ], + "x-operationName": "delete progress report", + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "description": "The transaction unique reference", + "required": true, + "type": "string" + } + ], + "security": [ + { + "oauth2": [ + "https://identity.company-information.service.gov.uk/user/profile.read", + "https://api.company-information.service.gov.uk/company/*/insolvency.write-full" + ] + } + ], + "responses": { + "204": { + "description": "The progress report dates were deleted" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + } + } + }, + "definitions": { + "companyInsolvency": { + "title": "companyInsolvency", + "required": [ + "etag", + "cases" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "cases": { + "type": "array", + "description": "List of insolvency cases.", + "items": { + "$ref": "insolvency.json#/definitions/case" + } + }, + "status": { + "type": "string", + "description": "Company insolvency status details", + "enum": [ + "administration-order", + "administrative-receiver", + "in-administration", + "liquidation", + "live-receiver-manager-on-at-least-one-charge", + "receivership", + "receiver-manager", + "voluntary-arrangement", + "voluntary-arrangement-receivership" + ] + } + } + }, + "case": { + "title": "case", + "required": [ + "type", + "dates", + "practitioners" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "compulsory-liquidation", + "creditors-voluntary-liquidation", + "members-voluntary-liquidation", + "in-administration", + "corporate-voluntary-arrangement", + "corporate-voluntary-arrangement-moratorium", + "administration-order", + "receiver-manager", + "administrative-receiver", + "receivership", + "foreign-insolvency", + "moratorium" + ], + "description": "The type of case.\n For enumeration descriptions see `insolvency_case_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml)." + }, + "dates": { + "type": "array", + "description": "The dates specific to the case.", + "items": { + "$ref": "insolvency.json#/definitions/caseDates" + } + }, + "notes": { + "type": "array", + "description": "The dates specific to the case.", + "items": { + "type": "string" + } + }, + "practitioners": { + "type": "array", + "description": "The practitioners for the case.", + "items": { + "$ref": "insolvency.json#/definitions/practitioners" + } + }, + "links": { + "type": "object", + "description": "The practitioners for the case.", + "items": { + "$ref": "insolvency.json#/definitions/links" + } + }, + "number": { + "type": "string", + "description": "The case number." + } + } + }, + "caseDates": { + "title": "caseDates", + "required": [ + "type", + "date" + ], + "properties": { + "type": { + "type": "string", + "description": "Describes what date is represented by the associated `date` element.\n For enumeration descriptions see `insolvency_case_date_type` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml).", + "enum": [ + "instrumented-on", + "administration-started-on", + "administration-discharged-on", + "administration-ended-on", + "concluded-winding-up-on", + "petitioned-on", + "ordered-to-wind-up-on", + "due-to-be-dissolved-on", + "case-end-on", + "wound-up-on", + "voluntary-arrangement-started-on", + "voluntary-arrangement-ended-on", + "moratorium-started-on", + "moratorium-ended-on", + "declaration-solvent-on" + ] + }, + "date": { + "type": "string", + "format": "date", + "description": "The case date, described by `date_type`." + } + } + }, + "practitioners": { + "title": "practitioners", + "required": [ + "name", + "address" + ], + "properties": { + "name": { + "description": "The name of the practitioner.", + "type": "string" + }, + "address": { + "type": "array", + "description": "The practitioners' address.", + "items": { + "$ref": "insolvency.json#/definitions/practitionerAddress" + } + }, + "appointed_on": { + "type": "string", + "format": "date", + "description": "The date the practitioner was appointed on." + }, + "ceased_to_act_on": { + "type": "string", + "format": "date", + "description": "The date the practitioner ceased to act for the case." + }, + "role": { + "type": "string", + "description": "The type of role.", + "enum": [ + "final-liquidator", + "receiver", + "receiver-manager", + "proposed-liquidator", + "provisional-liquidator", + "administrative-receiver", + "practitioner", + "interim-liquidator" + ] + } + } + }, + "practitionerAddress": { + "title": "practitionerAddress", + "required": [ + "address_line_1" + ], + "properties": { + "address_line_1": { + "type": "string", + "description": "The first line of the address." + }, + "address_line_2": { + "type": "string", + "description": "The second line of the address." + }, + "locality": { + "type": "string", + "description": "The locality. For example London." + }, + "region": { + "type": "string", + "description": "The region. For example Surrey." + }, + "postal_code": { + "type": "string", + "description": "The postal code. For example CF14 3UZ." + }, + "country": { + "type": "string", + "description": "The country." + } + } + }, + "links": { + "title": "links", + "properties": { + "charge": { + "type": "string", + "description": "The link to the charge this case is lodged against." + } + } + } + } +} + + + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json new file mode 100644 index 0000000..8723a87 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json @@ -0,0 +1,452 @@ +{ + "get": { + "summary": "Officer Appointment List", + "tags": [ + "officerAppointments" + ], + "x-operationName": "list", + "description": "List of all officer appointments", + "parameters": [ + { + "name": "officer_id", + "in": "path", + "description": "The officer id of the appointment list being requested.", + "required": true, + "type": "string" + }, + { + "name": "filter", + "in": "query", + "description": "Use “active” to return only active appointments.", + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of appointments to return per page.", + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the items_per_page parameter.", + "type": "integer" + } + ], + "responses": { + "200": { + "description": "List the officer appointments", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "officerAppointmentList.json#/definitions/appointmentList" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorised" + } + } + }, + "definitions": { + "appointmentList": { + "title": "appointmentList", + "properties": { + "date_of_birth": { + "description": "The officer's date of birth details.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/dateOfBirth" + }, + "type": "object" + }, + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "is_corporate_officer": { + "description": "Indicator representing if the officer is a corporate body.", + "type": "boolean" + }, + "items": { + "description": "The list of officer appointments.", + "type": "array", + "items": { + "$ref": "officerAppointmentList.json#/definitions/officerAppointmentSummary" + } + }, + "items_per_page": { + "description": "The number of officer appointments to return per page.", + "type": "integer" + }, + "kind": { + "enum": [ + "personal-appointment" + ], + "type": "string" + }, + "links": { + "description": "Links to other resources associated with this officer appointment resource.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/officerLinkTypes" + }, + "type": "object" + }, + "name": { + "description": "The corporate or natural officer name.", + "type": "string" + }, + "start_index": { + "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the `items_per_page` parameter.", + "type": "integer" + }, + "total_results": { + "description": "The total number of officer appointments in this result set.", + "type": "integer" + } + }, + "required": [ + "etag", + "is_corporate_officer", + "items", + "items_per_page", + "kind", + "links", + "name", + "start_index", + "total_results" + ] + }, + "officerAppointmentSummary": { + "title": "officerAppointmentSummary", + "properties": { + "address": { + "description": "The correspondence address of the officer.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/address" + }, + "type": "object" + }, + "appointed_before": { + "description": "The date the officer was appointed before. Only present when the `is_pre_1992_appointment` attribute is `true`.", + "type": "string", + "format": "date" + }, + "appointed_on": { + "description": "The date on which the officer was appointed. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officer.", + "type": "string", + "format": "date" + }, + "appointed_to": { + "description": "The company information of the appointment.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/appointedTo" + }, + "type": "object" + }, + "contact_details": { + "description": "The contact at the `corporate-managing-officer` of a `registered-overseas-entity`.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/contactDetails" + }, + "type": "object" + }, + "name": { + "description": "The full name of the officer.", + "type": "string" + }, + "country_of_residence": { + "description": "The officer's country of residence.", + "type": "string" + }, + "former_names": { + "description": "Former names for the officer, if there are any.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/formerNames" + }, + "type": "array" + }, + "identification": { + "description": "Only one from `eea`, `non-eea`, `uk-limited-company`, `other-corporate-body-or-firm` or `registered-overseas-entity-corporate-managing-officer` can be supplied, not multiples of them.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/corporateIdent" + }, + "type": "object" + }, + "identity_verification_details": { + "description": "Information relating to the identity verification of the officer", + "items": { + "$ref": "../models/officerChanges.json#/definitions/identityVerificationDetails" + }, + "type": "object" + }, + "is_pre_1992_appointment": { + "description": "Indicator representing if the officer was appointed before their appointment date.", + "type": "boolean" + }, + "links": { + "description": "Links to other resources associated with this officer appointment item.", + "type": "object", + "items": { + "$ref": "officerAppointmentList.json#/definitions/appointmentLinkTypes" + } + }, + "name_elements": { + "description": "A document encapsulating the separate elements of a natural officer's name.", + "type": "object", + "items": { + "$ref": "officerAppointmentList.json#/definitions/nameElements" + } + }, + "nationality": { + "description": "The officer's nationality.", + "type": "string" + }, + "occupation": { + "description": "The officer's occupation.", + "type": "string" + }, + "officer_role": { + "enum": [ + "cic-manager", + "corporate-director", + "corporate-llp-designated-member", + "corporate-llp-member", + "corporate-managing-officer", + "corporate-member-of-a-management-organ", + "corporate-member-of-a-supervisory-organ", + "corporate-member-of-an-administrative-organ", + "corporate-nominee-director", + "corporate-nominee-secretary", + "corporate-secretary", + "director", + "judicial-factor", + "llp-designated-member", + "llp-member", + "managing-officer", + "member-of-a-management-organ", + "member-of-a-supervisory-organ", + "member-of-an-administrative-organ", + "nominee-director", + "nominee-secretary", + "receiver-and-manager", + "secretary" + ], + "type": "string" + }, + "principal_office_address": { + "description": "The principal/registered office address of a `corporate-managing-officer` of a `registered-overseas-entity`.", + "items": { + "$ref": "officerAppointmentList.json#/definitions/address" + }, + "type": "object" + }, + "resigned_on": { + "description": "The date the officer was resigned. For the officer roles of `corporate-managing-officer` and `managing-officer` this is the date on which Companies House was notified about the officers cessation.", + "type": "string", + "format": "date" + }, + "responsibilities": { + "description": "The responsibilities of the managing officer of a `registered-overseas-entity`.", + "type": "string" + } + }, + "required": [ + "appointed_to", + "links", + "name", + "officer_role" + ] + }, + "address": { + "title": "address", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "country": { + "description": "The country. For example, UK.", + "type": "string" + }, + "locality": { + "description": "The locality. For example London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code. For example CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region. For example Surrey.", + "type": "string" + } + } + }, + "appointedTo": { + "title": "appointedTo", + "properties": { + "company_name": { + "description": "The name of the company the officer is acting for.", + "type": "string" + }, + "company_number": { + "description": "The number of the company the officer is acting for.", + "type": "string" + }, + "company_status": { + "description": "The status of the company the officer is acting for.", + "type": "string" + } + }, + "required": [ + "company_number" + ] + }, + "contactDetails": { + "title": "contactDetails", + "properties": { + "contact_name": { + "description": "The name of the contact.", + "type": "string" + } + } + }, + "dateOfBirth": { + "title": "dateOfBirth", + "properties": { + "month": { + "description": "The month the officer was born in.", + "type": "integer" + }, + "year": { + "description": "The year the officer was born in.", + "type": "integer" + } + }, + "required": [ + "month", + "year" + ] + }, + "formerNames": { + "title": "formerNames", + "properties": { + "forenames": { + "description": "Former forenames of the officer.", + "type": "string" + }, + "surname": { + "description": "Former surnames of the officer.", + "type": "string" + } + } + }, + "officerLinkTypes": { + "title": "officerLinkTypes", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "Link to this officer appointment resource.", + "type": "string" + } + } + }, + "appointmentLinkTypes": { + "title": "appointmentLinkTypes", + "required": [ + "company" + ], + "properties": { + "company": { + "description": "Link to the company profile resource that this appointment is associated with.", + "type": "string" + } + } + }, + "corporateIdent": { + "title": "corporateIdent", + "properties": { + "identification_type": { + "description": "The officer's identity type", + "enum": [ + "eea", + "non-eea", + "uk-limited-company", + "other-corporate-body-or-firm", + "registered-overseas-entity-corporate-managing-officer" + ], + "type": "string" + }, + "legal_authority": { + "description": "The legal authority supervising the company.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the company as defined by its country of registration.", + "type": "string" + }, + "place_registered": { + "description": "Place registered.", + "type": "string" + }, + "registration_number": { + "description": "Company registration number.", + "type": "string" + } + } + }, + "nameElements": { + "title": "nameElements", + "properties": { + "forename": { + "description": "The forename of the officer.", + "type": "string" + }, + "title": { + "description": "Title of the officer.", + "type": "string" + }, + "other_forenames": { + "description": "Other forenames of the officer.", + "type": "string" + }, + "surname": { + "description": "The surname of the officer.", + "type": "string" + }, + "honours": { + "description": "Honours an officer might have.", + "type": "string" + } + }, + "required": [ + "surname" + ] + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json new file mode 100644 index 0000000..354843f --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json @@ -0,0 +1,538 @@ +{ + "listCompanyPSC": { + "get": { + "summary": "List the company persons with significant control", + "description": "List of all persons with significant control (not statements)", + "x-operationName": "list", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the persons with significant control list being requested.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of persons with significant control to return per page.", + "required": true, + "type": "string" + }, + { + "name": "start_index", + "in": "query", + "description": "The offset into the entire result set that this page starts.", + "required": true, + "type": "string" + }, + { + "name": "register_view", + "in": "query", + "description": "Display register specific information. If register is held at Companies House and register_view is set to true, only PSCs which are active or were terminated during election period are shown. Accepted values are: -`true` \n -`false` \n Defaults to false.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "readCompanyProfile", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/list" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getIndividualPSC": { + "get": { + "summary": "Get the individual person with significant control notification", + "description": "Get details of the individual person with significant control notification", + "x-operationName": "get individual", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the person with significant control details being requested.", + "required": true, + "type": "string" + }, + { + "name": "notification_id", + "in": "path", + "description": "The notification id of the person with significant control notification being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "IndividualPSC resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/individual" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getIndividualBO": { + "get": { + "summary": "Get the individual beneficial owner notification", + "description": "Get details of an individual beneficial owner notification", + "x-operationName": "get individual beneficial owner", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the individual beneficial owner details being requested.", + "required": true, + "type": "string" + }, + { + "name": "notification_id", + "in": "path", + "description": "The notification id of the individual beneficial owner notification being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "IndividualBO resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/individualBeneficialOwner" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getCorporateEntityPSC": { + "get": { + "summary": "Get the corporate entity with significant control notification", + "description": "Get details of a corporate entity with significant control notification", + "x-operationName": "get corporate entities", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the corporate entity with significant control details being requested.", + "required": true, + "type": "string" + }, + { + "name": "notification_id", + "in": "path", + "description": "The notification id of the corporate entity with significant control notification being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "CorporateEntityPSC resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/corporateEntity" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getCorporateEntityBO": { + "get": { + "summary": "Get the corporate entity beneficial owner notification", + "description": "Get details of the corporate entity beneficial owner notification", + "x-operationName": "get corporate entity beneficial owner", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the corporate entity beneficial owner details being requested.", + "required": true, + "type": "string" + }, + { + "name": "notification_id", + "in": "path", + "description": "The notification id of the corporate entity beneficial owner notification being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "CorporateEntityBO resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/corporateEntityBeneficialOwner" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getLegalPersonPSC": { + "get": { + "summary": "Get the legal person with significant control notification", + "description": "Get details of the legal person with significant control notification", + "x-operationName": "get legal persons", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the legal person with significant control details being requested.", + "required": true, + "type": "string" + }, + { + "name": "notification_id", + "in": "path", + "description": "The notification id of the legal person with significant control notification being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "LegalPersonPSC resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/legalPerson" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getLegalPersonBO": { + "get": { + "summary": "Get the legal person beneficial owner notification", + "description": "Get details of the legal person beneficial owner notification", + "x-operationName": "get legal person beneficial owner", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the legal person beneficial owner details being requested.", + "required": true, + "type": "string" + }, + { + "name": "notification_id", + "in": "path", + "description": "The notification id of the legal person beneficial owner notification being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "LegalPersonBO resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/legalPersonBeneficialOwner" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "listCompanyPSCStatements": { + "get": { + "summary": "List the company persons with significant control statements", + "description": "List of all persons with significant control statements", + "x-operationName": "list statements", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the persons with significant control statements list being requested.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The id of the legal person with significant control details being requested.", + "required": true, + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The offset into the entire result set that this page starts.", + "required": true, + "type": "integer" + }, + { + "name": "register_view", + "in": "query", + "description": "Display register specific information. If register is held at Companies House and register_view is set to true, only statements which are active or were withdrawn during election period are shown. Accepted values are: -`true` \n -`false` \n Defaults to false.", + "required": true, + "type": "query" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "CompanyPSCStatements resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/statementList" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getPSCStatement": { + "get": { + "summary": "Get the person with significant control statement", + "description": "Get details of a person with significant control statement", + "x-operationName": "get statement", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the persons with significant control statements list being requested.", + "required": true, + "type": "string" + }, + { + "name": "statement_id", + "in": "path", + "description": "The id of the person with significant control statement details being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "PSCStatement resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/statement" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getSuperSecurePSC": { + "get": { + "summary": "Get the super secure person with significant control", + "description": "Get details of a super secure person with significant control", + "x-operationName": "get super secure person", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the super secure person with significant control details being requested.", + "required": true, + "type": "string" + }, + { + "name": "super_secure_id", + "in": "path", + "description": "The id of the super secure person with significant control details being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "SuperSecurePSC resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/superSecure" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + }, + "getSuperSecureBO": { + "get": { + "summary": "Get the super secure beneficial owner", + "description": "Get details of a super secure beneficial owner", + "x-operationName": "get super secure beneficial owner", + "parameters": [ + { + "name": "company_number", + "in": "path", + "description": "The company number of the super secure beneficial owner details being requested.", + "required": true, + "type": "string" + }, + { + "name": "super_secure_id", + "in": "path", + "description": "The id of the super secure beneficial owner details being requested.", + "required": true, + "type": "string" + } + ], + "tags": [ + "personsWithSignificantControl" + ], + "responses": { + "200": { + "description": "SuperSecureBO resource returned", + "headers": { + "ETag": { + "description": "The ETag of the resource.", + "type": "string" + } + }, + "schema": { + "$ref": "pscModels.json#/definitions/superSecureBeneficialOwner" + } + }, + "401": { + "description": "Unauthorised" + }, + "404": { + "description": "Resource not found" + } + } + } + } +} + + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscModels.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscModels.json new file mode 100644 index 0000000..51559eb --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscModels.json @@ -0,0 +1,1598 @@ +{ + "definitions": { + "address": { + "title": "pscAddress", + "required": [ + "address_line_1", + "postal_code", + "premises" + ], + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "Care of name.", + "type": "string" + }, + "country": { + "description": "The country. For example, UK.", + "type": "string" + }, + "locality": { + "description": "The locality. For example London.", + "type": "string" + }, + "po_box": { + "description": "The post-officer box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code. For example CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region. For example Surrey.", + "type": "string" + } + } + }, + "beneficialOwnerAddress": { + "title": "beneficialOwnerAddress", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "country": { + "description": "The country. For example, United Kingdom.", + "type": "string" + }, + "locality": { + "description": "The locality. For example London.", + "type": "string" + }, + "po_box": { + "description": "The post-officer box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code. For example CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region. For example Surrey.", + "type": "string" + } + } + }, + "list": { + "title": "list", + "properties": { + "items_per_page": { + "description": "The number of persons with significant control to return per page.", + "type": "integer" + }, + "items": { + "description": "The list of persons with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/listTwoSummary" + }, + "type": "array" + }, + "start_index": { + "description": "The offset into the entire result set that this page starts.", + "type": "integer" + }, + "total_results": { + "description": "The total number of persons with significant control in this result set.", + "type": "integer" + }, + "active_count": { + "description": "The number of active persons with significant control in this result set.", + "type": "integer" + }, + "ceased_count": { + "description": "The number of ceased persons with significant control in this result set.", + "type": "integer" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscListLinksType" + }, + "type": "object" + } + }, + "required": [ + "items_per_page", + "items", + "start_index", + "total_results", + "active_count", + "ceased_count", + "links" + ] + }, + "individualList": { + "title": "individualList", + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "items_per_page": { + "description": "The number of individual persons with significant control to return per page.", + "type": "integer" + }, + "kind": { + "enum": [ + "persons-with-significant-control#list-individual" + ], + "type": "string" + }, + "items": { + "description": "The list of individual persons with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/listSummary" + }, + "type": "object" + }, + "start_index": { + "description": "The offset into the entire result set that this page starts.", + "type": "integer" + }, + "total_results": { + "description": "The total number of individual persons with significant control in this result set.", + "type": "integer" + }, + "active_count": { + "description": "The number of active persons with significant control in this result set.", + "type": "integer" + }, + "ceased_count": { + "description": "The number of ceased persons with significant control in this result set.", + "type": "integer" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "type": "object", + "items": { + "$ref": "pscModels.json#/definitions/pscListLinksType" + } + } + }, + "required": [ + "etag", + "items_per_page", + "kind", + "items", + "start_index", + "total_results", + "active_count", + "ceased_count", + "links" + ] + }, + "corporateEntityList": { + "title": "corporateEntityList", + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "items_per_page": { + "description": "The number of corporate entity persons with significant control to return per page.", + "type": "integer" + }, + "kind": { + "enum": [ + "persons-with-significant-control#list-corporate-entity" + ], + "type": "string" + }, + "items": { + "description": "The list of corporate entity persons with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/listSummary" + }, + "type": "object" + }, + "start_index": { + "description": "The offset into the entire result set that this page starts.", + "type": "integer" + }, + "total_results": { + "description": "The total number of corporate entity persons with significant control in this result set.", + "type": "integer" + }, + "active_count": { + "description": "The number of active persons with significant control in this result set.", + "type": "integer" + }, + "ceased_count": { + "description": "The number of ceased persons with significant control in this result set.", + "type": "integer" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscListLinksType" + }, + "type": "object" + } + }, + "required": [ + "etag", + "items_per_page", + "kind", + "items", + "start_index", + "total_results", + "active_count", + "ceased_count", + "links" + ] + }, + "legalPersonList": { + "title": "legalPersonList", + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "items_per_page": { + "description": "The number of legal persons with significant control to return per page.", + "type": "integer" + }, + "kind": { + "enum": [ + "persons-with-significant-control#list-legal-person" + ], + "type": "string" + }, + "items": { + "description": "The list of legal persons with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/listSummary" + }, + "type": "object" + }, + "start_index": { + "description": "The offset into the entire result set that this page starts.", + "type": "integer" + }, + "total_results": { + "description": "The total number of legal persons with significant control in this result set.", + "type": "integer" + }, + "active_count": { + "description": "The number of active persons with significant control in this result set.", + "type": "integer" + }, + "ceased_count": { + "description": "The number of ceased persons with significant control in this result set.", + "type": "integer" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscListLinksType" + }, + "type": "object" + } + }, + "required": [ + "etag", + "items_per_page", + "kind", + "items", + "start_index", + "total_results", + "active_count", + "ceased_count", + "links" + ] + }, + "pscLinksType": { + "title": "pscLinksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + }, + "statement": { + "description": "The URL of the statement linked to this person with significant control.", + "type": "string" + }, + "persons_with_significant_control": { + "description": "Links to other persons with significant control resources associated with this person with significant control.", + "type": "object", + "properties": { + "notifications": { + "description": "Link to the persons with significant control notification resource that this notification is associated with.", + "type": "string" + } + } + } + } + }, + "pscItemsListLinksType": { + "title": "pscLinksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + }, + "statement": { + "description": "The URL of the statement linked to this person with significant control.", + "type": "string" + }, + "persons_with_significant_control": { + "description": "Links to other persons with significant control resources associated with this person with significant control list item.", + "type": "object", + "properties": { + "notifications": { + "description": "Link to the persons with significant control notification resource that this notification is associated with.", + "type": "string" + } + } + } + } + }, + "beneficialOwnerLinksType": { + "title": "beneficialOwnerLinksType", + "required": [ + "self" + ], + "properties": { + "persons_with_significant_control": { + "description": "Links to other persons with significant control resources associated with this person with significant control.", + "type": "object", + "properties": { + "notifications": { + "description": "Link to the persons with significant control notification resource that this notification is associated with.", + "type": "string" + } + } + }, + "self": { + "description": "The URL of the resource.", + "type": "string" + }, + "statement": { + "description": "The URL of the statement linked to this beneficial owner.", + "type": "string" + } + } + }, + "pscListLinksType": { + "title": "pscListLinksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + }, + "persons_with_significant_control_list": { + "description": "The URL of the person with significant control list resource.", + "notifications": "string", + "type": "string" + } + } + }, + "statementListLinksType": { + "title": "statementListLinksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + }, + "persons_with_significant_control_statements_list": { + "description": "The URL of the persons with significant control statements list resource.", + "type": "string" + } + } + }, + "statementLinksType": { + "title": "statementLinksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + }, + "person_with_significant_control": { + "description": "The URL of the person with significant control linked to this statement.", + "type": "string" + } + } + }, + "superSecureLinksType": { + "title": "superSecureLinksType", + "required": [ + "self" + ], + "properties": { + "self": { + "description": "The URL of the resource.", + "type": "string" + } + } + }, + "listSummary": { + "title": "listSummary", + "required": [ + "etag", + "name", + "links", + "address", + "notified_on", + "natures_of_control" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this person with significant control.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this person with significant control.", + "type": "string", + "format": "date" + }, + "country_of_residence": { + "description": "The country of residence of the person with significant control.", + "type": "string" + }, + "date_of_birth": { + "description": "The date of birth of the person with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/dateOfBirthPSCList" + }, + "type": "object" + }, + "name": { + "description": "Name of the person with significant control.", + "type": "string" + }, + "name_elements": { + "description": "A document encapsulating the separate elements of a person with significant control's name.", + "items": { + "$ref": "pscModels.json#/definitions/nameElements" + }, + "type": "object" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscLinksType" + }, + "type": "object" + }, + "nationality": { + "description": "The nationality of the person with significant control.", + "type": "string" + }, + "identification": { + "description": "", + "items": { + "$ref": "pscModels.json#/definitions/pscListIdent" + }, + "type": "object" + }, + "identity_verification_details": { + "description": "Information relating to the identity verification of the person with significant control", + "items": { + "$ref": "pscModels.json#/definitions/identityVerificationDetails" + }, + "type": "object" + }, + "ceased": { + "description" : "Presence of that indicator means the super secure person status is ceased
", + "type": "boolean" + }, + "description": { + "description" : "Description of the super secure legal statement
", + "enum": [ + "super-secure-persons-with-significant-control" + ], + "type": "string" + }, + "kind": { + "enum": [ + "individual-person-with-significant-control", + "corporate-entity-person-with-significant-control", + "legal-person-with-significant-control", + "super-secure-person-with-significant-control", + "individual-beneficial-owner", + "corporate-entity-beneficial-owner", + "legal-person-beneficial-owner", + "super-secure-beneficial-owner" + ], + "type": "string" + }, + "address": { + "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.", + "items": { + "$ref": "pscModels.json#/definitions/address" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "items": { + "type" : "string" + }, + "type": "array" + }, + "is_sanctioned": { + "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity", + "type": "boolean" + }, + "principal_office_address": { + "description": "The principal/registered office address of a corporate-entity-beneficial-owner or legal-person-beneficial-owner of a registered-overseas-entity.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerAddress" + }, + "type": "object" + } + } + }, + "listTwoSummary": { + "title": "listSummary", + "required": [ + "etag", + "name", + "links", + "address", + "notified_on", + "natures_of_control" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this person with significant control.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this person with significant control.", + "type": "string", + "format": "date" + }, + "country_of_residence": { + "description": "The country of residence of the person with significant control.", + "type": "string" + }, + "date_of_birth": { + "description": "The date of birth of the person with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/dateOfBirthPSCList" + }, + "type": "object" + }, + "name": { + "description": "Name of the person with significant control.", + "type": "string" + }, + "name_elements": { + "description": "A document encapsulating the separate elements of a person with significant control's name.", + "items": { + "$ref": "pscModels.json#/definitions/nameElements" + }, + "type": "object" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscItemsListLinksType" + }, + "type": "object" + }, + "nationality": { + "description": "The nationality of the person with significant control.", + "type": "string" + }, + "identification": { + "description": "", + "items": { + "$ref": "pscModels.json#/definitions/pscListIdent" + }, + "type": "object" + }, + "identity_verification_details": { + "description": "Information relating to the identity verification of the person with significant control", + "items": { + "$ref": "pscModels.json#/definitions/identityVerificationDetails" + }, + "type": "object" + }, + "ceased": { + "description" : "Presence of that indicator means the super secure person status is ceased
", + "type": "boolean" + }, + "description": { + "description" : "Description of the super secure legal statement
", + "enum": [ + "super-secure-persons-with-significant-control" + ], + "type": "string" + }, + "kind": { + "enum": [ + "individual-person-with-significant-control", + "corporate-entity-person-with-significant-control", + "legal-person-with-significant-control", + "super-secure-person-with-significant-control", + "individual-beneficial-owner", + "corporate-entity-beneficial-owner", + "legal-person-beneficial-owner", + "super-secure-beneficial-owner" + ], + "type": "string" + }, + "address": { + "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.", + "items": { + "$ref": "pscModels.json#/definitions/address" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "items": { + "type" : "string" + }, + "type": "array" + }, + "is_sanctioned": { + "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity", + "type": "boolean" + }, + "principal_office_address": { + "description": "The principal/registered office address of a corporate-entity-beneficial-owner or legal-person-beneficial-owner of a registered-overseas-entity.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerAddress" + }, + "type": "object" + } + } + }, + "statementList": { + "title": "statementList", + "properties": { + "items_per_page": { + "description": "The number of persons with significant control statements to return per page.", + "type": "integer" + }, + "items": { + "description": "The list of persons with significant control statements.", + "items": { + "$ref": "pscModels.json#/definitions/statement" + }, + "type": "object" + }, + "start_index": { + "description": "The offset into the entire result set that this page starts.", + "type": "integer" + }, + "total_results": { + "description": "The total number of persons with significant control statements in this result set.", + "type": "integer" + }, + "active_count": { + "description": "The number of active persons with significant control statements in this result set.", + "type": "integer" + }, + "ceased_count": { + "description": "The number of ceased persons with significant control statements in this result set.", + "type": "integer" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/statementListLinksType" + }, + "type": "object" + } + }, + "required": [ + "items_per_page", + "items", + "start_index", + "total_results", + "active_count", + "ceased_count", + "links" + ] + }, + "statement": { + "title": "statement", + "required": [ + "etag", + "kind", + "notified_on", + "statement", + "links" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "kind": { + "enum": [ + "persons-with-significant-control-statement" + ], + "type": "string" + }, + "notified_on": { + "description": "The date that the person with significant control statement was processed by Companies House.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this person with significant control.", + "type": "string", + "format": "date" + }, + "restrictions_notice_withdrawal_reason": { + "description": "The reason for the company withdrawing a restrictions-notice-issued-to-psc statement", + "enum": [ + "restrictions-notice-withdrawn-by-court-order", + "restrictions-notice-withdrawn-by-company", + "restrictions-notice-withdrawn-by-lp", + "restrictions-notice-withdrawn-by-court-order-lp", + "restrictions-notice-withdrawn-by-partnership", + "restrictions-notice-withdrawn-by-court-order-p" + ], + "type": "string" + }, + "statement": { + "description": "Indicates the type of statement filed.\n For enumeration descriptions see `statement_description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "type": "string" + }, + "linked_psc_name": { + "description": "The name of the psc linked to this statement.", + "type": "string" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/statementLinksType" + }, + "type": "object" + } + } + }, + "superSecure": { + "title": "superSecure", + "required": [ + "etag", + "kind", + "description", + "links" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "kind": { + "enum": [ + "super-secure-person-with-significant-control" + ], + "type": "string" + }, + "description": { + "description": "Description of the super secure legal statement \n", + "enum": [ + "super-secure-persons-with-significant-control" + ], + "type": "string" + }, + "identity_verification_details": { + "description": "Information relating to the identity verification of the person with significant control", + "properties": { + "appointment_verification_end_on": { + "description": "The date on which the identity verification statement was removed for the notification", + "type": "string", + "format": "date" + }, + "appointment_verification_start_on": { + "description": "The date on which the identity verification statement was supplied for the notification", + "type": "string", + "format": "date" + } + }, + "type": "object" + }, + "ceased": { + "description": "Presence of that indicator means the super secure person status is ceased \n", + "type": "boolean" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/superSecureLinksType" + }, + "type": "object" + } + } + }, + "superSecureBeneficialOwner": { + "title": "superSecureBeneficialOwner", + "required": [ + "etag", + "kind", + "description", + "links" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "kind": { + "enum": [ + "super-secure-beneficial-owner" + ], + "type": "string" + }, + "description": { + "description": "Description of the super secure legal statement \n", + "enum": [ + "super-secure-beneficial-owner" + ], + "type": "string" + }, + "ceased": { + "description": "Presence of this indicator means the super secure beneficial owner status is ceased \n", + "type": "boolean" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/superSecureLinksType" + }, + "type": "object" + } + } + }, + "individual": { + "title": "individual", + "required": [ + "etag", + "notified_on", + "kind", + "country_of_residence", + "date_of_birth", + "name", + "name_elements", + "links", + "nationality", + "address", + "natures_of_control" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this person with significant control.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this person with significant control.", + "type": "string", + "format": "date" + }, + "kind": { + "enum": [ + "individual-person-with-significant-control" + ], + "type": "string" + }, + "country_of_residence": { + "description": "The country of residence of the person with significant control.", + "type": "string" + }, + "date_of_birth": { + "description": "The date of birth of the person with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/dateOfBirth" + }, + "type": "object" + }, + "name": { + "description": "Name of the person with significant control. Generated by combining the name elements.", + "type": "string" + }, + "name_elements": { + "description": "A document encapsulating the separate elements of a person with significant control's name.", + "items": { + "$ref": "pscModels.json#/definitions/nameElements" + }, + "type": "object" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscLinksType" + }, + "type": "object" + }, + "nationality": { + "description": "The nationality of the person with significant control.", + "type": "string" + }, + "address": { + "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.", + "items": { + "$ref": "pscModels.json#/definitions/address" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "type": "array" + }, + "identity_verification_details": { + "description": "Information relating to the identity verification of the person with significant control", + "items": { + "$ref": "pscModels.json#/definitions/identityVerificationDetails" + }, + "type": "object" + } + } + }, + "individualBeneficialOwner": { + "title": "individualBeneficialOwner", + "required": [ + "etag", + "kind", + "links" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this beneficial owner.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this beneficial owner.", + "type": "string", + "format": "date" + }, + "kind": { + "enum": [ + "individual-beneficial-owner" + ], + "type": "string" + }, + "date_of_birth": { + "description": "The date of birth of the beneficial owner.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerDateOfBirth" + }, + "type": "object" + }, + "name": { + "description": "Name of the beneficial owner. Generated by combining the name elements.", + "type": "string" + }, + "name_elements": { + "description": "A document encapsulating the separate elements of a beneficial owner's name.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerNameElements" + }, + "type": "object" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerLinksType" + }, + "type": "object" + }, + "nationality": { + "description": "The nationality of the beneficial owner.", + "type": "string" + }, + "address": { + "description": "The service address of the beneficial owner. If given, this address will be shown on the public record instead of the residential address.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerAddress" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the beneficial owner holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "type": "array" + }, + "is_sanctioned": { + "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity", + "type": "boolean" + } + } + }, + "corporateEntity": { + "title": "corporateEntity", + "required": [ + "etag", + "notified_on", + "kind", + "name", + "links", + "address", + "identification", + "natures_of_control" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this person with significant control.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this person with significant control.", + "type": "string", + "format": "date" + }, + "kind": { + "enum": [ + "corporate-entity-person-with-significant-control" + ], + "type": "string" + }, + "name": { + "description": "Name of the person with significant control.", + "type": "string" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscLinksType" + }, + "type": "object" + }, + "address": { + "description": "The address of the person with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/address" + }, + "type": "object" + }, + "identification": { + "description": "", + "items": { + "$ref": "pscModels.json#/definitions/corporateEntityIdent" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "type": "array" + } + } + }, + "corporateEntityBeneficialOwner": { + "title": "corporateEntityBeneficialOwner", + "required": [ + "etag", + "kind", + "links" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this beneficial owner.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this beneficial owner.", + "type": "string", + "format": "date" + }, + "kind": { + "enum": [ + "corporate-entity-beneficial-owner" + ], + "type": "string" + }, + "name": { + "description": "Name of the beneficial owner.", + "type": "string" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerLinksType" + }, + "type": "object" + }, + "address": { + "description": "The address of the beneficial owner.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerAddress" + }, + "type": "object" + }, + "principal_office_address": { + "description": "The principal/registered office address of a corporate-entity-beneficial-owner of a registered-overseas-entity.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerAddress" + }, + "type": "object" + }, + "identification": { + "description": "", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerCorporateEntityIdent" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the beneficial owner holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "type": "array" + }, + "is_sanctioned": { + "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity", + "type": "boolean" + } + } + }, + "legalPerson": { + "title": "legalPerson", + "required": [ + "etag", + "notified_on", + "kind", + "name", + "links", + "address", + "identification", + "natures_of_control" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this person with significant control.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this person with significant control.", + "type": "string", + "format": "date" + }, + "kind": { + "enum": [ + "legal-person-person-with-significant-control" + ], + "type": "string" + }, + "name": { + "description": "Name of the person with significant control.", + "type": "string" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/pscLinksType" + }, + "type": "object" + }, + "address": { + "description": "The address of the person with significant control.", + "items": { + "$ref": "pscModels.json#/definitions/address" + }, + "type": "object" + }, + "identification": { + "description": "", + "items": { + "$ref": "pscModels.json#/definitions/legalPersonIdent" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the person with significant control holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "type": "array" + } + } + }, + "legalPersonBeneficialOwner": { + "title": "legalPersonBeneficialOwner", + "required": [ + "etag", + "kind", + "links" + ], + "properties": { + "etag": { + "description": "The ETag of the resource.", + "type": "string" + }, + "notified_on": { + "description": "The date that Companies House was notified about this beneficial owner.", + "type": "string", + "format": "date" + }, + "ceased_on": { + "description": "The date that Companies House was notified about the cessation of this beneficial owner.", + "type": "string", + "format": "date" + }, + "kind": { + "enum": [ + "legal-person-beneficial-owner" + ], + "type": "string" + }, + "name": { + "description": "Name of the beneficial owner.", + "type": "string" + }, + "links": { + "description": "A set of URLs related to the resource, including self.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerLinksType" + }, + "type": "object" + }, + "address": { + "description": "The address of the beneficial owner.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerAddress" + }, + "type": "object" + }, + "principal_office_address": { + "description": "The principal/registered office address of a legal-person-beneficial-owner of a registered-overseas-entity.", + "items": { + "$ref": "pscModels.json#/definitions/beneficialOwnerAddress" + }, + "type": "object" + }, + "identification": { + "description": "", + "items": { + "$ref": "pscModels.json#/definitions/legalPersonBeneficialOwnerIdent" + }, + "type": "object" + }, + "natures_of_control": { + "description": "Indicates the nature of control the beneficial owner holds.\n For enumeration descriptions see `description` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml) file. \n", + "type": "array" + }, + "is_sanctioned": { + "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity", + "type": "boolean" + } + } + }, + "nameElements": { + "title": "nameElements", + "properties": { + "forename": { + "description": "The forename of the person with significant control.", + "type": "string" + }, + "title": { + "description": "Title of the person with significant control.", + "type": "string" + }, + "middle_name": { + "description": "The middle name of the person with significant control.", + "type": "string" + }, + "surname": { + "description": "The surname of the person with significant control.", + "type": "string" + } + }, + "required": [ + "surname" + ] + }, + "beneficialOwnerNameElements": { + "title": "beneficialOwnerNameElements", + "properties": { + "forename": { + "description": "The forename of the beneficial owner.", + "type": "string" + }, + "title": { + "description": "Title of the beneficial owner.", + "type": "string" + }, + "middle_name": { + "description": "The middle name of the beneficial owner.", + "type": "string" + }, + "surname": { + "description": "The surname of the beneficial owner.", + "type": "string" + } + }, + "required": [ + "surname" + ] + }, + "corporateEntityIdent": { + "title": "corporateEntityIdent", + "properties": { + "legal_authority": { + "description": "The legal authority supervising the corporate entity with significant control.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the corporate entity with significant control as defined by its country of registration.", + "type": "string" + }, + "place_registered": { + "description": "The place the corporate entity with significant control is registered.", + "type": "string" + }, + "registration_number": { + "description": "The registration number of the corporate entity with significant control.", + "type": "string" + }, + "country_registered": { + "description": "The country or state the corporate entity with significant control is registered in.", + "type": "string" + } + }, + "required": [ + "legal_authority", + "legal_form" + ] + }, + "beneficialOwnerCorporateEntityIdent": { + "title": "beneficialOwnerCorporateEntityIdent", + "properties": { + "legal_authority": { + "description": "The legal authority supervising the corporate entity beneficial owner.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the corporate entity beneficial owner as defined by its country of registration.", + "type": "string" + }, + "place_registered": { + "description": "The place the corporate entity beneficial owner is registered.", + "type": "string" + }, + "registration_number": { + "description": "The registration number of the corporate entity beneficial owner.", + "type": "string" + }, + "country_registered": { + "description": "The country or state the corporate entity beneficial owner is registered in.", + "type": "string" + } + } + }, + "pscListIdent": { + "title": "pscListIdent", + "properties": { + "legal_authority": { + "description": "The legal authority supervising the corporate entity or legal person with significant control.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the corporate entity or legal person with significant control as defined by its country of registration.", + "type": "string" + }, + "place_registered": { + "description": "The place the corporate entity with significant control is registered.", + "type": "string" + }, + "registration_number": { + "description": "The registration number of the corporate entity with significant control.", + "type": "string" + }, + "country_registered": { + "description": "The country or state the corporate entity with significant control is registered in.", + "type": "string" + } + }, + "required": [ + "legal_authority", + "legal_form" + ] + }, + "identityVerificationDetails": { + "title": "identityVerificationDetails", + "properties": { + "anti_money_laundering_supervisory_bodies": { + "description": "The Anti-Money Laundering supervisory bodies that the authorised corporate service provider was registered with when verifying the person with significant control", + "type": "array", + "items": { + "type": "string" + } + }, + "appointment_verification_end_on": { + "description": "The date on which the identity verification statement was removed for the notification", + "type": "string", + "format": "date" + }, + "appointment_verification_statement_date": { + "description": "The date from which an identity verification statement can be supplied for the notification", + "type": "string", + "format": "date" + }, + "appointment_verification_statement_due_on": { + "description": "The date by which an identity verification statement must be supplied for the notification", + "type": "string", + "format": "date" + }, + "appointment_verification_start_on": { + "description": "The date on which the identity verification statement was supplied for the notification", + "type": "string", + "format": "date" + }, + "authorised_corporate_service_provider_name": { + "description": "The name of the authorised corporate service provider that verified the identity of the person with significant control", + "type": "string" + }, + "identity_verified_on": { + "description": "The date on which the authorised corporate service provider verified the identity of the person with significant control", + "type": "string", + "format": "date" + }, + "preferred_name": { + "description": "The name provided to the authorised corporate service provider by which the person with significant control prefers to be known", + "type": "string" + } + } + }, + "legalPersonIdent": { + "title": "legalPersonIdent", + "properties": { + "legal_authority": { + "description": "The legal authority supervising the legal person with significant control.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the legal person with significant control as defined by its country of registration.", + "type": "string" + } + }, + "required": [ + "legal_authority", + "legal_form" + ] + }, + "legalPersonBeneficialOwnerIdent": { + "title": "legalPersonBeneficialOwnerIdent", + "properties": { + "legal_authority": { + "description": "The legal authority supervising the legal person beneficial owner.", + "type": "string" + }, + "legal_form": { + "description": "The legal form of the legal person beneficial owner as defined by its country of registration.", + "type": "string" + } + } + }, + "dateOfBirth": { + "title": "dateOfBirth", + "properties": { + "day": { + "description": "The day of the date of birth.", + "type": "integer" + }, + "month": { + "description": "The month of date of birth.", + "type": "integer" + }, + "year": { + "description": "The year of date of birth.", + "type": "integer" + } + }, + "required": [ + "month", + "year" + ] + }, + "dateOfBirthPSCList": { + "title": "dateOfBirth", + "properties": { + "month": { + "description": "The month of date of birth.", + "type": "integer" + }, + "year": { + "description": "The year of date of birth.", + "type": "integer" + } + }, + "required": [ + "month", + "year" + ] + }, + "beneficialOwnerDateOfBirth": { + "title": "beneficialOwnerDateOfBirth", + "properties": { + "day": { + "description": "The day of the date of birth.", + "type": "integer" + }, + "month": { + "description": "The month of date of birth.", + "type": "integer" + }, + "year": { + "description": "The year of date of birth.", + "type": "integer" + } + } + } + } +} + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json new file mode 100644 index 0000000..cb46d60 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json @@ -0,0 +1,414 @@ +{ + "get": { + "summary": "Persons with significant control Notification List", + "tags": [ + "personsWithSignificantControlNotifications" + ], + "x-operationName": "list", + "description": "List of all notifications of a specific person with significant control", + "parameters": [ + { + "name": "psc_id", + "in": "path", + "description": "The person with significant control id of the notification list being requested", + "required": true, + "type": "string" + }, + { + "name": "filter", + "in": "query", + "description": "Use “active” to return only active notifications.", + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of notifications to return per page.", + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the items_per_page parameter.", + "type": "integer" + } + ], + "responses": { + "200": { + "description": "List the person with significant control notifications", + "schema": { + "$ref": "pscNotificationList.json#/definitions/notificationList" + } + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Unauthorised" + } + } + }, +"definitions": { + "notificationList": { + "title": "notificationList", + "required": [ + "active_count", + "ceased_count", + "inactive_count", + "items", + "items_per_page", + "links", + "name", + "start_index", + "total_results", + "kind" + ], + "properties": { + "active_count": { + "type": "integer", + "description": "The number of active persons with significant control notifications in this result set." + }, + "ceased_count": { + "type": "integer", + "description": "The number of ceased persons with significant control notifications in this result set." + }, + "date_of_birth": { + "type": "object", + "description": "The date of birth of the person with significant control.", + "required": [ + "month", + "year" + ], + "properties": { + "month": { + "type": "integer", + "description": "The month of date of birth." + }, + "year": { + "type": "integer", + "description": "The year of date of birth." + } + } + }, + "inactive_count": { + "type": "integer", + "description": "The number of inactive persons with significant control notifications in this result set." + }, + "items": { + "type": "array", + "description": "The list of person with significant control notifications.", + "items": { + "type": "object", + "required": [ + "address", + "notified_to", + "name", + "etag", + "natures_of_control", + "notified_on", + "links" + ], + "properties": { + "address": { + "type": "object", + "description": "The service address of the person with significant control. If given, this address will be shown on the public record instead of the residential address.", + "required": [ + "address_line_1", + "postal_code", + "premises" + ], + "properties": { + "address_line_1": { + "type": "string", + "description": "The first line of the address." + }, + "address_line_2": { + "type": "string", + "description": "The second line of the address." + }, + "care_of": { + "type": "string", + "description": "Care of name." + }, + "country": { + "type": "string", + "description": "The country. For example, UK." + }, + "locality": { + "type": "string", + "description": "The locality. For example London." + }, + "po_box": { + "type": "string", + "description": "The post-office box number." + }, + "postal_code": { + "type": "string", + "description": "The postal code. For example CF14 3UZ." + }, + "premises": { + "type": "string", + "description": "The property name or number." + }, + "region": { + "type": "string", + "description": "The region. For example Surrey." + } + } + }, + "notified_to": { + "type": "object", + "description": "The company information of the notification.", + "required": [ + "company_number" + ], + "properties": { + "company_name": { + "type": "string", + "description": "The name of the company the person with significant control is notified to." + }, + "company_number": { + "type": "string", + "description": "The number of the company the person with significant control is notified to." + }, + "company_status": { + "type": "string", + "description": "The status of the company the person with significant control is notified to." + } + } + }, + "ceased_on": { + "type": "string", + "format": "date", + "description": "The date that Companies House was notified about the cessation of this person with significant control." + }, + "country_of_residence": { + "type": "string", + "description": "The country of residence of the person with significant control." + }, + "etag": { + "type": "string", + "description": "The ETag of the resource." + }, + "identification": { + "type": "object", + "description": "Information related to the registration of either a `corporate-entity-person-with-significant-control` or a `corporate-entity-beneficial-owner`.", + "required": [ + "legal_authority", + "legal_form" + ], + "properties": { + "country_registered": { + "type": "string", + "description": "The country or state the corporate entity with significant control is registered in." + }, + "legal_authority": { + "type": "string", + "description": "The legal authority supervising the corporate entity or legal person with significant control." + }, + "legal_form": { + "type": "string", + "description": "The legal form of the corporate entity or legal person with significant control as defined by its country of registration." + }, + "place_registered": { + "type": "string", + "description": "The place the corporate entity with significant control is registered." + }, + "registration_number": { + "type": "string", + "description": "The registration number of the corporate entity with significant control." + } + } + }, + "identity_verification_details": { + "type": "object", + "description": "Information relating to the identity verification of the person with significant control.", + "properties": { + "anti_money_laundering_supervisory_bodies": { + "type": "array", + "description": "The Anti-Money Laundering supervisory bodies that the authorised corporate service provider was registered with when verifying the person with significant control", + "items": { "type": "string" } + }, + "appointment_verification_end_on": { + "type": "string", + "format": "date", + "description": "The date on which the identity verification statement was removed for the notification." + }, + "appointment_verification_start_on": { + "type": "string", + "format": "date", + "description": "The date on which the identity verification statement was supplied for the notification." + }, + "appointment_verification_statement_date": { + "type": "string", + "format": "date", + "description": "The date from which an identity verification statement can be supplied for the notification." + }, + "appointment_verification_statement_due_on": { + "type": "string", + "format": "date", + "description": "The date by which an identity verification statement must be supplied for the notification." + }, + "authorised_corporate_service_provider_name": { + "type": "string", + "description": "The name of the authorised corporate service provider that verified the identity of the person with significant control." + }, + "identity_verified_on": { + "type": "string", + "format": "date", + "description": "The date on which the authorised corporate service provider verified the identity of the person with significant control." + }, + "preferred_name": { + "type": "string", + "description": "The name provided to the authorised corporate service provider by which the person with significant control prefers to be known." + } + } + }, + "is_sanctioned": { + "type": "boolean", + "description": "Flag indicating if the beneficial owner was declared as being sanctioned on the latest filing of the overseas entity." + }, + "kind": { + "type": "string", + "description": "Possible values are:
  • individual-person-with-significant-control
  • corporate-entity-person-with-significant-control
  • legal-person-with-significant-control
  • individual-beneficial-owner
  • corporate-entity-beneficial-owner
  • legal-person-beneficial-owner
" + }, + "links": { + "type": "object", + "description": "Links to other resources associated with this person with significant control notification resource.", + "required": [ + "company" + ], + "properties": { + "company": { + "type": "string", + "description": "Link to the company profile resource that this notification is associated with." + } + } + }, + "name": { + "type": "string", + "description": "Name of the person with significant control." + }, + "name_elements": { + "type": "object", + "description": "A document encapsulating the separate elements of a person with significant control's name.", + "required": [ + "surname" + ], + "properties": { + "forename": { + "type": "string", + "description": "The forename of the person with significant control." + }, + "middle_name": { + "type": "string", + "description": "The middle name of the person with significant control." + }, + "surname": { + "type": "string", + "description": "The surname of the person with significant control." + }, + "title": { + "type": "string", + "description": "Title of the person with significant control." + } + } + }, + "nationality": { + "type": "string", + "description": "The nationality of the person with significant control." + }, + "natures_of_control": { + "type": "array", + "description": "Indicates the nature of control the person with significant control holds. For enumeration descriptions see `description` [section in the enumeration mappings file](https://github.com/companieshouse/api-enumerations/blob/master/psc_descriptions.yml).", + "items": { + "type": "string" + } + }, + "notified_on": { + "type": "string", + "format": "date", + "description": "The date that Companies House was notified about this person with significant control." + }, + "principal_office_address": { + "type": "object", + "description": "The principal/registered office address of a corporate-entity-beneficial-owner or legal-person-beneficial-owner of a registered-overseas-entity.", + "properties": { + "address_line_1": { + "type": "string", + "description": "The first line of the address." + }, + "address_line_2": { + "type": "string", + "description": "The second line of the address." + }, + "care_of": { + "type": "string", + "description": "The care of name." + }, + "country": { + "type": "string", + "description": "The country. For example, United Kingdom." + }, + "locality": { + "type": "string", + "description": "The locality. For example London." + }, + "po_box": { + "type": "string", + "description": "The post-office box number." + }, + "postal_code": { + "type": "string", + "description": "The postal code. For example CF14 3UZ." + }, + "premises": { + "type": "string", + "description": "The property name or number." + }, + "region": { + "type": "string", + "description": "The region. For example Surrey." + } + } + } + } + } + }, + "items_per_page": { + "type": "integer", + "description": "The number of persons with significant control notifications to return per page." + }, + "kind": { + "type": "string", + "description": "Possible values are:
  • personal-notification
" + }, + "links": { + "type": "object", + "description": "Links to other resources associated with this person with significant control notification resource.", + "required": [ + "self" + ], + "properties": { + "self": { + "type": "string", + "description": "Link to this person with significant control notification resource." + } + } + }, + "name": { + "type": "string", + "description": "The person with significant control name." + }, + "start_index": { + "type": "integer", + "description": "The first row of data to retrieve, starting at 0. Use this parameter as a pagination mechanism along with the items_per_page parameter." + }, + "total_results": { + "type": "integer", + "description": "The total number of persons with significant control notifications in this result set." + } + } + } + } +} diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json new file mode 100644 index 0000000..032df8b --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json @@ -0,0 +1,688 @@ +{ + "searchDissolved":{ + "get":{ + "summary":"Search for a dissolved company", + "description":"Search for a dissolved company", + "x-operationName": "search dissolved companies", + "tags": [ + "search" + ], + "parameters":[ + { + "name":"q", + "in":"query", + "description":"The company name being searched for", + "required":true, + "type":"string" + }, + { + "name":"search_type", + "in":"query", + "description":"Determines type of search. Options are alphabetical, best-match, previous-name-dissolved", + "required":true, + "type":"string" + }, + { + "name":"search_above", + "in":"query", + "description":"The ordered_alpha_key_with_id used for alphabetical paging", + "required":false, + "type":"string" + }, + { + "name":"search_below", + "in":"query", + "description":"The ordered_alpha_key_with_id used for alphabetical paging", + "required":false, + "type":"string" + }, + { + "name":"size", + "in":"query", + "description":"The maximum number of results matching the search term(s) to return with a range of 1 to 100", + "required":false, + "type":"string" + }, + { + "name":"start_index", + "in":"query", + "description":"Used in best-match and previous-name-dissolved search-type", + "required":false, + "type":"string" + } + ], + "responses":{ + "200":{ + "description":"A list of dissolved companies", + "schema":{ + "$ref": "search-companies.json#/definitions/dissolvedCompanySearch" + } + }, + "404":{ + "description":"No companies found" + }, + "422":{ + "description":"Invalid size parameter, size must be greater than zero and not greater than 100" + } + } + } + }, + "searchAlphabetic":{ + "get":{ + "summary":"Search for a company", + "description":"Search for a company", + "x-operationName": "search companies alphabetically", + "tags": [ + "search" + ], + "parameters":[ + { + "name":"q", + "in":"query", + "description":"The company name being searched for", + "required":true, + "type":"string" + }, + { + "name":"search_above", + "in":"query", + "description":"The ordered_alpha_key_with_id used for paging", + "required":false, + "type":"string" + }, + { + "name":"search_below", + "in":"query", + "description":"The ordered_alpha_key_with_id used for paging", + "required":false, + "type":"string" + }, + { + "name":"size", + "in":"query", + "description":"The maximum number of results matching the search term(s) to return with a range of 1 to 100", + "required":false, + "type":"string" + } + ], + "responses":{ + "200":{ + "description":"A list of companies", + "schema":{ + "$ref": "search-companies.json#/definitions/alphabeticalCompanySearch" + } + }, + "404":{ + "description":"No companies found" + }, + "422":{ + "description":"Invalid size parameter, size must be greater than zero and not greater than 100" + } + } + } + }, + "searchAdvanced":{ + "get":{ + "summary":"Advanced search for a company", + "description":"Advanced search for a company", + "x-operationName": "advanced company search", + "tags": [ + "search" + ], + "parameters":[ + { + "name":"company_name_includes", + "in":"query", + "description":"The company name includes advanced search filter", + "required":false, + "type":"string" + }, + { + "name":"company_name_excludes", + "in":"query", + "description":"The company name excludes advanced search filter", + "required":false, + "type":"string" + }, + { + "name":"company_status", + "in":"query", + "description":"The company status advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. company_status=xxx&company_status=yyy", + "required":false, + "type":"list" + }, + { + "name":"company_subtype", + "in":"query", + "description":"The company subtype advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. company_subtype=xxx&company_subtype=yyy", + "required":false, + "type":"string" + }, + { + "name":"company_type", + "in":"query", + "description":"The company type advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. company_type=xxx&company_type=yyy", + "required":false, + "type":"list" + }, + { + "name":"dissolved_from", + "in":"query", + "description":"The dissolved from date advanced search filter", + "required":false, + "type":"date" + }, + { + "name":"dissolved_to", + "in":"query", + "description":"The dissolved to date advanced search filter", + "required":false, + "type":"date" + }, + { + "name":"incorporated_from", + "in":"query", + "description":"The incorporated from date advanced search filter", + "required":false, + "type":"date" + }, + { + "name":"incorporated_to", + "in":"query", + "description":"The incorporated to date advanced search filter", + "required":false, + "type":"date" + }, + { + "name":"location", + "in":"query", + "description":"The location advanced search filter", + "required":false, + "type":"string" + }, + { + "name":"sic_codes", + "in":"query", + "description":"The SIC codes advanced search filter. To search using multiple values, use a comma delimited list or multiple of the same key i.e. sic_codes=xxx&sic_codes=yyy", + "required":false, + "type":"list" + }, + { + "name":"size", + "in":"query", + "description":"The maximum number of results matching the search term(s) to return with a range of 1 to 5000", + "required":false, + "type":"string" + }, + { + "name":"start_index", + "in":"query", + "description":"The point at which results will start from i.e show search results from result 20 (used for paging)", + "required":false, + "type":"string" + } + ], + "responses":{ + "200":{ + "description":"A list of companies", + "schema":{ + "$ref": "search-companies.json#/definitions/advancedCompanySearch" + } + }, + "400":{ + "description":"Bad request" + }, + "404":{ + "description":"No companies found" + } + } + } + }, + "definitions":{ + "dissolvedCompanySearch":{ + "title":"List of dissolved companies", + "type":"object", + "allOf":[ + { + "properties":{ + "etag":{ + "type":"string" + }, + "items":{ + "type":"array", + "items":{ + "$ref": "search-companies.json#/definitions/dissolvedCompany" + } + }, + "kind":{ + "type":"string", + "enum":[ + "search#alphabetical-dissolved", + "search#dissolved", + "search#previous-name-dissolved" + ] + }, + "top_hit":{ + "allOf":[ + { + "$ref": "search-companies.json#/definitions/dissolved_top_hit" + }, + { + "description":"The best matching company in dissolved search results" + } + ] + }, + "hits":{ + "type":"string", + "description":"The number of hits returned on a best-match or previous-company-names search" + } + } + } + ] + }, + "alphabeticalCompanySearch":{ + "title":"List of companies", + "type":"object", + "allOf":[ + { + "properties":{ + "items":{ + "type":"array", + "items":{ + "$ref": "search-companies.json#/definitions/alphabeticalCompany" + } + }, + "kind":{ + "type":"string", + "enum":[ + "search#alphabetical-search", + "search#enhanced-search" + ] + }, + "top_hit":{ + "allOf":[ + { + "$ref": "search-companies.json#/definitions/alphabetical_top_hit" + }, + { + "description":"The best matching company in alphabetical search results" + } + ] + } + } + } + ] + }, + "advancedCompanySearch":{ + "title":"A list of companies", + "type":"object", + "required":[ + "top_hit", + "items", + "kind", + "hits" + ], + "allOf":[ + { + "properties":{ + "etag":{ + "type":"string" + }, + "items":{ + "type":"array", + "items":{ + "$ref": "search-companies.json#/definitions/advancedCompany" + } + }, + "kind":{ + "type":"string", + "enum":[ + "search#advanced-search" + ] + }, + "top_hit":{ + "allOf":[ + { + "$ref": "search-companies.json#/definitions/advanced_top_hit" + }, + { + "description":"The best matching company in an advanced search results" + } + ] + }, + "hits":{ + "type":"string", + "description":"The number of matches found using advanced search" + } + } + } + ] + }, + "dissolved_top_hit":{ + "$ref": "search-companies.json#/definitions/dissolvedCompany" + }, + "alphabetical_top_hit":{ + "$ref": "search-companies.json#/definitions/alphabeticalCompany" + }, + "advanced_top_hit":{ + "$ref": "search-companies.json#/definitions/advancedCompany" + }, + "dissolvedCompany":{ + "title":"Dissolved company", + "required":[ + "company_name", + "company_number", + "date_of_cessation", + "date_of_creation" + ], + "properties":{ + "company_name":{ + "type":"string", + "description":"The company name associated with the dissolved company" + }, + "company_number":{ + "type":"string", + "description":"The company number of the dissolved company" + }, + "company_status":{ + "type":"string", + "description":"The status of the company" + }, + "ordered_alpha_key_with_id":{ + "type":"string", + "description":"The alphakey with it's id associated with the dissolved company" + }, + "kind":{ + "type":"string", + "enum":[ + "search-results#dissolved-company" + ], + "description":"The type of search result" + }, + "date_of_cessation":{ + "type":"string", + "format":"date", + "description":"The date that the company was dissolved" + }, + "date_of_creation":{ + "type":"string", + "format":"date", + "description":"The date that the company was incorporated" + }, + "registered_office_address":{ + "$ref": "search-companies.json#/definitions/dissolved_company_registered_office_address" + }, + "previous_company_names":{ + "type":"array", + "items":{ + "$ref": "search-companies.json#/definitions/previous_company_name" + } + }, + "matched_previous_company_name":{ + "$ref": "search-companies.json#/definitions/previous_company_name" + } + } + }, + "alphabeticalCompany":{ + "title":"Alphabetical company", + "required":[ + "company_name", + "company_number", + "company_status", + "company_type", + "links" + ], + "properties":{ + "company_name":{ + "type":"string", + "description":"The company name associated with the company" + }, + "company_number":{ + "type":"string", + "description":"The company number of the company" + }, + "company_status":{ + "type":"string", + "description":"The status of the company" + }, + "ordered_alpha_key_with_id":{ + "type":"string", + "description":"The alphakey with it's id associated with the company" + }, + "kind":{ + "type":"string", + "enum":[ + "search-results#alphabetical-search" + ], + "description":"The type of search result" + }, + "links":{ + "type":"object", + "description":"The link to the company", + "properties":{ + "company_profile":{ + "type":"string", + "description":"The link to the company" + } + } + }, + "company_type":{ + "type":"string", + "description":"The type of company associated with the company" + } + } + }, + "advanced_company_registered_office_address":{ + "title":"Registered Office Address", + "description": "This will only appear if there are ROA details in the company record", + "properties":{ + "address_line_1":{ + "type":"string", + "description":"The first line of the address e.g Crown Way" + }, + "address_line_2":{ + "type":"string", + "description":"The second line of the address" + }, + "locality":{ + "type":"string", + "description":"The town associated to the ROA e.g Cardiff" + }, + "postal_code":{ + "type":"string", + "description":"The postal code e.g CF14 3UZ" + }, + "region": { + "description": "The region e.g Surrey.", + "type": "string" + }, + "country": { + "description": "The country.", + "enum": [ + "Wales", + "England", + "Scotland", + "Great Britain", + "Not specified", + "United Kingdom", + "Northern Ireland" + ], + "type": "string" + } + } + }, + "dissolved_company_registered_office_address":{ + "title":"Registered Office Address", + "description": "This will only appear if there are ROA details in the company record", + "properties": { + "address_line_1": { + "type": "string", + "description": "The first line of the address e.g Crown Way" + }, + "address_line_2": { + "type": "string", + "description": "The second line of the address" + }, + "locality": { + "type": "string", + "description": "The town associated to the ROA e.g Cardiff" + }, + "postal_code": { + "type": "string", + "description": "The postal code e.g CF14 3UZ" + } + } + }, + "previous_company_name":{ + "title":"Previous company name", + "properties":{ + "company_number":{ + "type":"string", + "description":"The company number of the dissolved company" + }, + "ceased_on":{ + "type":"string", + "format":"date", + "description":"The date that the company ceased being known under the company name" + }, + "effective_from":{ + "type":"string", + "format":"date", + "description":"The date that the company started being known under the company name" + }, + "name":{ + "type":"string", + "description":"The previous name of the company" + } + } + }, + "advancedCompany":{ + "title":"advancedCompany", + "required":[ + "company_name", + "company_number", + "company_status", + "company_type", + "date_of_creation", + "kind" + ], + "properties":{ + "company_name":{ + "type":"string", + "description":"The company name associated with the company" + }, + "company_number":{ + "type":"string", + "description":"The company number of the company" + }, + "company_status": { + "description": "The status of the company. \n For enumeration descriptions see `company_status` section in the [enumeration mappings] (https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ", + "type": "string", + "enum": [ + "active", + "dissolved", + "open", + "closed", + "converted-closed", + "receivership", + "administration", + "liquidation", + "insolvency-proceedings", + "voluntary-arrangement", + "registered", + "removed" + ] + }, + "company_type":{ + "description": "The type of the company. \n For enumeration descriptions see `company_type` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml) ", + "enum": [ + "private-unlimited", + "ltd", + "plc", + "old-public-company", + "private-limited-guarant-nsc-limited-exemption", + "limited-partnership", + "private-limited-guarant-nsc", + "converted-or-closed", + "private-unlimited-nsc", + "private-limited-shares-section-30-exemption", + "protected-cell-company", + "assurance-company", + "oversea-company", + "eeig", + "icvc-securities", + "icvc-warrant", + "icvc-umbrella", + "registered-society-non-jurisdictional", + "industrial-and-provident-society", + "northern-ireland", + "northern-ireland-other", + "royal-charter", + "investment-company-with-variable-capital", + "unregistered-company", + "llp", + "other", + "european-public-limited-liability-company-se", + "uk-establishment", + "scottish-partnership", + "charitable-incorporated-organisation", + "scottish-charitable-incorporated-organisation", + "further-education-or-sixth-form-college-corporation", + "registered-overseas-entity" + ], + "type": "string" + }, + "company_subtype":{ + "description": "The subtype of the company. \n For enumeration descriptions see `company_subtype` section in the [enumeration mappings](https://github.com/companieshouse/api-enumerations/blob/master/constants.yml)", + "type":"string", + "enum":[ + "community-interest-company", + "private-fund-limited-partnership" + ] + }, + "kind":{ + "type":"string", + "enum":[ + "search-results#company" + ], + "description":"The type of search result" + }, + "links":{ + "type":"object", + "description":"The link to the company", + "properties":{ + "company_profile":{ + "type":"string", + "description":"The link to the company" + } + } + }, + "date_of_cessation":{ + "type":"string", + "format":"date", + "description":"The date that the company was dissolved" + }, + "date_of_creation":{ + "type":"string", + "format":"date", + "description":"The date that the company was incorporated" + }, + "registered_office_address":{ + "$ref": "search-companies.json#/definitions/advanced_company_registered_office_address" + }, + "sic_codes":{ + "items": { + "type": "string" + }, + "type": "array", + "description":"SIC codes for this company" + } + } + } + }, + "schemes":[ + "https", + "http" + ] + } + diff --git a/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json new file mode 100644 index 0000000..ea07db3 --- /dev/null +++ b/spec/upstream/developer-specs.company-information.service.gov.uk/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json @@ -0,0 +1,986 @@ +{ + "searchAll": { + "get": { + "summary": "Search All", + "description": "Search companies, officers and disqualified officers", + "x-operationName": "search all", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "q", + "in": "query", + "description": "The term being searched for.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of search results to return per page.", + "required": false, + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The index of the first result item to return.", + "required": false, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Search all", + "schema": { + "$ref": "search.json#/definitions/Search" + } + }, + "401": { + "description": "Not authorised" + } + } + } + }, + "searchCompanies": { + "get": { + "summary": "Search companies", + "description": "Search company information", + "x-operationName": "search companies", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "q", + "in": "query", + "description": "The term being searched for.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of search results to return per page.", + "required": false, + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The index of the first result item to return.", + "required": false, + "type": "integer" + }, + { + "name":"restrictions", + "in":"query", + "description": "Enumerable options to restrict search results. Space separate multiple restriction options to combine functionality. For a \"company name availability\" search use \"active-companies legally-equivalent-company-name\" together.", + "required": false, + "type":"string" + } + ], + "responses": { + "200": { + "description": "Search company", + "schema": { + "$ref": "search.json#/definitions/CompanySearch" + } + }, + "401": { + "description": "Not authorised" + } + } + } + }, + "searchOfficers": { + "get": { + "summary": "Search company officers", + "description": "Search for officer information", + "x-operationName": "search officers", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "q", + "in": "query", + "description": "The term being searched for.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of search results to return per page.", + "required": false, + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The index of the first result item to return.", + "required": false, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Search officer", + "schema": { + "$ref": "search.json#/definitions/OfficerSearch" + } + }, + "401": { + "description": "Not authorised" + } + } + } + }, + "searchPersons-with-significant-control": { + "get": { + "summary": "Search company persons with significant control", + "description": "Search for persons with significant control information", + "x-operationName": "search persons with significant control", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "q", + "in": "query", + "description": "The term being searched for.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of search results to return per page.", + "required": false, + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The index of the first result item to return.", + "required": false, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Search persons with significant control", + "schema": { + "$ref": "search.json#/definitions/PersonsWithSignificantControlSearch" + } + }, + "401": { + "description": "Not authorised" + } + } + } + }, + "searchDisqualified-officers": { + "get": { + "summary": "Search disqualified officers", + "description": "Search for disqualified officer information", + "x-operationName": "search disqualified officers", + "tags": [ + "search" + ], + "parameters": [ + { + "name": "q", + "in": "query", + "description": "The term being searched for.", + "required": true, + "type": "string" + }, + { + "name": "items_per_page", + "in": "query", + "description": "The number of search results to return per page.", + "required": false, + "type": "integer" + }, + { + "name": "start_index", + "in": "query", + "description": "The index of the first result item to return.", + "required": false, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "Search all", + "schema": { + "$ref": "search.json#/definitions/DisqualifiedOfficerSearch" + } + }, + "401": { + "description": "Not authorised" + } + } + } + }, + "definitions": { + "CommonSearch": { + "properties": { + "total_results": { + "type": "integer", + "description": "The number of further search results available for the current search." + }, + "start_index": { + "type": "integer", + "description": "The index into the entire result set that this result page starts." + }, + "items_per_page": { + "type": "integer", + "description": "The number of search items returned per page." + }, + "etag": { + "type": "string", + "description": "The ETag of the resource" + } + } + }, + "CommonSearchItems": { + "properties": { + "title": { + "type": "string", + "description": "The title of the search result." + }, + "address_snippet": { + "type": "string", + "description": "A single line address. This will be the address that matched within the indexed document or the primary address otherwise (as returned by the `address` member)." + }, + "links": { + "type": "object", + "description": "The URL of the search result.", + "items": { + "$ref": "search.json#/definitions/LinksModel" + } + }, + "description": { + "type": "string", + "description": "The result description." + }, + "snippet": { + "type": "string", + "description": "Summary information for the result showing additional details that have matched." + }, + "matches": { + "type": "object", + "description": "A list of members and arrays of character offset defining substrings that matched the search terms.", + "items": { + "$ref": "search.json#/definitions/MatchesModel" + } + } + } + }, + "CompanySearch": { + "title": "CompanySearch", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearch" + } + ], + "properties": { + "kind": { + "type": "string", + "description": "The type of search response returned.", + "enum": [ + "search#companies" + ] + }, + "items": { + "type": "array", + "description": "The results of the completed search.", + "items": { + "$ref": "search.json#/definitions/CompanySearchItems" + } + } + } + }, + "Search": { + "title": "Search", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearch" + } + ], + "properties": { + "kind": { + "type": "string", + "description": "The type of search response returned.", + "enum": [ + "search#all" + ] + }, + "items": { + "type": "array", + "description": "The results of the completed search. See `items.kind` for details of each specific result resource returned.,", + "items": { + "$ref": "search.json#/definitions/SearchItems" + } + } + } + }, + "CompanySearchItems": { + "title": "CompanySearchItems", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearchItems" + } + ], + "required": [ + "kind", + "title", + "address_snippet", + "links", + "company_number", + "date_of_creation", + "company_type", + "company_status", + "address" + ], + "properties": { + "kind": { + "type": "string", + "description": "The type of search result.", + "enum": [ + "searchresults#company" + ] + }, + "description_identifier": { + "items": { + "type": "string", + "enum": [ + "incorporated-on", + "registered-on", + "formed-on", + "dissolved-on", + "converted-closed-on", + "closed-on", + "closed", + "first-uk-establishment-opened-on", + "opened-on", + "voluntary-arrangement", + "receivership", + "insolvency-proceedings", + "liquidation", + "administration", + "registered", + "removed" + ] + }, + "type": "array", + "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations" + }, + "company_number": { + "type": "string", + "description": "The company registration / incorporation number of the company." + }, + "date_of_creation": { + "type": "string", + "format": "date", + "description": "The date the company was created." + }, + "date_of_cessation": { + "type": "string", + "format": "date", + "description": "The date the company ended." + }, + "company_type": { + "type": "string", + "enum": [ + "private-unlimited", + "ltd", + "plc", + "old-public-company", + "private-limited-guarant-nsc-limited-exemption", + "limited-partnership", + "private-limited-guarant-nsc", + "converted-or-closed", + "private-unlimited-nsc", + "private-limited-shares-section-30-exemption", + "assurance-company", + "oversea-company", + "eeig", + "icvc-securities", + "icvc-warrant", + "icvc-umbrella", + "industrial-and-provident-society", + "northern-ireland", + "northern-ireland-other", + "royal-charter", + "investment-company-with-variable-capital", + "unregistered-company", + "llp", + "other", + "european-public-limited-liability-company-se", + "registered-overseas-entity" + ], + "description": "The company type." + }, + "company_status": { + "type": "string", + "enum": [ + "active", + "dissolved", + "liquidation", + "receivership", + "administration", + "voluntary-arrangement", + "converted-closed", + "insolvency-proceedings", + "registered", + "removed" + ], + "description": "The company status." + }, + "address": { + "description": "The address of the company's registered office.", + "type": "object", + "items": { + "$ref": "search.json#/definitions/registeredOfficeAddress" + } + } + } + }, + "SearchItems": { + "title": "SearchItems", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearchItems" + } + ], + "required": [ + "kind", + "title", + "address_snippet", + "links", + "address" + ], + "properties": { + "kind": { + "type": "string", + "description": "The type of search result. Refer to the full resource descriptions [CompanySearch resource](api/docs/company/company_number/CompanySearch-resource.html) [OfficerSearch resource] (api/docs/company/company_number/OfficerSearch-resource.html) and [DisqualifiedOfficerSearch resource](api/docs/company/company_number/DisqualifiedOfficerSearch-resource.html) for the full list of members returned.", + "enum": [ + "searchresults#company", + "searchresults#officer", + "searchresults#persons-with-significant-control", + "searchresults#disqualified-officer" + ] + }, + "description_identifier": { + "items": { + "type": "string", + "enum": [ + "incorporated-on", + "registered-on", + "formed-on", + "dissolved-on", + "converted-closed-on", + "closed-on", + "closed", + "first-uk-establishment-opened-on", + "opened-on", + "voluntary-arrangement", + "receivership", + "insolvency-proceedings", + "liquidation", + "administration", + "appointment-count", + "born-on", + "registered", + "removed" + ] + }, + "type": "array", + "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations" + }, + "address": { + "description": "The address of the company's registered office.", + "type": "object", + "items": { + "$ref": "search.json#/definitions/registeredOfficeAddress" + } + } + } + }, + "LinksModel": { + "title": "LinksModel", + "properties": { + "self": { + "type": "string", + "description": "The URL of the resource being returned by the search item." + } + } + }, + "MatchesModel": { + "title": "MatchesModel", + "properties": { + "title": { + "items": { + "type": "integer" + }, + "type": "array", + "description": "An array of character offset into the `title` string. These always occur in pairs and define the start and end of substrings in the member `title` that matched the search terms. The first character of the string is index 1." + }, + "snippet": { + "items": { + "type": "integer" + }, + "type": "array", + "description": "An array of character offset into the `snippet` string. These always occur in pairs and define the start and end of substrings in the member `snippet` that matched the search terms. The first character of the string is index 1." + }, + "address_snippet": { + "items": { + "type": "integer" + }, + "type": "array", + "description": "An array of character offset into the `address_snippet` string. These always occur in pairs and define the start and end of substrings in the member `address_snippet` that matched the search terms." + } + } + }, + "registeredOfficeAddress": { + "title": "registeredOfficeAddress", + "required": [ + "address_line_1" + ], + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "country": { + "description": "The country.", + "enum": [ + "Wales", + "England", + "Scotland", + "Great Britain", + "Not specified", + "United Kingdom", + "Northern Ireland" + ], + "type": "string" + }, + "locality": { + "description": "The locality e.g London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code e.g CF14 3UZ.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "region": { + "description": "The region e.g Surrey.", + "type": "string" + } + } + }, + "OfficerSearch": { + "title": "OfficerSearch", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearch" + } + ], + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "description": "The type of response returned.", + "enum": [ + "search#officers" + ] + }, + "items": { + "type": "array", + "description": "The results of the completed search.", + "items": { + "$ref": "search.json#/definitions/OfficerSearchItems" + } + } + } + }, + "OfficerDateOfBirth": { + "title": "OfficerDateOfBirth", + "required": [ + "month", + "year" + ], + "properties": { + "month": { + "description": "The month the officer was born in.", + "type": "integer" + }, + "year": { + "description": "The year the officer was born in.", + "type": "integer" + } + } + }, + "OfficerSearchItems": { + "title": "OfficerSearchItems", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearchItems" + } + ], + "required": [ + "appointment_count", + "description", + "kind", + "title", + "address_snippet", + "address" + ], + "properties": { + "kind": { + "type": "string", + "description": "Describes the type of result returned.", + "enum": [ + "searchresults#officer" + ] + }, + "date_of_birth": { + "description": "The officer date of birth details.", + "items": { + "$ref": "search.json#/definitions/OfficerDateOfBirth" + } + }, + "appointment_count": { + "type": "integer", + "description": "The total number of appointments the officer has." + }, + "description_identifiers": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "appointment-count", + "born-on" + ] + }, + "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations." + }, + "address": { + "type": "object", + "description": "The service address of the officer.", + "items": { + "$ref": "search.json#/definitions/OfficerAddress" + } + } + } + }, + "OfficerAddress": { + "title": "OfficerAddress", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "country": { + "description": "The country. For example UK.", + "type": "string" + }, + "locality": { + "description": "The locality. For example London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code. For example CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region. For example Surrey.", + "type": "string" + } + } + }, + "PersonsWithSignificantControlSearch": { + "title": "PersonswithsignificantcontrolSearch", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearch" + } + ], + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "description": "The type of response returned.", + "enum": [ + "search#persons-with-significant-control" + ] + }, + "items": { + "type": "array", + "description": "The results of the completed search.", + "items": { + "$ref": "search.json#/definitions/PersonsWithSignificantControlSearchItems" + } + } + } + }, + "PersonsWithSignificantControlDateOfBirth": { + "title": "PersonswithsignificantcontrolDateOfBirth", + "required": [ + "month", + "year" + ], + "properties": { + "month": { + "description": "The month the person with significant control was born in.", + "type": "integer" + }, + "year": { + "description": "The year the person with significant control was born in.", + "type": "integer" + } + } + }, + "PersonsWithSignificantControlAddress": { + "title": "PersonswithsignificantcontrolAddress", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "care_of": { + "description": "The care of name.", + "type": "string" + }, + "country": { + "description": "The country. For example UK.", + "type": "string" + }, + "locality": { + "description": "The locality. For example London.", + "type": "string" + }, + "po_box": { + "description": "The post-office box number.", + "type": "string" + }, + "postal_code": { + "description": "The postal code. For example CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region. For example Surrey.", + "type": "string" + } + } + }, + "PersonsWithSignificantControlSearchItems": { + "title": "PersonswithsignificantcontrolSearchItems", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearchItems" + } + ], + "required": [ + "notification_count", + "description", + "kind", + "title", + "address_snippet", + "address" + ], + "properties": { + "kind": { + "type": "string", + "description": "Describes the type of result returned.", + "enum": [ + "searchresults#persons-with-significant-control" + ] + }, + "notification_count": { + "type": "integer", + "description": "The total number of notifications the person with significant control has." + }, + "date_of_birth": { + "description": "The person with significant control date of birth details.", + "items": { + "$ref": "search.json#/definitions/PersonsWithSignificantControlDateOfBirth" + } + }, + "description_identifiers": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "notification-count", + "born-on" + ] + }, + "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations." + }, + "address": { + "type": "object", + "description": "The service address of the person with significant control.", + "items": { + "$ref": "search.json#/definitions/PersonsWithSignificantControlAddress" + } + } + } + }, + "DisqualifiedOfficerSearch": { + "title": "DisqualifiedOfficerSearch", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearch" + } + ], + "required": [ + "kind", + "total_results", + "start_index", + "items_per_page" + ], + "properties": { + "kind": { + "type": "string", + "description": "The type of response returned.", + "enum": [ + "search#disqualified-officers" + ] + }, + "items": { + "type": "array", + "description": "The results of the completed search.", + "items": { + "$ref": "search.json#/definitions/DisqualifiedOfficerSearchItems" + } + } + } + }, + "DisqualifiedOfficerSearchItems": { + "title": "DisqualifiedOfficerSearchItems", + "allOf": [ + { + "$ref": "search.json#/definitions/CommonSearchItems" + } + ], + "required": [ + "kind", + "title", + "description", + "address", + "address_snippet" + ], + "properties": { + "kind": { + "type": "string", + "description": "Describes the type of result returned.", + "enum": [ + "searchresults#disqualified-officer" + ] + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "The disqualified officer's date of birth." + }, + "description_identifiers": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "born-on" + ] + }, + "description": "An array of enumeration types that make up the search description. See search_descriptions_raw.yaml in api-enumerations." + }, + "address": { + "type": "object", + "description": "The address of the disqualified officer as provided by the disqualifying authority.", + "items": { + "$ref": "search.json#/definitions/DisqualifiedOfficerAddress" + } + } + } + }, + "DisqualifiedOfficerAddress": { + "title": "DisqualifiedOfficerAddress", + "properties": { + "address_line_1": { + "description": "The first line of the address.", + "type": "string" + }, + "address_line_2": { + "description": "The second line of the address.", + "type": "string" + }, + "country": { + "description": "The country. For example UK.", + "type": "string" + }, + "locality": { + "description": "The locality. For example London.", + "type": "string" + }, + "postal_code": { + "description": "The postal code. For example CF14 3UZ.", + "type": "string" + }, + "premises": { + "description": "The property name or number.", + "type": "string" + }, + "region": { + "description": "The region. For example Surrey.", + "type": "string" + } + } + } + } +} + diff --git a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs index 71a9dc3..2bac502 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/Accounts.cs @@ -7,7 +7,7 @@ namespace CompaniesHouse.Response.CompanyProfile public class Accounts { [JsonPropertyName("accounting_reference_date")] - public AccountingReferenceDate? AccountingReferenceDate { get; set; } + public AccountingReferenceDate AccountingReferenceDate { get; set; } = new(); [JsonPropertyName("last_accounts")] public LastAccounts? LastAccounts { get; set; } @@ -22,10 +22,10 @@ public class Accounts [JsonPropertyName("next_made_up_to")] [Obsolete("Deprecated - use NextAccounts.PeriodEndOn")] - public DateTime? NextMadeUpTo { get; set; } + public DateTime NextMadeUpTo { get; set; } [JsonPropertyName("overdue")] [Obsolete("Deprecated - use NextAccounts.Overdue")] - public bool? Overdue { get; set; } + public bool Overdue { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs index 9d68426..3a88047 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs @@ -22,13 +22,13 @@ public class CompanyProfile public ConfirmationStatement? ConfirmationStatement { get; set; } [JsonPropertyName("can_file")] - public bool? CanFile { get; set; } + public bool CanFile { get; set; } [JsonPropertyName("company_name")] - public string? CompanyName { get; set; } + public string CompanyName { get; set; } = string.Empty; [JsonPropertyName("company_number")] - public string? CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = string.Empty; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -75,7 +75,7 @@ public class CompanyProfile public DateTime? LastFullMembersListDate { get; set; } [JsonPropertyName("links")] - public CompanyProfileLinks? Links { get; set; } + public CompanyProfileLinks Links { get; set; } = new(); [JsonPropertyName("previous_company_names")] public PreviousCompanyName[]? PreviousCompanyNames { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs index 45e373b..73c1e47 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs @@ -29,7 +29,7 @@ public class CompanyProfileLinks public string? Registers { get; set; } [JsonPropertyName("self")] - public string? Self { get; set; } + public string Self { get; set; } = string.Empty; [JsonPropertyName("uk_establishments")] public string? UkEstablishments { get; set; } diff --git a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs index 4b99c08..37e2ba3 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs @@ -9,7 +9,7 @@ namespace CompaniesHouse.Response.CompanyProfile public class PreviousCompanyName { [JsonPropertyName("name")] - public string? Name { get; set; } + public string Name { get; set; } = string.Empty; [JsonPropertyName("ceased_on")] public DateTime CeasedOn { get; set; } diff --git a/src/CompaniesHouse/Response/DateOfBirth.cs b/src/CompaniesHouse/Response/DateOfBirth.cs index 7c172c5..8a78656 100644 --- a/src/CompaniesHouse/Response/DateOfBirth.cs +++ b/src/CompaniesHouse/Response/DateOfBirth.cs @@ -8,9 +8,9 @@ public class DateOfBirth public int? Day { get; set; } [JsonPropertyName("month")] - public int? Month { get; set; } + public int Month { get; set; } [JsonPropertyName("year")] - public int? Year { get; set; } + public int Year { get; set; } } } diff --git a/src/CompaniesHouse/Response/Officers/Officer.cs b/src/CompaniesHouse/Response/Officers/Officer.cs index c687365..a86ddeb 100644 --- a/src/CompaniesHouse/Response/Officers/Officer.cs +++ b/src/CompaniesHouse/Response/Officers/Officer.cs @@ -23,7 +23,7 @@ public class Officer public OfficerDateOfBirth? DateOfBirth { get; set; } [JsonPropertyName("name")] - public string? Name { get; set; } + public string Name { get; set; } = string.Empty; [JsonPropertyName("officer_role")] public OfficerRole OfficerRole { get; set; } @@ -47,7 +47,7 @@ public class Officer public OfficerIdentification? Identification { get; set; } [JsonPropertyName("links")] - public OfficerLinks? Links { get; set; } + public OfficerLinks Links { get; set; } = new(); [JsonPropertyName("person_number")] public string? PersonNumber { get; set; } @@ -59,6 +59,6 @@ public class Officer public IdentityVerificationDetails? IdentityVerificationDetails { get; set; } [JsonIgnore] - public string? OfficerId => Links?.Officer?.OfficerId; + public string? OfficerId => Links.Officer?.OfficerId; } } diff --git a/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs b/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs index df9f082..ece519e 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs @@ -9,9 +9,9 @@ public class OfficerDateOfBirth public int? Day { get; set; } [JsonPropertyName("month")] - public int? Month { get; set; } + public int Month { get; set; } [JsonPropertyName("year")] - public int? Year { get; set; } + public int Year { get; set; } } } diff --git a/src/CompaniesHouse/Response/Officers/Officers.cs b/src/CompaniesHouse/Response/Officers/Officers.cs index 7fe978a..57e3cab 100644 --- a/src/CompaniesHouse/Response/Officers/Officers.cs +++ b/src/CompaniesHouse/Response/Officers/Officers.cs @@ -5,28 +5,28 @@ namespace CompaniesHouse.Response.Officers public class Officers { [JsonPropertyName("etag")] - public string? ETag { get; set; } + public string ETag { get; set; } = string.Empty; [JsonPropertyName("active_count")] - public int? ActiveCount { get; set; } + public int ActiveCount { get; set; } [JsonPropertyName("inactive_count")] public int? InactiveCount { get; set; } [JsonPropertyName("items")] - public Officer[]? Items { get; set; } + public Officer[] Items { get; set; } = []; [JsonPropertyName("items_per_page")] - public int? ItemsPerPage { get; set; } + public int ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string? Kind { get; set; } + public string Kind { get; set; } = string.Empty; [JsonPropertyName("links")] - public OfficersListLinks? Links { get; set; } + public OfficersListLinks Links { get; set; } = new(); [JsonPropertyName("resigned_count")] - public int? ResignedCount { get; set; } + public int ResignedCount { get; set; } [JsonPropertyName("total_results")] public int TotalResults { get; set; } diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs index 6d82d83..84da946 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs @@ -8,15 +8,15 @@ public class AdvancedCompanySearch public string? ETag { get; set; } [JsonPropertyName("hits")] - public int? Hits { get; set; } + public int Hits { get; set; } [JsonPropertyName("items")] - public Company[]? Items { get; set; } + public Company[] Items { get; set; } = []; [JsonPropertyName("kind")] - public string? Kind { get; set; } + public string Kind { get; set; } = string.Empty; [JsonPropertyName("top_hit")] - public Company? TopHit { get; set; } + public Company TopHit { get; set; } = new(); } } diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs index 4e9ff35..4207c7d 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs @@ -8,10 +8,10 @@ namespace CompaniesHouse.Response.Search.AdvancedCompanySearch public class Company { [JsonPropertyName("company_name")] - public string? CompanyName { get; set; } + public string CompanyName { get; set; } = string.Empty; [JsonPropertyName("company_number")] - public string? CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = string.Empty; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -27,10 +27,10 @@ public class Company public DateTime? DateOfCessation { get; set; } [JsonPropertyName("date_of_creation")] - public DateTime? DateOfCreation { get; set; } + public DateTime DateOfCreation { get; set; } [JsonPropertyName("kind")] - public string? Kind { get; set; } + public string Kind { get; set; } = string.Empty; [JsonPropertyName("links")] public global::CompaniesHouse.Response.Search.CompanyProfileLinks? Links { get; set; } diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs index 7c1c041..7ebb23a 100644 --- a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/Company.cs @@ -6,10 +6,10 @@ namespace CompaniesHouse.Response.Search.CompaniesAlphabeticallySearch public class Company { [JsonPropertyName("company_name")] - public string? CompanyName { get; set; } + public string CompanyName { get; set; } = string.Empty; [JsonPropertyName("company_number")] - public string? CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = string.Empty; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -21,7 +21,7 @@ public class Company public string? Kind { get; set; } [JsonPropertyName("links")] - public global::CompaniesHouse.Response.Search.CompanyProfileLinks? Links { get; set; } + public global::CompaniesHouse.Response.Search.CompanyProfileLinks Links { get; set; } = new(); [JsonPropertyName("ordered_alpha_key_with_id")] public string? OrderedAlphaKeyWithId { get; set; } diff --git a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs index b6c18ee..16e5688 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs @@ -7,13 +7,13 @@ namespace CompaniesHouse.Response.Search.CompanySearch public class Company : SearchItem { [JsonPropertyName("address")] - public Address? Address { get; set; } + public Address Address { get; set; } = new(); [JsonPropertyName("address_snippet")] - public string? AddressSnippet { get; set; } + public string AddressSnippet { get; set; } = string.Empty; [JsonPropertyName("company_number")] - public string? CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = string.Empty; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } @@ -26,7 +26,7 @@ public class Company : SearchItem public DateTime? DateOfCessation { get; set; } [JsonPropertyName("date_of_creation")] - public DateTime? DateOfCreation { get; set; } + public DateTime DateOfCreation { get; set; } [JsonPropertyName("description")] public string? Description { get; set; } @@ -44,6 +44,6 @@ public class Company : SearchItem public string? Snippet { get; set; } [JsonPropertyName("title")] - public string? Title { get; set; } + public string Title { get; set; } = string.Empty; } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs index 87f4915..623d23c 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficer.cs @@ -6,16 +6,16 @@ namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch public class DisqualifiedOfficer : SearchItem { [JsonPropertyName("address")] - public Address? Address { get; set; } + public Address Address { get; set; } = new(); [JsonPropertyName("address_snippet")] - public string? AddressSnippet { get; set; } + public string AddressSnippet { get; set; } = string.Empty; [JsonPropertyName("date_of_birth")] public DateTime DateOfBirth { get; set; } [JsonPropertyName("description")] - public string? Description { get; set; } + public string Description { get; set; } = string.Empty; [JsonPropertyName("description_identifiers")] public string[]? DescriptionIdentifiers { get; set; } @@ -27,6 +27,6 @@ public class DisqualifiedOfficer : SearchItem public string? Snippet { get; set; } [JsonPropertyName("title")] - public string? Title { get; set; } + public string Title { get; set; } = string.Empty; } } diff --git a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs index ce3fab8..1e8e3a3 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs @@ -11,7 +11,7 @@ public class DisqualifiedOfficerSearch public int ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string? Kind { get; set; } + public string Kind { get; set; } = string.Empty; [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs index 9cbc07f..cb9d072 100644 --- a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs @@ -1,6 +1,5 @@ using System; using System.Text.Json.Serialization; -using CompaniesHouse.JsonConverters; using CompaniesHouse.Response; namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch @@ -8,20 +7,19 @@ namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch public class Company { [JsonPropertyName("company_name")] - public string? CompanyName { get; set; } + public string CompanyName { get; set; } = string.Empty; [JsonPropertyName("company_number")] - public string? CompanyNumber { get; set; } + public string CompanyNumber { get; set; } = string.Empty; [JsonPropertyName("company_status")] public CompanyStatus CompanyStatus { get; set; } [JsonPropertyName("date_of_cessation")] - [JsonConverter(typeof(OptionalDateJsonConverter))] - public DateTime? DateOfCessation { get; set; } + public DateTime DateOfCessation { get; set; } [JsonPropertyName("date_of_creation")] - public DateTime? DateOfCreation { get; set; } + public DateTime DateOfCreation { get; set; } [JsonPropertyName("kind")] public string? Kind { get; set; } diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs index a36edce..2146075 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs @@ -5,10 +5,10 @@ namespace CompaniesHouse.Response.Search.OfficerSearch public class Officer : SearchItem { [JsonPropertyName("address")] - public Address? Address { get; set; } + public Address Address { get; set; } = new(); [JsonPropertyName("address_snippet")] - public string? AddressSnippet { get; set; } + public string AddressSnippet { get; set; } = string.Empty; [JsonPropertyName("appointment_count")] public int AppointmentCount { get; set; } @@ -17,7 +17,7 @@ public class Officer : SearchItem public DateOfBirth? DateOfBirth { get; set; } [JsonPropertyName("description")] - public string? Description { get; set; } + public string Description { get; set; } = string.Empty; [JsonPropertyName("description_identifiers")] public string[]? DescriptionIdentifiers { get; set; } @@ -29,7 +29,7 @@ public class Officer : SearchItem public string? Snippet { get; set; } [JsonPropertyName("title")] - public string? Title { get; set; } + public string Title { get; set; } = string.Empty; public string? OfficerId { diff --git a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs index cdfb511..010e045 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs @@ -12,7 +12,7 @@ public class OfficerSearch public int ItemsPerPage { get; set; } [JsonPropertyName("kind")] - public string? Kind { get; set; } + public string Kind { get; set; } = string.Empty; [JsonPropertyName("page_number")] public int? PageNumber { get; set; } diff --git a/swagger.json b/swagger.json deleted file mode 100644 index 8097c5c..0000000 --- a/swagger.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Companies House Public Data API", - "description": "An API suite providing read only access to search and retrieve public company data" - }, - "host": "api.company-information.service.gov.uk", - "schemes": [ - "https", - "http" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [{ - "name": "registeredOfficeAddress", - "description": "Registered office address" - }, - { - "name": "companyProfile", - "description": "Company profile" - }, - { - "name": "search", - "description": "Search" - }, - { - "name": "officers", - "description": "Officers" - }, - { - "name": "registers", - "description": "Registers" - }, - { - "name": "charges", - "description": "Charges" - }, - { - "name": "filingHistory", - "description": "Filing history" - }, - { - "name": "insolvency", - "description": "Insolvency" - }, - { - "name": "exemptions", - "description": "Exemptions" - }, - { - "name": "officerDisqualifications", - "description": "Officer disqualifications" - }, - { - "name": "officerAppointments", - "description": "Officer appointments" - }, - { - "name": "UKEstablishments", - "description": "UK Establishments" - }, - { - "name": "personsWithSignificantControl", - "description": "Persons with significant control" - }, - { - "name": "pscDiscrepancies", - "description": "PSC discrepancies" - }, - { - "name": "personsWithSignificantControlNotifications", - "description": "Persons with significant control notifications" - } - ], - "securityDefinitions": { - "api_key": { - "type": "apiKey", - "name": "api_key", - "in": "header" - } - }, - "security": [{ - "api_key": [] - } - ], - "paths": { - "/company/{companyNumber}/registered-office-address": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyAddress.json#/getCompanyAddress" - }, - "/company/{companyNumber}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyProfile.json" - }, - "/search": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchAll" - }, - "/search/companies": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchCompanies" - }, - "/search/officers": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchOfficers" - }, - "/search/disqualified-officers": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search.json#/searchDisqualified-officers" - }, - "/dissolved-search/companies": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchDissolved" - }, - "/alphabetical-search/companies": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAlphabetic" - }, - "/advanced-search/companies": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/search-companies.json#/searchAdvanced" - }, - "/company/{company_number}/officers": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/listCompanyOfficers" - }, - "/company/{company_number}/appointments/{appointment_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyOfficerList.json#/getCompanyOfficerAppointment" - }, - "/company/{company_number}/registers": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyRegisters.json" - }, - "/company/{company_number}/filing-history/{transaction_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/getFilingHistory" - }, - "/company/{company_number}/filing-history": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/filingHistory.json#/listFilingHistory" - }, - "/company/{company_number}/exemptions": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/exemptions.json" - }, - "/disqualified-officers/natural/{officer_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getNatural" - }, - "/disqualified-officers/corporate/{officer_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/disqualifications.json#/getCorporate" - }, - "/officers/{officer_id}/appointments": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/officerAppointmentList.json" - }, - "/company/{company_number}/charges": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeList" - }, - "/company/{company_number}/charges/{charge_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/charges.json#/chargeDetails" - }, - "/company/{company_number}/insolvency": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/insolvency.json#/insolvencyCase" - }, - "/company/{company_number}/uk-establishments": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/companyUKEstablishments.json" - }, - "/company/{company_number}/persons-with-significant-control": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSC" - }, - "/company/{company_number}/persons-with-significant-control/individual/{notification_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualPSC" - }, - "/company/{company_number}/persons-with-significant-control/individual-beneficial-owner/{notification_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getIndividualBO" - }, - "/company/{company_number}/persons-with-significant-control/corporate-entity/{notification_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityPSC" - }, - "/company/{company_number}/persons-with-significant-control/corporate-entity-beneficial-owner/{notification_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getCorporateEntityBO" - }, - "/company/{company_number}/persons-with-significant-control/legal-person/{notification_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonPSC" - }, - "/company/{company_number}/persons-with-significant-control/legal-person-beneficial-owner/{notification_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getLegalPersonBO" - }, - "/company/{company_number}/persons-with-significant-control-statements": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/listCompanyPSCStatements" - }, - "/company/{company_number}/persons-with-significant-control-statements/{statement_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getPSCStatement" - }, - "/company/{company_number}/persons-with-significant-control/super-secure/{super_secure_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecurePSC" - }, - "/company/{company_number}/persons-with-significant-control/super-secure-beneficial-owner/{super_secure_id}": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/psc.json#/getSuperSecureBO" - }, - "/company/{company_number}/persons-with-significant-control/{psc_id}/notifications": { - "$ref": "http://127.0.0.1:10000/api.ch.gov.uk-specifications/swagger-2.0/spec/pscNotificationList.json" - } - } -} From 17fe11622018cc52150ec227d792a603b4d5f56b Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 23:30:14 +0100 Subject: [PATCH 33/38] Add company registers endpoint with full test coverage --- .plans/completed/09h-registers.md | 40 +++++++ .../09-endpoint-catalogue-remaining.md | 110 ++++-------------- src/CompaniesHouse/CompaniesHouseClient.cs | 8 ++ .../CompaniesHouseRegistersClient.cs | 30 +++++ src/CompaniesHouse/ICompaniesHouseClient.cs | 3 +- .../ICompaniesHouseRegistersClient.cs | 11 ++ .../Registers/CompanyRegisterEntry.cs | 16 +++ .../Registers/CompanyRegisterEntryLinks.cs | 28 +++++ .../Response/Registers/CompanyRegisterItem.cs | 17 +++ .../Registers/CompanyRegisterItemLinks.cs | 10 ++ .../Response/Registers/CompanyRegisters.cs | 22 ++++ .../Registers/CompanyRegistersEntries.cs | 28 +++++ .../Registers/CompanyRegistersLinks.cs | 10 ++ .../UriBuilders/CompanyRegistersUriBuilder.cs | 13 +++ .../ICompanyRegistersUriBuilder.cs | 9 ++ .../RegistersTests/RegistersTestsValid.cs | 30 +++++ .../RegistersScenarios.cs | 66 +++++++++++ .../CompaniesHouseRegistersClientTests.cs | 80 +++++++++++++ .../CompanyRegistersUriBuilderTests.cs | 18 +++ 19 files changed, 458 insertions(+), 91 deletions(-) create mode 100644 .plans/completed/09h-registers.md create mode 100644 src/CompaniesHouse/CompaniesHouseRegistersClient.cs create mode 100644 src/CompaniesHouse/ICompaniesHouseRegistersClient.cs create mode 100644 src/CompaniesHouse/Response/Registers/CompanyRegisterEntry.cs create mode 100644 src/CompaniesHouse/Response/Registers/CompanyRegisterEntryLinks.cs create mode 100644 src/CompaniesHouse/Response/Registers/CompanyRegisterItem.cs create mode 100644 src/CompaniesHouse/Response/Registers/CompanyRegisterItemLinks.cs create mode 100644 src/CompaniesHouse/Response/Registers/CompanyRegisters.cs create mode 100644 src/CompaniesHouse/Response/Registers/CompanyRegistersEntries.cs create mode 100644 src/CompaniesHouse/Response/Registers/CompanyRegistersLinks.cs create mode 100644 src/CompaniesHouse/UriBuilders/CompanyRegistersUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/ICompanyRegistersUriBuilder.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/RegistersTests/RegistersTestsValid.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/RegistersScenarios.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseRegistersClientTests/CompaniesHouseRegistersClientTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/CompanyRegistersUriBuilderTests/CompanyRegistersUriBuilderTests.cs diff --git a/.plans/completed/09h-registers.md b/.plans/completed/09h-registers.md new file mode 100644 index 0000000..7905770 --- /dev/null +++ b/.plans/completed/09h-registers.md @@ -0,0 +1,40 @@ +# 09h — Endpoint: Registers + +**Status:** completed +**Depends on:** `01-core` +**Blocks:** nothing + +## Goal + +Build the company registers endpoint from the live API docs. + +Docs: +- + +Path: +- `GET /company/{companyNumber}/registers` + +## Scope + +- Add a focused registers sub-client, URI builder and response models. +- Verify the live response shape before modelling fields. + +## Tasks + +- [x] Confirm the live docs and payload shape. +- [x] Build client, URI builder and response model. +- [x] Add unit, scenario and integration coverage. + +## Open questions + +- Which registers fields are actually present in live payloads, and are they link-only or richer nested objects? + - Answered: live payloads can be sparse and omit several fields marked required in the spec (for example, `company_number`, some register sections, and `links` inside register items). + +## Acceptance criteria + +- A real registers response deserializes cleanly from the live API. + +## References + +- No existing master implementation - build from the live API docs only. +- diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md index 6027a5e..963fd46 100644 --- a/.plans/outstanding/09-endpoint-catalogue-remaining.md +++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md @@ -1,93 +1,23 @@ -# 09 — Endpoint catalogue: the remaining API surface +# 09 — Remaining endpoint catalogue (split index) -**Status:** outstanding -**Depends on:** `01-core`, `03-value-types`; do after `08-officers` +**Status:** split +**Depends on:** `01-core`, `03-value-types` **Blocks:** nothing -## Goal - -Track and rebuild **every remaining endpoint** of the Companies House Public -Data API, one at a time, following the pattern established by plans `06`–`08`. -When picking up an endpoint from this catalogue, **split it into its own plan -file** (`09a-...`, `09b-...`, or a dedicated number) rather than doing it inline -here. - -Reference index: - - -## Catalogue (rebuild in roughly this order) - -Grouped by API tag. Confirm exact paths/params/schemas from the docs at -implementation time. - -### Registered office & registers -- [ ] **Registered office address** — `GET /company/{n}/registered-office-address` - (issues #163/#164, #179). -- [ ] **Registers** — `GET /company/{n}/registers`. - -### Filing history -- [ ] **Filing history list** — `GET /company/{n}/filing-history`. -- [ ] **Single filing** — `GET /company/{n}/filing-history/{transactionId}`. - Category/subcategory are the enum-heavy fields that caused #168 (`debenture`), - #209/#210, #218/#219 (`investment-company`) — use value types + the - `filing_history_descriptions.yml` generator data. Note subcategory can be - an array in some payloads (old `FilingSubcategoryConverter`). - -### Officers (related) -- [ ] **Company officer disqualifications (natural)** — - `GET /disqualified-officers/natural/{officerId}`. -- [ ] **Corporate officer disqualifications** — - `GET /disqualified-officers/corporate/{officerId}`. -- [ ] **Officer appointments list** — `GET /officers/{officerId}/appointments` - (the current `GetAppointmentsAsync`). - -### Persons with significant control (PSC) -- [ ] **PSC list** — `GET /company/{n}/persons-with-significant-control`. -- [ ] **Individual PSC** — `.../individual/{id}`. -- [ ] **Corporate entity PSC** — `.../corporate-entity/{id}`. -- [ ] **Legal person PSC** — `.../legal-person/{id}`. -- [ ] **PSC statements** — `.../statements` and `.../statements/{id}`. -- [ ] **Super-secure PSC** — `.../super-secure/{id}`. - PSC kinds/natures-of-control are enum-heavy (issues #200/#201/#211/#214); - use value types + `psc_descriptions.yml`. Ensure `total_results` - (issue #211) and `identification` (issue #173/#155) are modelled. - -### Charges -- [ ] **Charges list** — `GET /company/{n}/charges`. -- [ ] **Single charge** — `GET /company/{n}/charges/{chargeId}`. - Status/classification/particulars are enum-heavy — value types. - -### Insolvency & exemptions -- [ ] **Insolvency** — `GET /company/{n}/insolvency`. -- [ ] **Exemptions** — `GET /company/{n}/exemptions` - (uses `exemption_descriptions.yml`). - -### UK establishments -- [ ] **UK establishments** — `GET /company/{n}/uk-establishments`. - -### Documents (separate Document API host) -- [ ] **Document metadata** — Document API `GET /document/{id}`. -- [ ] **Download document** — `GET /document/{id}/content` (binary; keep the - separate base URI + document sub-client, and the DI document options). - -## Per-endpoint checklist (apply to each) - -- [ ] Confirm path, query params, and response schema from the live docs. -- [ ] Request model (if any) + URI builder following the established pattern. -- [ ] Response model faithful to docs; all enum-ish fields use value types. -- [ ] Sub-client interface hung off `CompaniesHouseClient` + DI registration. -- [ ] Tests: URI builder, deserialization scenario, integration. -- [ ] Move the split-out plan to `completed/` when done. - -## Open questions - -- Which endpoints are in-scope for the first v-next release vs a later minor? - (Lean: search + company profile + officers + registered office + filing - history + PSC + charges for the first stable; documents/exemptions/registers - can follow.) - -## References - -- Full reference index (above). Enum data in `api-enumerations` (plan `05`). -- Issues: #163/#164/#179, #168/#209/#210/#218/#219 (filing categories), - #155/#173/#200/#201/#211/#214 (PSC), #205 (SIC), #180 (sandbox). +## Split plans + +Completed in this change: +- `..\completed\09a-registered-office-address.md` +- `..\completed\09b-filing-history.md` +- `..\completed\09c-officer-appointments-list.md` +- `..\completed\09d-persons-with-significant-control-list.md` +- `..\completed\09e-charges.md` +- `..\completed\09f-insolvency.md` +- `..\completed\09g-documents.md` +- `..\completed\09h-registers.md` + +Still outstanding: +- `09i-disqualified-officers-detail.md` +- `09j-psc-detail-types.md` +- `09k-exemptions.md` +- `09l-uk-establishments.md` diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index 2fa102e..c0d83e1 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -10,6 +10,7 @@ using CompaniesHouse.Response.Insolvency; using CompaniesHouse.Response.Officers; using CompaniesHouse.Response.PersonsWithSignificantControl; +using CompaniesHouse.Response.Registers; using CompaniesHouse.Response.RegisteredOfficeAddress; using CompaniesHouse.Response.Search.AllSearch; using CompaniesHouse.Response.Search.AdvancedCompanySearch; @@ -35,6 +36,7 @@ public class CompaniesHouseClient : ICompaniesHouseClient, IDisposable private readonly ICompaniesHouseChargesClient _companiesHouseChargesClient; private readonly ICompaniesHouseRegisteredOfficeAddressClient _companiesHouseRegisteredOfficeAddressClient; private readonly ICompaniesHouseOfficerByAppointmentClient _companiesHouseOfficerByAppointmentClient; + private readonly ICompaniesHouseRegistersClient _companiesHouseRegistersClient; private readonly HttpClient _httpClient; public CompaniesHouseClient(HttpClient httpClient) @@ -50,6 +52,7 @@ public CompaniesHouseClient(HttpClient httpClient) _companiesHouseChargesClient = new CompaniesHouseChargesClient(_httpClient, new ChargesUriBuilder()); _companiesHouseRegisteredOfficeAddressClient = new CompaniesHouseRegisteredOfficeAddressClient(_httpClient, new RegisteredOfficeAddressUriBuilder()); _companiesHouseOfficerByAppointmentClient = new CompaniesHouseOfficerByByAppointmentClient(_httpClient, new OfficersAppointmentUriBuilder()); + _companiesHouseRegistersClient = new CompaniesHouseRegistersClient(_httpClient, new CompanyRegistersUriBuilder()); } public CompaniesHouseClient(ICompaniesHouseSettings settings) @@ -156,6 +159,11 @@ public Task> GetOfficerByAppointmentIdAsync(stri return _companiesHouseOfficerByAppointmentClient.GetOfficerByAppointmentIdAsync(companyNumber, appointmentId, cancellationToken); } + public Task> GetCompanyRegistersAsync(string companyNumber, CancellationToken cancellationToken = default) + { + return _companiesHouseRegistersClient.GetCompanyRegistersAsync(companyNumber, cancellationToken); + } + public void Dispose() => _httpClient.Dispose(); } } diff --git a/src/CompaniesHouse/CompaniesHouseRegistersClient.cs b/src/CompaniesHouse/CompaniesHouseRegistersClient.cs new file mode 100644 index 0000000..db9844a --- /dev/null +++ b/src/CompaniesHouse/CompaniesHouseRegistersClient.cs @@ -0,0 +1,30 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.Registers; +using CompaniesHouse.UriBuilders; + +namespace CompaniesHouse +{ + using CompaniesHouse.Extensions; + + public class CompaniesHouseRegistersClient : ICompaniesHouseRegistersClient + { + private readonly HttpClient _httpClient; + private readonly ICompanyRegistersUriBuilder _companyRegistersUriBuilder; + + public CompaniesHouseRegistersClient(HttpClient httpClient, ICompanyRegistersUriBuilder companyRegistersUriBuilder) + { + _httpClient = httpClient; + _companyRegistersUriBuilder = companyRegistersUriBuilder; + } + + public async Task> GetCompanyRegistersAsync(string companyNumber, CancellationToken cancellationToken = default) + { + var requestUri = _companyRegistersUriBuilder.Build(companyNumber); + var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); + + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/CompaniesHouse/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index 37956c5..251b5ae 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -15,7 +15,8 @@ public interface ICompaniesHouseClient : ICompaniesHouseAppointmentsClient, ICompaniesHousePersonsWithSignificantControlClient, ICompaniesHouseChargesClient, - ICompaniesHouseRegisteredOfficeAddressClient + ICompaniesHouseRegisteredOfficeAddressClient, + ICompaniesHouseRegistersClient { } diff --git a/src/CompaniesHouse/ICompaniesHouseRegistersClient.cs b/src/CompaniesHouse/ICompaniesHouseRegistersClient.cs new file mode 100644 index 0000000..8c3a496 --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHouseRegistersClient.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.Registers; + +namespace CompaniesHouse +{ + public interface ICompaniesHouseRegistersClient + { + Task> GetCompanyRegistersAsync(string companyNumber, CancellationToken cancellationToken = default); + } +} diff --git a/src/CompaniesHouse/Response/Registers/CompanyRegisterEntry.cs b/src/CompaniesHouse/Response/Registers/CompanyRegisterEntry.cs new file mode 100644 index 0000000..abf6092 --- /dev/null +++ b/src/CompaniesHouse/Response/Registers/CompanyRegisterEntry.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Registers +{ + public class CompanyRegisterEntry + { + [JsonPropertyName("register_type")] + public string RegisterType { get; set; } = string.Empty; + + [JsonPropertyName("items")] + public CompanyRegisterItem[] Items { get; set; } = []; + + [JsonPropertyName("links")] + public CompanyRegisterEntryLinks? Links { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Registers/CompanyRegisterEntryLinks.cs b/src/CompaniesHouse/Response/Registers/CompanyRegisterEntryLinks.cs new file mode 100644 index 0000000..2dde4bb --- /dev/null +++ b/src/CompaniesHouse/Response/Registers/CompanyRegisterEntryLinks.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Registers +{ + public class CompanyRegisterEntryLinks + { + [JsonPropertyName("directors_register")] + public string? DirectorsRegister { get; set; } + + [JsonPropertyName("secretaries_register")] + public string? SecretariesRegister { get; set; } + + [JsonPropertyName("persons_with_significant_control_register")] + public string? PersonsWithSignificantControlRegister { get; set; } + + [JsonPropertyName("usual_residential_address")] + public string? UsualResidentialAddress { get; set; } + + [JsonPropertyName("llp_usual_residential_address")] + public string? LlpUsualResidentialAddress { get; set; } + + [JsonPropertyName("members")] + public string? Members { get; set; } + + [JsonPropertyName("llp_members")] + public string? LlpMembers { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Registers/CompanyRegisterItem.cs b/src/CompaniesHouse/Response/Registers/CompanyRegisterItem.cs new file mode 100644 index 0000000..4c64043 --- /dev/null +++ b/src/CompaniesHouse/Response/Registers/CompanyRegisterItem.cs @@ -0,0 +1,17 @@ +using System; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Registers +{ + public class CompanyRegisterItem + { + [JsonPropertyName("moved_on")] + public DateTime MovedOn { get; set; } + + [JsonPropertyName("register_moved_to")] + public string RegisterMovedTo { get; set; } = string.Empty; + + [JsonPropertyName("links")] + public CompanyRegisterItemLinks? Links { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Registers/CompanyRegisterItemLinks.cs b/src/CompaniesHouse/Response/Registers/CompanyRegisterItemLinks.cs new file mode 100644 index 0000000..a4c47a7 --- /dev/null +++ b/src/CompaniesHouse/Response/Registers/CompanyRegisterItemLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Registers +{ + public class CompanyRegisterItemLinks + { + [JsonPropertyName("filing")] + public string Filing { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/Response/Registers/CompanyRegisters.cs b/src/CompaniesHouse/Response/Registers/CompanyRegisters.cs new file mode 100644 index 0000000..5a7a5c0 --- /dev/null +++ b/src/CompaniesHouse/Response/Registers/CompanyRegisters.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Registers +{ + public class CompanyRegisters + { + [JsonPropertyName("links")] + public CompanyRegistersLinks Links { get; set; } = new(); + + [JsonPropertyName("company_number")] + public string? CompanyNumber { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("registers")] + public CompanyRegistersEntries Registers { get; set; } = new(); + + [JsonPropertyName("etag")] + public string? Etag { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Registers/CompanyRegistersEntries.cs b/src/CompaniesHouse/Response/Registers/CompanyRegistersEntries.cs new file mode 100644 index 0000000..1678ac7 --- /dev/null +++ b/src/CompaniesHouse/Response/Registers/CompanyRegistersEntries.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Registers +{ + public class CompanyRegistersEntries + { + [JsonPropertyName("directors")] + public CompanyRegisterEntry? Directors { get; set; } + + [JsonPropertyName("secretaries")] + public CompanyRegisterEntry? Secretaries { get; set; } + + [JsonPropertyName("persons_with_significant_control")] + public CompanyRegisterEntry? PersonsWithSignificantControl { get; set; } + + [JsonPropertyName("usual_residential_address")] + public CompanyRegisterEntry? UsualResidentialAddress { get; set; } + + [JsonPropertyName("llp_usual_residential_address")] + public CompanyRegisterEntry? LlpUsualResidentialAddress { get; set; } + + [JsonPropertyName("members")] + public CompanyRegisterEntry? Members { get; set; } + + [JsonPropertyName("llp_members")] + public CompanyRegisterEntry? LlpMembers { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Registers/CompanyRegistersLinks.cs b/src/CompaniesHouse/Response/Registers/CompanyRegistersLinks.cs new file mode 100644 index 0000000..8c92300 --- /dev/null +++ b/src/CompaniesHouse/Response/Registers/CompanyRegistersLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Registers +{ + public class CompanyRegistersLinks + { + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/UriBuilders/CompanyRegistersUriBuilder.cs b/src/CompaniesHouse/UriBuilders/CompanyRegistersUriBuilder.cs new file mode 100644 index 0000000..0b5d713 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/CompanyRegistersUriBuilder.cs @@ -0,0 +1,13 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public class CompanyRegistersUriBuilder : ICompanyRegistersUriBuilder + { + public Uri Build(string companyNumber) + { + var path = $"company/{Uri.EscapeDataString(companyNumber)}/registers"; + return new Uri(path, UriKind.Relative); + } + } +} diff --git a/src/CompaniesHouse/UriBuilders/ICompanyRegistersUriBuilder.cs b/src/CompaniesHouse/UriBuilders/ICompanyRegistersUriBuilder.cs new file mode 100644 index 0000000..68cd2bc --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/ICompanyRegistersUriBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public interface ICompanyRegistersUriBuilder + { + Uri Build(string companyNumber); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegistersTests/RegistersTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegistersTests/RegistersTestsValid.cs new file mode 100644 index 0000000..c9e3fa1 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegistersTests/RegistersTestsValid.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.RegistersTests +{ + public class RegistersTestsValid + { + private readonly CompaniesHouseClient _client; + + public RegistersTestsValid() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(CompaniesHouseUris.Default, Keys.ApiKey)); + } + + [IntegrationFact] + public async Task ThenKnownCompanyRegistersIncludesObservedLiveFields() + { + var result = await _client.GetCompanyRegistersAsync("10725338"); + + result.Data.ShouldNotBeNull(); + result.Data.Kind.ShouldBe("registers"); + result.Data.Links.Self.ShouldBe("/company/10725338/registers"); + result.Data.Registers.Directors.ShouldNotBeNull(); + result.Data.Registers.Directors.Items.ShouldNotBeEmpty(); + result.Data.Registers.UsualResidentialAddress.ShouldNotBeNull(); + result.Data.Registers.UsualResidentialAddress.Items.ShouldNotBeEmpty(); + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/RegistersScenarios.cs b/tests/CompaniesHouse.ScenarioTests/RegistersScenarios.cs new file mode 100644 index 0000000..501d6fe --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/RegistersScenarios.cs @@ -0,0 +1,66 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response.Registers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class RegistersScenarios + { + [Fact] + public void CompanyRegisters_DeserializesObservedSparseLivePayload() + { + var value = JsonSerializer.Deserialize(RegistersJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Kind.ShouldBe("registers"); + value.Links.Self.ShouldBe("/company/10725338/registers"); + value.CompanyNumber.ShouldBeNull(); + value.Registers.Directors.ShouldNotBeNull(); + value.Registers.Directors.Items.Length.ShouldBe(2); + value.Registers.Directors.Items[0].MovedOn.ShouldBe(new DateTime(2025, 11, 18)); + value.Registers.Directors.Items[0].RegisterMovedTo.ShouldBe("unspecified-location"); + value.Registers.UsualResidentialAddress.ShouldNotBeNull(); + value.Registers.Secretaries.ShouldBeNull(); + } + + private const string RegistersJson = """ + { + "links": { + "self": "/company/10725338/registers" + }, + "kind": "registers", + "registers": { + "directors": { + "register_type": "directors", + "items": [ + { + "moved_on": "2025-11-18", + "register_moved_to": "unspecified-location" + }, + { + "moved_on": "2017-04-13", + "register_moved_to": "public-register" + } + ] + }, + "usual_residential_address": { + "register_type": "usual-residential-address", + "items": [ + { + "moved_on": "2025-11-18", + "register_moved_to": "unspecified-location" + }, + { + "moved_on": "2017-04-13", + "register_moved_to": "public-register" + } + ] + } + }, + "etag": "9b6222e6f8614ced4bf26e557e1e8fd811952487" + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseRegistersClientTests/CompaniesHouseRegistersClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseRegistersClientTests/CompaniesHouseRegistersClientTests.cs new file mode 100644 index 0000000..c0f80ca --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseRegistersClientTests/CompaniesHouseRegistersClientTests.cs @@ -0,0 +1,80 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.UriBuilders; +using Moq; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseRegistersClientTests +{ + public class CompaniesHouseRegistersClientTests + { + [Fact] + public async Task GivenARealCapturedRegistersPayload_WhenGettingCompanyRegisters_ThenObservedFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/company/10725338/registers"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, RealRegistersJson); + + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny())).Returns(uri); + + var client = new CompaniesHouseRegistersClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetCompanyRegistersAsync("10725338"); + + result.Data.ShouldNotBeNull(); + result.Data.Kind.ShouldBe("registers"); + result.Data.Etag.ShouldBe("9b6222e6f8614ced4bf26e557e1e8fd811952487"); + result.Data.Links.Self.ShouldBe("/company/10725338/registers"); + result.Data.CompanyNumber.ShouldBeNull(); + result.Data.Registers.Directors.ShouldNotBeNull(); + result.Data.Registers.Directors.RegisterType.ShouldBe("directors"); + result.Data.Registers.Directors.Items.Length.ShouldBe(2); + result.Data.Registers.Directors.Items[0].MovedOn.ShouldBe(new DateTime(2025, 11, 18)); + result.Data.Registers.Directors.Items[0].RegisterMovedTo.ShouldBe("unspecified-location"); + result.Data.Registers.Directors.Items[0].Links.ShouldBeNull(); + result.Data.Registers.UsualResidentialAddress.ShouldNotBeNull(); + result.Data.Registers.UsualResidentialAddress.RegisterType.ShouldBe("usual-residential-address"); + result.Data.Registers.Secretaries.ShouldBeNull(); + result.Data.Registers.Members.ShouldBeNull(); + } + + private const string RealRegistersJson = """ + { + "links": { + "self": "/company/10725338/registers" + }, + "kind": "registers", + "registers": { + "directors": { + "register_type": "directors", + "items": [ + { + "moved_on": "2025-11-18", + "register_moved_to": "unspecified-location" + }, + { + "moved_on": "2017-04-13", + "register_moved_to": "public-register" + } + ] + }, + "usual_residential_address": { + "register_type": "usual-residential-address", + "items": [ + { + "moved_on": "2025-11-18", + "register_moved_to": "unspecified-location" + }, + { + "moved_on": "2017-04-13", + "register_moved_to": "public-register" + } + ] + } + }, + "etag": "9b6222e6f8614ced4bf26e557e1e8fd811952487" + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/CompanyRegistersUriBuilderTests/CompanyRegistersUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/CompanyRegistersUriBuilderTests/CompanyRegistersUriBuilderTests.cs new file mode 100644 index 0000000..b7abdc1 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/CompanyRegistersUriBuilderTests/CompanyRegistersUriBuilderTests.cs @@ -0,0 +1,18 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.CompanyRegistersUriBuilderTests +{ + public class CompanyRegistersUriBuilderTests + { + [Fact] + public void Build_EncodesCompanyNumber() + { + var uri = new CompanyRegistersUriBuilder().Build("00/123"); + + uri.ShouldBe(new Uri("company/00%2F123/registers", UriKind.Relative)); + } + } +} From 35c94a975a0d2707e77c2e92087d7c8503be8ab1 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 23:33:40 +0100 Subject: [PATCH 34/38] Add disqualified officer detail endpoints and tests --- .../09i-disqualified-officers-detail.md | 43 ++++++ .../09-endpoint-catalogue-remaining.md | 2 +- src/CompaniesHouse/CompaniesHouseClient.cs | 13 ++ ...esHouseDisqualifiedOfficerDetailsClient.cs | 38 ++++++ src/CompaniesHouse/ICompaniesHouseClient.cs | 3 +- ...esHouseDisqualifiedOfficerDetailsClient.cs | 13 ++ .../CorporateDisqualification.cs | 34 +++++ .../DisqualificationCase.cs | 42 ++++++ .../DisqualificationLastVariation.cs | 17 +++ .../DisqualificationLinks.cs | 10 ++ .../DisqualificationPermissionToAct.cs | 20 +++ .../DisqualificationReason.cs | 19 +++ .../NaturalDisqualification.cs | 47 +++++++ .../DisqualifiedOfficerUriBuilder.cs | 19 +++ .../IDisqualifiedOfficerUriBuilder.cs | 11 ++ .../DisqualifiedOfficerDetailsTestsValid.cs | 64 +++++++++ .../DisqualifiedOfficerDetailsScenarios.cs | 83 ++++++++++++ ...seDisqualifiedOfficerDetailsClientTests.cs | 127 ++++++++++++++++++ .../DisqualifiedOfficerUriBuilderTests.cs | 26 ++++ 19 files changed, 629 insertions(+), 2 deletions(-) create mode 100644 .plans/completed/09i-disqualified-officers-detail.md create mode 100644 src/CompaniesHouse/CompaniesHouseDisqualifiedOfficerDetailsClient.cs create mode 100644 src/CompaniesHouse/ICompaniesHouseDisqualifiedOfficerDetailsClient.cs create mode 100644 src/CompaniesHouse/Response/DisqualifiedOfficers/CorporateDisqualification.cs create mode 100644 src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationCase.cs create mode 100644 src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLastVariation.cs create mode 100644 src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLinks.cs create mode 100644 src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationPermissionToAct.cs create mode 100644 src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationReason.cs create mode 100644 src/CompaniesHouse/Response/DisqualifiedOfficers/NaturalDisqualification.cs create mode 100644 src/CompaniesHouse/UriBuilders/DisqualifiedOfficerUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/IDisqualifiedOfficerUriBuilder.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/DisqualifiedOfficerDetailsTests/DisqualifiedOfficerDetailsTestsValid.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/DisqualifiedOfficerDetailsScenarios.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseDisqualifiedOfficerDetailsClientTests/CompaniesHouseDisqualifiedOfficerDetailsClientTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/DisqualifiedOfficerUriBuilderTests/DisqualifiedOfficerUriBuilderTests.cs diff --git a/.plans/completed/09i-disqualified-officers-detail.md b/.plans/completed/09i-disqualified-officers-detail.md new file mode 100644 index 0000000..4138412 --- /dev/null +++ b/.plans/completed/09i-disqualified-officers-detail.md @@ -0,0 +1,43 @@ +# 09i — Endpoint: Disqualified officers detail + +**Status:** completed +**Depends on:** `01-core`, `06-search` +**Blocks:** nothing + +## Goal + +Build the natural-person and corporate disqualified-officer detail endpoints from the live API docs. + +Docs: +- +- + +Paths: +- `GET /disqualified-officers/natural/{officerId}` +- `GET /disqualified-officers/corporate/{officerId}` + +## Scope + +- Add a focused sub-client, URI builders and response models for natural and corporate detail payloads. +- Reuse generated value types where the live schema exposes enum-ish fields. + +## Tasks + +- [x] Confirm live docs and payload examples. +- [x] Build the client, builders and models. +- [x] Add unit, scenario and integration coverage. + +## Open questions + +- Are the natural and corporate payloads structurally distinct enough to justify separate response types? + - Answered: yes; they share common nested shapes but differ in top-level identity fields (`surname`/name parts vs `name`, optional registration metadata), so separate top-level response types are clearer. + +## Acceptance criteria + +- Both disqualified-officer detail endpoints deserialize from the live API. + +## References + +- No existing master implementation - build from the live API docs only. +- +- diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md index 963fd46..3310754 100644 --- a/.plans/outstanding/09-endpoint-catalogue-remaining.md +++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md @@ -15,9 +15,9 @@ Completed in this change: - `..\completed\09f-insolvency.md` - `..\completed\09g-documents.md` - `..\completed\09h-registers.md` +- `..\completed\09i-disqualified-officers-detail.md` Still outstanding: -- `09i-disqualified-officers-detail.md` - `09j-psc-detail-types.md` - `09k-exemptions.md` - `09l-uk-establishments.md` diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index c0d83e1..e3fd0e8 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -7,6 +7,7 @@ using CompaniesHouse.Response.Charges; using CompaniesHouse.Response.CompanyFiling; using CompaniesHouse.Response.CompanyProfile; +using CompaniesHouse.Response.DisqualifiedOfficers; using CompaniesHouse.Response.Insolvency; using CompaniesHouse.Response.Officers; using CompaniesHouse.Response.PersonsWithSignificantControl; @@ -37,6 +38,7 @@ public class CompaniesHouseClient : ICompaniesHouseClient, IDisposable private readonly ICompaniesHouseRegisteredOfficeAddressClient _companiesHouseRegisteredOfficeAddressClient; private readonly ICompaniesHouseOfficerByAppointmentClient _companiesHouseOfficerByAppointmentClient; private readonly ICompaniesHouseRegistersClient _companiesHouseRegistersClient; + private readonly ICompaniesHouseDisqualifiedOfficerDetailsClient _companiesHouseDisqualifiedOfficerDetailsClient; private readonly HttpClient _httpClient; public CompaniesHouseClient(HttpClient httpClient) @@ -53,6 +55,7 @@ public CompaniesHouseClient(HttpClient httpClient) _companiesHouseRegisteredOfficeAddressClient = new CompaniesHouseRegisteredOfficeAddressClient(_httpClient, new RegisteredOfficeAddressUriBuilder()); _companiesHouseOfficerByAppointmentClient = new CompaniesHouseOfficerByByAppointmentClient(_httpClient, new OfficersAppointmentUriBuilder()); _companiesHouseRegistersClient = new CompaniesHouseRegistersClient(_httpClient, new CompanyRegistersUriBuilder()); + _companiesHouseDisqualifiedOfficerDetailsClient = new CompaniesHouseDisqualifiedOfficerDetailsClient(_httpClient, new DisqualifiedOfficerUriBuilder()); } public CompaniesHouseClient(ICompaniesHouseSettings settings) @@ -164,6 +167,16 @@ public Task> GetCompanyRegistersAsync(s return _companiesHouseRegistersClient.GetCompanyRegistersAsync(companyNumber, cancellationToken); } + public Task> GetNaturalDisqualificationAsync(string officerId, CancellationToken cancellationToken = default) + { + return _companiesHouseDisqualifiedOfficerDetailsClient.GetNaturalDisqualificationAsync(officerId, cancellationToken); + } + + public Task> GetCorporateDisqualificationAsync(string officerId, CancellationToken cancellationToken = default) + { + return _companiesHouseDisqualifiedOfficerDetailsClient.GetCorporateDisqualificationAsync(officerId, cancellationToken); + } + public void Dispose() => _httpClient.Dispose(); } } diff --git a/src/CompaniesHouse/CompaniesHouseDisqualifiedOfficerDetailsClient.cs b/src/CompaniesHouse/CompaniesHouseDisqualifiedOfficerDetailsClient.cs new file mode 100644 index 0000000..fc99be3 --- /dev/null +++ b/src/CompaniesHouse/CompaniesHouseDisqualifiedOfficerDetailsClient.cs @@ -0,0 +1,38 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.DisqualifiedOfficers; +using CompaniesHouse.UriBuilders; + +namespace CompaniesHouse +{ + using CompaniesHouse.Extensions; + + public class CompaniesHouseDisqualifiedOfficerDetailsClient : ICompaniesHouseDisqualifiedOfficerDetailsClient + { + private readonly HttpClient _httpClient; + private readonly IDisqualifiedOfficerUriBuilder _disqualifiedOfficerUriBuilder; + + public CompaniesHouseDisqualifiedOfficerDetailsClient(HttpClient httpClient, IDisqualifiedOfficerUriBuilder disqualifiedOfficerUriBuilder) + { + _httpClient = httpClient; + _disqualifiedOfficerUriBuilder = disqualifiedOfficerUriBuilder; + } + + public async Task> GetNaturalDisqualificationAsync(string officerId, CancellationToken cancellationToken = default) + { + var requestUri = _disqualifiedOfficerUriBuilder.BuildNatural(officerId); + var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); + + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); + } + + public async Task> GetCorporateDisqualificationAsync(string officerId, CancellationToken cancellationToken = default) + { + var requestUri = _disqualifiedOfficerUriBuilder.BuildCorporate(officerId); + var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); + + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/CompaniesHouse/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index 251b5ae..8df1bd8 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -16,7 +16,8 @@ public interface ICompaniesHouseClient : ICompaniesHousePersonsWithSignificantControlClient, ICompaniesHouseChargesClient, ICompaniesHouseRegisteredOfficeAddressClient, - ICompaniesHouseRegistersClient + ICompaniesHouseRegistersClient, + ICompaniesHouseDisqualifiedOfficerDetailsClient { } diff --git a/src/CompaniesHouse/ICompaniesHouseDisqualifiedOfficerDetailsClient.cs b/src/CompaniesHouse/ICompaniesHouseDisqualifiedOfficerDetailsClient.cs new file mode 100644 index 0000000..19cc48b --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHouseDisqualifiedOfficerDetailsClient.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.DisqualifiedOfficers; + +namespace CompaniesHouse +{ + public interface ICompaniesHouseDisqualifiedOfficerDetailsClient + { + Task> GetNaturalDisqualificationAsync(string officerId, CancellationToken cancellationToken = default); + + Task> GetCorporateDisqualificationAsync(string officerId, CancellationToken cancellationToken = default); + } +} diff --git a/src/CompaniesHouse/Response/DisqualifiedOfficers/CorporateDisqualification.cs b/src/CompaniesHouse/Response/DisqualifiedOfficers/CorporateDisqualification.cs new file mode 100644 index 0000000..87be294 --- /dev/null +++ b/src/CompaniesHouse/Response/DisqualifiedOfficers/CorporateDisqualification.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.DisqualifiedOfficers +{ + public class CorporateDisqualification + { + [JsonPropertyName("company_number")] + public string? CompanyNumber { get; set; } + + [JsonPropertyName("country_of_registration")] + public string? CountryOfRegistration { get; set; } + + [JsonPropertyName("etag")] + public string Etag { get; set; } = string.Empty; + + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("links")] + public DisqualificationLinks Links { get; set; } = new(); + + [JsonPropertyName("disqualifications")] + public DisqualificationCase[] Disqualifications { get; set; } = []; + + [JsonPropertyName("permissions_to_act")] + public DisqualificationPermissionToAct[]? PermissionsToAct { get; set; } + + [JsonPropertyName("person_number")] + public string? PersonNumber { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationCase.cs b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationCase.cs new file mode 100644 index 0000000..be7cedc --- /dev/null +++ b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationCase.cs @@ -0,0 +1,42 @@ +using System; +using System.Text.Json.Serialization; +using CompaniesHouse.Response; + +namespace CompaniesHouse.Response.DisqualifiedOfficers +{ + public class DisqualificationCase + { + [JsonPropertyName("case_identifier")] + public string? CaseIdentifier { get; set; } + + [JsonPropertyName("address")] + public Address Address { get; set; } = new(); + + [JsonPropertyName("company_names")] + public string[]? CompanyNames { get; set; } + + [JsonPropertyName("court_name")] + public string? CourtName { get; set; } + + [JsonPropertyName("disqualification_type")] + public string DisqualificationType { get; set; } = string.Empty; + + [JsonPropertyName("disqualified_from")] + public DateTime DisqualifiedFrom { get; set; } + + [JsonPropertyName("disqualified_until")] + public DateTime DisqualifiedUntil { get; set; } + + [JsonPropertyName("heard_on")] + public DateTime? HeardOn { get; set; } + + [JsonPropertyName("undertaken_on")] + public DateTime? UndertakenOn { get; set; } + + [JsonPropertyName("last_variation")] + public DisqualificationLastVariation[]? LastVariation { get; set; } + + [JsonPropertyName("reason")] + public DisqualificationReason Reason { get; set; } = new(); + } +} diff --git a/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLastVariation.cs b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLastVariation.cs new file mode 100644 index 0000000..ca6c21b --- /dev/null +++ b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLastVariation.cs @@ -0,0 +1,17 @@ +using System; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.DisqualifiedOfficers +{ + public class DisqualificationLastVariation + { + [JsonPropertyName("varied_on")] + public DateTime? VariedOn { get; set; } + + [JsonPropertyName("case_identifier")] + public string? CaseIdentifier { get; set; } + + [JsonPropertyName("court_name")] + public string? CourtName { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLinks.cs b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLinks.cs new file mode 100644 index 0000000..32b56d0 --- /dev/null +++ b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.DisqualifiedOfficers +{ + public class DisqualificationLinks + { + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationPermissionToAct.cs b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationPermissionToAct.cs new file mode 100644 index 0000000..ef2c10f --- /dev/null +++ b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationPermissionToAct.cs @@ -0,0 +1,20 @@ +using System; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.DisqualifiedOfficers +{ + public class DisqualificationPermissionToAct + { + [JsonPropertyName("company_names")] + public string[]? CompanyNames { get; set; } + + [JsonPropertyName("court_name")] + public string? CourtName { get; set; } + + [JsonPropertyName("expires_on")] + public DateTime ExpiresOn { get; set; } + + [JsonPropertyName("granted_on")] + public DateTime GrantedOn { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationReason.cs b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationReason.cs new file mode 100644 index 0000000..9385caa --- /dev/null +++ b/src/CompaniesHouse/Response/DisqualifiedOfficers/DisqualificationReason.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.DisqualifiedOfficers +{ + public class DisqualificationReason + { + [JsonPropertyName("description_identifier")] + public string DescriptionIdentifier { get; set; } = string.Empty; + + [JsonPropertyName("act")] + public string Act { get; set; } = string.Empty; + + [JsonPropertyName("article")] + public string? Article { get; set; } + + [JsonPropertyName("section")] + public string? Section { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/DisqualifiedOfficers/NaturalDisqualification.cs b/src/CompaniesHouse/Response/DisqualifiedOfficers/NaturalDisqualification.cs new file mode 100644 index 0000000..88f3aec --- /dev/null +++ b/src/CompaniesHouse/Response/DisqualifiedOfficers/NaturalDisqualification.cs @@ -0,0 +1,47 @@ +using System; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.DisqualifiedOfficers +{ + public class NaturalDisqualification + { + [JsonPropertyName("date_of_birth")] + public DateTime? DateOfBirth { get; set; } + + [JsonPropertyName("etag")] + public string Etag { get; set; } = string.Empty; + + [JsonPropertyName("forename")] + public string? Forename { get; set; } + + [JsonPropertyName("honours")] + public string? Honours { get; set; } + + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("nationality")] + public string? Nationality { get; set; } + + [JsonPropertyName("other_forenames")] + public string? OtherForenames { get; set; } + + [JsonPropertyName("surname")] + public string Surname { get; set; } = string.Empty; + + [JsonPropertyName("title")] + public string? Title { get; set; } + + [JsonPropertyName("links")] + public DisqualificationLinks Links { get; set; } = new(); + + [JsonPropertyName("disqualifications")] + public DisqualificationCase[] Disqualifications { get; set; } = []; + + [JsonPropertyName("permissions_to_act")] + public DisqualificationPermissionToAct[]? PermissionsToAct { get; set; } + + [JsonPropertyName("person_number")] + public string? PersonNumber { get; set; } + } +} diff --git a/src/CompaniesHouse/UriBuilders/DisqualifiedOfficerUriBuilder.cs b/src/CompaniesHouse/UriBuilders/DisqualifiedOfficerUriBuilder.cs new file mode 100644 index 0000000..3ecae95 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/DisqualifiedOfficerUriBuilder.cs @@ -0,0 +1,19 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public class DisqualifiedOfficerUriBuilder : IDisqualifiedOfficerUriBuilder + { + public Uri BuildNatural(string officerId) + { + var path = $"disqualified-officers/natural/{Uri.EscapeDataString(officerId)}"; + return new Uri(path, UriKind.Relative); + } + + public Uri BuildCorporate(string officerId) + { + var path = $"disqualified-officers/corporate/{Uri.EscapeDataString(officerId)}"; + return new Uri(path, UriKind.Relative); + } + } +} diff --git a/src/CompaniesHouse/UriBuilders/IDisqualifiedOfficerUriBuilder.cs b/src/CompaniesHouse/UriBuilders/IDisqualifiedOfficerUriBuilder.cs new file mode 100644 index 0000000..7b72fde --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/IDisqualifiedOfficerUriBuilder.cs @@ -0,0 +1,11 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public interface IDisqualifiedOfficerUriBuilder + { + Uri BuildNatural(string officerId); + + Uri BuildCorporate(string officerId); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DisqualifiedOfficerDetailsTests/DisqualifiedOfficerDetailsTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DisqualifiedOfficerDetailsTests/DisqualifiedOfficerDetailsTestsValid.cs new file mode 100644 index 0000000..bd0e9a4 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DisqualifiedOfficerDetailsTests/DisqualifiedOfficerDetailsTestsValid.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using CompaniesHouse.Request; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.DisqualifiedOfficerDetailsTests +{ + public class DisqualifiedOfficerDetailsTestsValid + { + private readonly CompaniesHouseClient _client; + + public DisqualifiedOfficerDetailsTestsValid() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + + [IntegrationFact] + public async Task ThenNaturalDisqualificationDetailsAreReturnedForASearchResult() + { + var search = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "kevin", ItemsPerPage = 20 }); + var officerId = ExtractOfficerId(search.Data.DisqualifiedOfficers ?? [], "/disqualified-officers/natural/"); + + officerId.ShouldNotBeNull(); + var result = await _client.GetNaturalDisqualificationAsync(officerId!); + + result.Data.Kind.ShouldBe("natural-disqualification"); + result.Data.Surname.ShouldNotBeNullOrWhiteSpace(); + result.Data.Disqualifications.ShouldNotBeEmpty(); + result.Data.Disqualifications[0].Reason.Act.ShouldNotBeNullOrWhiteSpace(); + } + + [IntegrationFact] + public async Task ThenCorporateDisqualificationDetailsAreReturnedForASearchResult() + { + var search = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "limited", ItemsPerPage = 50 }); + var officerId = ExtractOfficerId(search.Data.DisqualifiedOfficers ?? [], "/disqualified-officers/corporate/"); + + officerId.ShouldNotBeNull(); + var result = await _client.GetCorporateDisqualificationAsync(officerId!); + + result.Data.Kind.ShouldBe("corporate-disqualification"); + result.Data.Name.ShouldNotBeNullOrWhiteSpace(); + result.Data.Disqualifications.ShouldNotBeEmpty(); + result.Data.Disqualifications[0].Reason.DescriptionIdentifier.ShouldNotBeNullOrWhiteSpace(); + } + + private static string? ExtractOfficerId(Response.Search.DisqualifiedOfficersSearch.DisqualifiedOfficer[] officers, string expectedPrefix) + { + foreach (var officer in officers) + { + var self = officer.Links?.Self; + if (string.IsNullOrWhiteSpace(self) || !self.StartsWith(expectedPrefix, StringComparison.Ordinal)) + { + continue; + } + + return self.Substring(expectedPrefix.Length); + } + + return null; + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/DisqualifiedOfficerDetailsScenarios.cs b/tests/CompaniesHouse.ScenarioTests/DisqualifiedOfficerDetailsScenarios.cs new file mode 100644 index 0000000..2f7dd7d --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/DisqualifiedOfficerDetailsScenarios.cs @@ -0,0 +1,83 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response.DisqualifiedOfficers; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class DisqualifiedOfficerDetailsScenarios + { + [Fact] + public void NaturalDisqualification_DeserializesObservedFields() + { + var value = JsonSerializer.Deserialize(NaturalJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Kind.ShouldBe("natural-disqualification"); + value.Surname.ShouldBe("HENRY (AKA KEVIN GREGORY)"); + value.Disqualifications.Length.ShouldBe(1); + value.Disqualifications[0].DisqualifiedFrom.ShouldBe(new DateTime(2019, 07, 18)); + value.Disqualifications[0].Reason.DescriptionIdentifier.ShouldBe("investigation-of-company"); + } + + [Fact] + public void CorporateDisqualification_DeserializesObservedFields() + { + var value = JsonSerializer.Deserialize(CorporateJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Kind.ShouldBe("corporate-disqualification"); + value.Name.ShouldBe("LIMITED LIABILITY COMPANY BANK TOCHKA"); + value.Disqualifications.Length.ShouldBe(1); + value.Disqualifications[0].DisqualifiedUntil.ShouldBe(new DateTime(9999, 12, 31)); + value.Disqualifications[0].Reason.Act.ShouldBe("sanctions-anti-money-laundering-act-2018"); + } + + private const string NaturalJson = """ + { + "date_of_birth":"1968-06-18", + "person_number":"260506620001", + "etag":"718b494f50cef7c55484c965a77c6c660dab3925", + "kind":"natural-disqualification", + "forename":"Charles", + "surname":"HENRY (AKA KEVIN GREGORY)", + "title":"Mr", + "links":{"self":"/disqualified-officers/natural/iJZbzhXjhanBiPC9LRVC-FfaRqg"}, + "disqualifications":[ + { + "case_identifier":"CR-2018-002193", + "address":{"address_line_1":"Parkway","country":"United Kingdom","locality":"Romford","postal_code":"RM2 5NT","premises":"19","region":"Essex"}, + "company_names":["LEGAL ACTION ALSO KNOWN AS CHARLES HENRY","CHARLES HENRY AND CO"], + "court_name":"Business And Property Courts London", + "disqualification_type":"court-order", + "disqualified_from":"2019-07-18", + "disqualified_until":"2029-07-17", + "heard_on":"2019-06-27", + "reason":{"act":"company-directors-disqualification-act-1986","section":"8","description_identifier":"investigation-of-company"} + } + ] + } + """; + + private const string CorporateJson = """ + { + "person_number":"345902060001", + "etag":"8cf5a48f40f6a7b60adc6d6d24306f09ad0d1bec", + "kind":"corporate-disqualification", + "name":"LIMITED LIABILITY COMPANY BANK TOCHKA", + "links":{"self":"/disqualified-officers/corporate/XzsV1VeAiawcC6ntn1BRavuZdDA"}, + "disqualifications":[ + { + "case_identifier":"RUS3405", + "address":{"address_line_1":"3rd Krutitsky","country":"Russia","postal_code":"109044","premises":"7n Pomeshch 11","region":"Moscow"}, + "disqualification_type":"sanction", + "disqualified_from":"2026-02-24", + "disqualified_until":"9999-12-31", + "reason":{"act":"sanctions-anti-money-laundering-act-2018","section":"3A","description_identifier":"disqualification-under-sanctions-regulation"} + } + ] + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseDisqualifiedOfficerDetailsClientTests/CompaniesHouseDisqualifiedOfficerDetailsClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDisqualifiedOfficerDetailsClientTests/CompaniesHouseDisqualifiedOfficerDetailsClientTests.cs new file mode 100644 index 0000000..a54da94 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDisqualifiedOfficerDetailsClientTests/CompaniesHouseDisqualifiedOfficerDetailsClientTests.cs @@ -0,0 +1,127 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.UriBuilders; +using Moq; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseDisqualifiedOfficerDetailsClientTests +{ + public class CompaniesHouseDisqualifiedOfficerDetailsClientTests + { + [Fact] + public async Task GivenCapturedNaturalPayload_WhenGettingNaturalDisqualification_ThenObservedFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/disqualified-officers/natural/1"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, NaturalJson); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.BuildNatural(It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildCorporate(It.IsAny())).Returns(uri); + + var client = new CompaniesHouseDisqualifiedOfficerDetailsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetNaturalDisqualificationAsync("1"); + + result.Data.Kind.ShouldBe("natural-disqualification"); + result.Data.Surname.ShouldBe("HENRY (AKA KEVIN GREGORY)"); + result.Data.Links.Self.ShouldBe("/disqualified-officers/natural/iJZbzhXjhanBiPC9LRVC-FfaRqg"); + result.Data.Disqualifications.Length.ShouldBe(1); + result.Data.Disqualifications[0].DisqualificationType.ShouldBe("court-order"); + result.Data.Disqualifications[0].Reason.DescriptionIdentifier.ShouldBe("investigation-of-company"); + } + + [Fact] + public async Task GivenCapturedCorporatePayload_WhenGettingCorporateDisqualification_ThenObservedFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/disqualified-officers/corporate/1"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, CorporateJson); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.BuildCorporate(It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildNatural(It.IsAny())).Returns(uri); + + var client = new CompaniesHouseDisqualifiedOfficerDetailsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetCorporateDisqualificationAsync("1"); + + result.Data.Kind.ShouldBe("corporate-disqualification"); + result.Data.Name.ShouldBe("LIMITED LIABILITY COMPANY BANK TOCHKA"); + result.Data.Links.Self.ShouldBe("/disqualified-officers/corporate/XzsV1VeAiawcC6ntn1BRavuZdDA"); + result.Data.Disqualifications.Length.ShouldBe(1); + result.Data.Disqualifications[0].DisqualificationType.ShouldBe("sanction"); + result.Data.Disqualifications[0].Reason.Act.ShouldBe("sanctions-anti-money-laundering-act-2018"); + } + + private const string NaturalJson = """ + { + "date_of_birth": "1968-06-18", + "person_number": "260506620001", + "etag": "718b494f50cef7c55484c965a77c6c660dab3925", + "kind": "natural-disqualification", + "forename": "Charles", + "surname": "HENRY (AKA KEVIN GREGORY)", + "title": "Mr", + "links": { + "self": "/disqualified-officers/natural/iJZbzhXjhanBiPC9LRVC-FfaRqg" + }, + "disqualifications": [ + { + "case_identifier": "CR-2018-002193", + "address": { + "address_line_1": "Parkway", + "country": "United Kingdom", + "locality": "Romford", + "postal_code": "RM2 5NT", + "premises": "19", + "region": "Essex" + }, + "company_names": [ + "LEGAL ACTION ALSO KNOWN AS CHARLES HENRY", + "CHARLES HENRY AND CO" + ], + "court_name": "Business And Property Courts London", + "disqualification_type": "court-order", + "disqualified_from": "2019-07-18", + "disqualified_until": "2029-07-17", + "heard_on": "2019-06-27", + "reason": { + "act": "company-directors-disqualification-act-1986", + "section": "8", + "description_identifier": "investigation-of-company" + } + } + ] + } + """; + + private const string CorporateJson = """ + { + "person_number": "345902060001", + "etag": "8cf5a48f40f6a7b60adc6d6d24306f09ad0d1bec", + "kind": "corporate-disqualification", + "name": "LIMITED LIABILITY COMPANY BANK TOCHKA", + "links": { + "self": "/disqualified-officers/corporate/XzsV1VeAiawcC6ntn1BRavuZdDA" + }, + "disqualifications": [ + { + "case_identifier": "RUS3405", + "address": { + "address_line_1": "3rd Krutitsky", + "country": "Russia", + "postal_code": "109044", + "premises": "7n Pomeshch 11", + "region": "Moscow" + }, + "disqualification_type": "sanction", + "disqualified_from": "2026-02-24", + "disqualified_until": "9999-12-31", + "reason": { + "act": "sanctions-anti-money-laundering-act-2018", + "section": "3A", + "description_identifier": "disqualification-under-sanctions-regulation" + } + } + ] + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/DisqualifiedOfficerUriBuilderTests/DisqualifiedOfficerUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/DisqualifiedOfficerUriBuilderTests/DisqualifiedOfficerUriBuilderTests.cs new file mode 100644 index 0000000..3e3ff33 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/DisqualifiedOfficerUriBuilderTests/DisqualifiedOfficerUriBuilderTests.cs @@ -0,0 +1,26 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.DisqualifiedOfficerUriBuilderTests +{ + public class DisqualifiedOfficerUriBuilderTests + { + [Fact] + public void BuildNatural_EncodesOfficerId() + { + var uri = new DisqualifiedOfficerUriBuilder().BuildNatural("abc/123"); + + uri.ShouldBe(new Uri("disqualified-officers/natural/abc%2F123", UriKind.Relative)); + } + + [Fact] + public void BuildCorporate_EncodesOfficerId() + { + var uri = new DisqualifiedOfficerUriBuilder().BuildCorporate("abc/123"); + + uri.ShouldBe(new Uri("disqualified-officers/corporate/abc%2F123", UriKind.Relative)); + } + } +} From 68e0d6ba54fef50db6281cdb58ddeed8d56e39e1 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 23:43:39 +0100 Subject: [PATCH 35/38] Add PSC detail, statement, and super-secure endpoints --- .plans/completed/09j-psc-detail-types.md | 47 +++++ .../09-endpoint-catalogue-remaining.md | 2 +- src/CompaniesHouse/CompaniesHouseClient.cs | 52 +++++ ...sonsWithSignificantControlDetailsClient.cs | 78 +++++++ src/CompaniesHouse/ICompaniesHouseClient.cs | 3 +- ...sonsWithSignificantControlDetailsClient.cs | 29 +++ .../Officers/IdentityVerificationDetails.cs | 8 + .../PersonWithSignificantControlStatement.cs | 32 +++ ...sonWithSignificantControlStatementLinks.cs | 13 ++ ...PersonsWithSignificantControlStatements.cs | 28 +++ ...nsWithSignificantControlStatementsLinks.cs | 13 ++ ...SuperSecurePersonWithSignificantControl.cs | 26 +++ ...SecurePersonWithSignificantControlLinks.cs | 10 + ...WithSignificantControlDetailsUriBuilder.cs | 27 +++ ...WithSignificantControlDetailsUriBuilder.cs | 77 +++++++ ...WithSignificantControlDetailsTestsValid.cs | 194 ++++++++++++++++++ .../PscDetailsScenarios.cs | 100 +++++++++ ...ithSignificantControlDetailsClientTests.cs | 131 ++++++++++++ ...ignificantControlDetailsUriBuilderTests.cs | 82 ++++++++ 19 files changed, 950 insertions(+), 2 deletions(-) create mode 100644 .plans/completed/09j-psc-detail-types.md create mode 100644 src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlDetailsClient.cs create mode 100644 src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlDetailsClient.cs create mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatement.cs create mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatementLinks.cs create mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatements.cs create mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatementsLinks.cs create mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControl.cs create mode 100644 src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControlLinks.cs create mode 100644 src/CompaniesHouse/UriBuilders/IPersonsWithSignificantControlDetailsUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/PersonsWithSignificantControlDetailsUriBuilder.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlDetailsTestsValid.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/PscDetailsScenarios.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlDetailsClientTests/CompaniesHousePersonsWithSignificantControlDetailsClientTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlDetailsUriBuilderTests/PersonsWithSignificantControlDetailsUriBuilderTests.cs diff --git a/.plans/completed/09j-psc-detail-types.md b/.plans/completed/09j-psc-detail-types.md new file mode 100644 index 0000000..3341650 --- /dev/null +++ b/.plans/completed/09j-psc-detail-types.md @@ -0,0 +1,47 @@ +# 09j — Endpoint: PSC detail types + +**Status:** completed +**Depends on:** `09d-persons-with-significant-control-list`, `01-core`, `03-value-types` +**Blocks:** nothing + +## Goal + +Build the remaining PSC detail endpoints: individual, corporate entity, legal person, statements and super-secure PSCs. + +Docs: +- +- +- +- +- +- + +## Scope + +- Add the missing PSC detail/statement clients, builders and response models. +- Reuse the generated PSC kind/nature-of-control value types introduced in `09d`. + +## Tasks + +- [x] Confirm live docs and identify stable test IDs. +- [x] Build the client surface and response models for each PSC detail family. +- [x] Add unit, scenario and integration coverage. + +## Open questions + +- Which live companies expose stable statement and super-secure test data? + - Resolved pragmatically: stable individual/corporate/statement IDs are covered in integration tests; legal/super-secure probes run against sampled live companies and validate when present. + +## Acceptance criteria + +- PSC detail, statement and super-secure endpoints deserialize from the live API. + +## References + +- No existing master implementation - build from the live API docs only. +- +- +- +- +- +- diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md index 3310754..6d35c54 100644 --- a/.plans/outstanding/09-endpoint-catalogue-remaining.md +++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md @@ -16,8 +16,8 @@ Completed in this change: - `..\completed\09g-documents.md` - `..\completed\09h-registers.md` - `..\completed\09i-disqualified-officers-detail.md` +- `..\completed\09j-psc-detail-types.md` Still outstanding: -- `09j-psc-detail-types.md` - `09k-exemptions.md` - `09l-uk-establishments.md` diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index e3fd0e8..334bcd9 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -39,6 +39,7 @@ public class CompaniesHouseClient : ICompaniesHouseClient, IDisposable private readonly ICompaniesHouseOfficerByAppointmentClient _companiesHouseOfficerByAppointmentClient; private readonly ICompaniesHouseRegistersClient _companiesHouseRegistersClient; private readonly ICompaniesHouseDisqualifiedOfficerDetailsClient _companiesHouseDisqualifiedOfficerDetailsClient; + private readonly ICompaniesHousePersonsWithSignificantControlDetailsClient _companiesHousePersonsWithSignificantControlDetailsClient; private readonly HttpClient _httpClient; public CompaniesHouseClient(HttpClient httpClient) @@ -56,6 +57,7 @@ public CompaniesHouseClient(HttpClient httpClient) _companiesHouseOfficerByAppointmentClient = new CompaniesHouseOfficerByByAppointmentClient(_httpClient, new OfficersAppointmentUriBuilder()); _companiesHouseRegistersClient = new CompaniesHouseRegistersClient(_httpClient, new CompanyRegistersUriBuilder()); _companiesHouseDisqualifiedOfficerDetailsClient = new CompaniesHouseDisqualifiedOfficerDetailsClient(_httpClient, new DisqualifiedOfficerUriBuilder()); + _companiesHousePersonsWithSignificantControlDetailsClient = new CompaniesHousePersonsWithSignificantControlDetailsClient(_httpClient, new PersonsWithSignificantControlDetailsUriBuilder()); } public CompaniesHouseClient(ICompaniesHouseSettings settings) @@ -142,6 +144,56 @@ public Task> GetOfficersAsync( return _companiesHousePersonsWithSignificantControlClient.GetPersonsWithSignificantControlAsync(companyNumber, startIndex, pageSize, cancellationToken); } + public Task> GetIndividualPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetIndividualPersonWithSignificantControlAsync(companyNumber, notificationId, cancellationToken); + } + + public Task> GetIndividualBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetIndividualBeneficialOwnerAsync(companyNumber, notificationId, cancellationToken); + } + + public Task> GetCorporateEntityPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetCorporateEntityPersonWithSignificantControlAsync(companyNumber, notificationId, cancellationToken); + } + + public Task> GetCorporateEntityBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetCorporateEntityBeneficialOwnerAsync(companyNumber, notificationId, cancellationToken); + } + + public Task> GetLegalPersonPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetLegalPersonPersonWithSignificantControlAsync(companyNumber, notificationId, cancellationToken); + } + + public Task> GetLegalPersonBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetLegalPersonBeneficialOwnerAsync(companyNumber, notificationId, cancellationToken); + } + + public Task> GetPersonsWithSignificantControlStatementsAsync(string companyNumber, int startIndex = 0, int pageSize = 25, bool? registerView = null, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetPersonsWithSignificantControlStatementsAsync(companyNumber, startIndex, pageSize, registerView, cancellationToken); + } + + public Task> GetPersonsWithSignificantControlStatementAsync(string companyNumber, string statementId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetPersonsWithSignificantControlStatementAsync(companyNumber, statementId, cancellationToken); + } + + public Task> GetSuperSecurePersonWithSignificantControlAsync(string companyNumber, string superSecureId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetSuperSecurePersonWithSignificantControlAsync(companyNumber, superSecureId, cancellationToken); + } + + public Task> GetSuperSecureBeneficialOwnerAsync(string companyNumber, string superSecureId, CancellationToken cancellationToken = default) + { + return _companiesHousePersonsWithSignificantControlDetailsClient.GetSuperSecureBeneficialOwnerAsync(companyNumber, superSecureId, cancellationToken); + } + public Task> GetChargesListAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default) { return _companiesHouseChargesClient.GetChargesListAsync(companyNumber, startIndex, pageSize, cancellationToken); diff --git a/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlDetailsClient.cs b/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlDetailsClient.cs new file mode 100644 index 0000000..b08e0aa --- /dev/null +++ b/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlDetailsClient.cs @@ -0,0 +1,78 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.PersonsWithSignificantControl; +using CompaniesHouse.UriBuilders; + +namespace CompaniesHouse +{ + using CompaniesHouse.Extensions; + + public class CompaniesHousePersonsWithSignificantControlDetailsClient : ICompaniesHousePersonsWithSignificantControlDetailsClient + { + private readonly HttpClient _httpClient; + private readonly IPersonsWithSignificantControlDetailsUriBuilder _uriBuilder; + + public CompaniesHousePersonsWithSignificantControlDetailsClient(HttpClient httpClient, IPersonsWithSignificantControlDetailsUriBuilder uriBuilder) + { + _httpClient = httpClient; + _uriBuilder = uriBuilder; + } + + public async Task> GetIndividualPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildIndividual(companyNumber, notificationId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetIndividualBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildIndividualBeneficialOwner(companyNumber, notificationId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetCorporateEntityPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildCorporateEntity(companyNumber, notificationId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetCorporateEntityBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildCorporateEntityBeneficialOwner(companyNumber, notificationId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetLegalPersonPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildLegalPerson(companyNumber, notificationId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetLegalPersonBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildLegalPersonBeneficialOwner(companyNumber, notificationId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetPersonsWithSignificantControlStatementsAsync(string companyNumber, int startIndex = 0, int pageSize = 25, bool? registerView = null, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildStatementsList(companyNumber, startIndex, pageSize, registerView), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetPersonsWithSignificantControlStatementAsync(string companyNumber, string statementId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildStatement(companyNumber, statementId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetSuperSecurePersonWithSignificantControlAsync(string companyNumber, string superSecureId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildSuperSecure(companyNumber, superSecureId), cancellationToken).ConfigureAwait(false); + } + + public async Task> GetSuperSecureBeneficialOwnerAsync(string companyNumber, string superSecureId, CancellationToken cancellationToken = default) + { + return await GetAsync(_uriBuilder.BuildSuperSecureBeneficialOwner(companyNumber, superSecureId), cancellationToken).ConfigureAwait(false); + } + + private async Task> GetAsync(Uri requestUri, CancellationToken cancellationToken) + { + var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/CompaniesHouse/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index 8df1bd8..8264f85 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -17,7 +17,8 @@ public interface ICompaniesHouseClient : ICompaniesHouseChargesClient, ICompaniesHouseRegisteredOfficeAddressClient, ICompaniesHouseRegistersClient, - ICompaniesHouseDisqualifiedOfficerDetailsClient + ICompaniesHouseDisqualifiedOfficerDetailsClient, + ICompaniesHousePersonsWithSignificantControlDetailsClient { } diff --git a/src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlDetailsClient.cs b/src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlDetailsClient.cs new file mode 100644 index 0000000..8c5cd47 --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlDetailsClient.cs @@ -0,0 +1,29 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.PersonsWithSignificantControl; + +namespace CompaniesHouse +{ + public interface ICompaniesHousePersonsWithSignificantControlDetailsClient + { + Task> GetIndividualPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default); + + Task> GetIndividualBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default); + + Task> GetCorporateEntityPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default); + + Task> GetCorporateEntityBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default); + + Task> GetLegalPersonPersonWithSignificantControlAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default); + + Task> GetLegalPersonBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default); + + Task> GetPersonsWithSignificantControlStatementsAsync(string companyNumber, int startIndex = 0, int pageSize = 25, bool? registerView = null, CancellationToken cancellationToken = default); + + Task> GetPersonsWithSignificantControlStatementAsync(string companyNumber, string statementId, CancellationToken cancellationToken = default); + + Task> GetSuperSecurePersonWithSignificantControlAsync(string companyNumber, string superSecureId, CancellationToken cancellationToken = default); + + Task> GetSuperSecureBeneficialOwnerAsync(string companyNumber, string superSecureId, CancellationToken cancellationToken = default); + } +} diff --git a/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs b/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs index 90176ad..1a45078 100644 --- a/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs +++ b/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs @@ -17,6 +17,14 @@ public class IdentityVerificationDetails [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? AppointmentVerificationStartOn { get; set; } + [JsonPropertyName("appointment_verification_statement_date")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? AppointmentVerificationStatementDate { get; set; } + + [JsonPropertyName("appointment_verification_statement_due_on")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? AppointmentVerificationStatementDueOn { get; set; } + [JsonPropertyName("authorised_corporate_service_provider_name")] public string? AuthorisedCorporateServiceProviderName { get; set; } diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatement.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatement.cs new file mode 100644 index 0000000..1152d1f --- /dev/null +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatement.cs @@ -0,0 +1,32 @@ +using System; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.PersonsWithSignificantControl +{ + public class PersonWithSignificantControlStatement + { + [JsonPropertyName("etag")] + public string ETag { get; set; } = string.Empty; + + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("notified_on")] + public DateTime NotifiedOn { get; set; } + + [JsonPropertyName("ceased_on")] + public DateTime? CeasedOn { get; set; } + + [JsonPropertyName("restrictions_notice_withdrawal_reason")] + public string? RestrictionsNoticeWithdrawalReason { get; set; } + + [JsonPropertyName("statement")] + public string Statement { get; set; } = string.Empty; + + [JsonPropertyName("linked_psc_name")] + public string? LinkedPscName { get; set; } + + [JsonPropertyName("links")] + public PersonWithSignificantControlStatementLinks Links { get; set; } = new(); + } +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatementLinks.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatementLinks.cs new file mode 100644 index 0000000..5d393d1 --- /dev/null +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlStatementLinks.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.PersonsWithSignificantControl +{ + public class PersonWithSignificantControlStatementLinks + { + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + + [JsonPropertyName("person_with_significant_control")] + public string? PersonWithSignificantControl { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatements.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatements.cs new file mode 100644 index 0000000..d211d1c --- /dev/null +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatements.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.PersonsWithSignificantControl +{ + public class PersonsWithSignificantControlStatements + { + [JsonPropertyName("items_per_page")] + public int ItemsPerPage { get; set; } + + [JsonPropertyName("items")] + public PersonWithSignificantControlStatement[] Items { get; set; } = []; + + [JsonPropertyName("start_index")] + public int StartIndex { get; set; } + + [JsonPropertyName("total_results")] + public int TotalResults { get; set; } + + [JsonPropertyName("active_count")] + public int ActiveCount { get; set; } + + [JsonPropertyName("ceased_count")] + public int CeasedCount { get; set; } + + [JsonPropertyName("links")] + public PersonsWithSignificantControlStatementsLinks Links { get; set; } = new(); + } +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatementsLinks.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatementsLinks.cs new file mode 100644 index 0000000..eb23cc8 --- /dev/null +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControlStatementsLinks.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.PersonsWithSignificantControl +{ + public class PersonsWithSignificantControlStatementsLinks + { + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + + [JsonPropertyName("persons_with_significant_control_statements_list")] + public string? PersonsWithSignificantControlStatementsList { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControl.cs new file mode 100644 index 0000000..cbce2ec --- /dev/null +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControl.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; +using CompaniesHouse.Response.Officers; + +namespace CompaniesHouse.Response.PersonsWithSignificantControl +{ + public class SuperSecurePersonWithSignificantControl + { + [JsonPropertyName("etag")] + public string ETag { get; set; } = string.Empty; + + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + [JsonPropertyName("identity_verification_details")] + public IdentityVerificationDetails? IdentityVerificationDetails { get; set; } + + [JsonPropertyName("ceased")] + public bool? Ceased { get; set; } + + [JsonPropertyName("links")] + public SuperSecurePersonWithSignificantControlLinks Links { get; set; } = new(); + } +} diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControlLinks.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControlLinks.cs new file mode 100644 index 0000000..7667dce --- /dev/null +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/SuperSecurePersonWithSignificantControlLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.PersonsWithSignificantControl +{ + public class SuperSecurePersonWithSignificantControlLinks + { + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/UriBuilders/IPersonsWithSignificantControlDetailsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/IPersonsWithSignificantControlDetailsUriBuilder.cs new file mode 100644 index 0000000..56ed63c --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/IPersonsWithSignificantControlDetailsUriBuilder.cs @@ -0,0 +1,27 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public interface IPersonsWithSignificantControlDetailsUriBuilder + { + Uri BuildIndividual(string companyNumber, string notificationId); + + Uri BuildIndividualBeneficialOwner(string companyNumber, string notificationId); + + Uri BuildCorporateEntity(string companyNumber, string notificationId); + + Uri BuildCorporateEntityBeneficialOwner(string companyNumber, string notificationId); + + Uri BuildLegalPerson(string companyNumber, string notificationId); + + Uri BuildLegalPersonBeneficialOwner(string companyNumber, string notificationId); + + Uri BuildStatementsList(string companyNumber, int startIndex, int pageSize, bool? registerView); + + Uri BuildStatement(string companyNumber, string statementId); + + Uri BuildSuperSecure(string companyNumber, string superSecureId); + + Uri BuildSuperSecureBeneficialOwner(string companyNumber, string superSecureId); + } +} diff --git a/src/CompaniesHouse/UriBuilders/PersonsWithSignificantControlDetailsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/PersonsWithSignificantControlDetailsUriBuilder.cs new file mode 100644 index 0000000..7181fcf --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/PersonsWithSignificantControlDetailsUriBuilder.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace CompaniesHouse.UriBuilders +{ + public class PersonsWithSignificantControlDetailsUriBuilder : IPersonsWithSignificantControlDetailsUriBuilder + { + public Uri BuildIndividual(string companyNumber, string notificationId) + { + return Build(companyNumber, $"persons-with-significant-control/individual/{Uri.EscapeDataString(notificationId)}"); + } + + public Uri BuildIndividualBeneficialOwner(string companyNumber, string notificationId) + { + return Build(companyNumber, $"persons-with-significant-control/individual-beneficial-owner/{Uri.EscapeDataString(notificationId)}"); + } + + public Uri BuildCorporateEntity(string companyNumber, string notificationId) + { + return Build(companyNumber, $"persons-with-significant-control/corporate-entity/{Uri.EscapeDataString(notificationId)}"); + } + + public Uri BuildCorporateEntityBeneficialOwner(string companyNumber, string notificationId) + { + return Build(companyNumber, $"persons-with-significant-control/corporate-entity-beneficial-owner/{Uri.EscapeDataString(notificationId)}"); + } + + public Uri BuildLegalPerson(string companyNumber, string notificationId) + { + return Build(companyNumber, $"persons-with-significant-control/legal-person/{Uri.EscapeDataString(notificationId)}"); + } + + public Uri BuildLegalPersonBeneficialOwner(string companyNumber, string notificationId) + { + return Build(companyNumber, $"persons-with-significant-control/legal-person-beneficial-owner/{Uri.EscapeDataString(notificationId)}"); + } + + public Uri BuildStatementsList(string companyNumber, int startIndex, int pageSize, bool? registerView) + { + var queryParts = new List + { + "items_per_page=" + pageSize.ToString(CultureInfo.InvariantCulture), + "start_index=" + startIndex.ToString(CultureInfo.InvariantCulture) + }; + + if (registerView.HasValue) + { + queryParts.Add("register_view=" + registerView.Value.ToString().ToLowerInvariant()); + } + + var path = $"company/{Uri.EscapeDataString(companyNumber)}/persons-with-significant-control-statements?{string.Join("&", queryParts)}"; + return new Uri(path, UriKind.Relative); + } + + public Uri BuildStatement(string companyNumber, string statementId) + { + return Build(companyNumber, $"persons-with-significant-control-statements/{Uri.EscapeDataString(statementId)}"); + } + + public Uri BuildSuperSecure(string companyNumber, string superSecureId) + { + return Build(companyNumber, $"persons-with-significant-control/super-secure/{Uri.EscapeDataString(superSecureId)}"); + } + + public Uri BuildSuperSecureBeneficialOwner(string companyNumber, string superSecureId) + { + return Build(companyNumber, $"persons-with-significant-control/super-secure-beneficial-owner/{Uri.EscapeDataString(superSecureId)}"); + } + + private static Uri Build(string companyNumber, string endpointPath) + { + var path = $"company/{Uri.EscapeDataString(companyNumber)}/{endpointPath}"; + return new Uri(path, UriKind.Relative); + } + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlDetailsTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlDetailsTestsValid.cs new file mode 100644 index 0000000..e224883 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlDetailsTestsValid.cs @@ -0,0 +1,194 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using CompaniesHouse.Response.PersonsWithSignificantControl; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.PersonsWithSignificantControlTests +{ + public class PersonsWithSignificantControlDetailsTestsValid + { + private readonly CompaniesHouseClient _client; + + public PersonsWithSignificantControlDetailsTestsValid() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + + [IntegrationFact] + public async Task ThenIndividualPscDetailsCanBeRetrievedFromAListItem() + { + if (!TryGetSuccess(await _client.GetPersonsWithSignificantControlAsync("11790215"), out var list)) + { + return; + } + + var item = list.Items?.FirstOrDefault(x => x.Kind.Value.StartsWith("individual-", StringComparison.Ordinal)); + item.ShouldNotBeNull(); + + var notificationId = ExtractTrailingSegment(item.Links?.Self); + notificationId.ShouldNotBeNullOrWhiteSpace(); + + if (!TryGetSuccess(await _client.GetIndividualPersonWithSignificantControlAsync("11790215", notificationId!), out var detail)) + { + return; + } + + detail.Kind.Value.ShouldStartWith("individual-"); + detail.Links?.Self.ShouldNotBeNullOrWhiteSpace(); + } + + [IntegrationFact] + public async Task ThenCorporateEntityPscDetailsCanBeRetrievedFromAListItem() + { + if (!TryGetSuccess(await _client.GetPersonsWithSignificantControlAsync("00617641"), out var list)) + { + return; + } + + var item = list.Items?.FirstOrDefault(x => x.Kind.Value.StartsWith("corporate-entity-", StringComparison.Ordinal)); + item.ShouldNotBeNull(); + + var notificationId = ExtractTrailingSegment(item.Links?.Self); + notificationId.ShouldNotBeNullOrWhiteSpace(); + + if (!TryGetSuccess(await _client.GetCorporateEntityPersonWithSignificantControlAsync("00617641", notificationId!), out var detail)) + { + return; + } + + detail.Kind.Value.ShouldStartWith("corporate-entity-"); + detail.Identification.ShouldNotBeNull(); + } + + [IntegrationFact] + public async Task ThenPscStatementListAndDetailCanBeRetrieved() + { + if (!TryGetSuccess(await _client.GetPersonsWithSignificantControlStatementsAsync("05124262"), out var list)) + { + return; + } + + list.Items.ShouldNotBeEmpty(); + + var statement = list.Items[0]; + var statementId = ExtractTrailingSegment(statement.Links.Self); + statementId.ShouldNotBeNullOrWhiteSpace(); + + if (!TryGetSuccess(await _client.GetPersonsWithSignificantControlStatementAsync("05124262", statementId!), out var detail)) + { + return; + } + + detail.Statement.ShouldNotBeNullOrWhiteSpace(); + detail.Kind.ShouldContain("statement"); + } + + [IntegrationFact] + public async Task ThenLegalPersonAndSuperSecureDetailsCanBeRetrievedWhenPresent() + { + var companies = new[] { "03977902", "11790215", "00617641", "05124262" }; + foreach (var company in companies) + { + var list = await _client.GetPersonsWithSignificantControlAsync(company); + if (list is not CompaniesHouseResponse.Success success) + { + if (list is CompaniesHouseResponse.RateLimited) + { + return; + } + + continue; + } + + var legal = success.Data.Items?.FirstOrDefault(x => x.Kind.Value.StartsWith("legal-person-", StringComparison.Ordinal)); + if (legal is not null) + { + var legalId = ExtractTrailingSegment(legal.Links?.Self); + legalId.ShouldNotBeNullOrWhiteSpace(); + + if (legal.Kind.Value.Contains("beneficial-owner", StringComparison.Ordinal)) + { + if (!TryGetSuccess(await _client.GetLegalPersonBeneficialOwnerAsync(company, legalId!), out var legalBo)) + { + return; + } + + legalBo.Kind.Value.ShouldStartWith("legal-person-"); + } + else + { + if (!TryGetSuccess(await _client.GetLegalPersonPersonWithSignificantControlAsync(company, legalId!), out var legalDetail)) + { + return; + } + + legalDetail.Kind.Value.ShouldStartWith("legal-person-"); + } + } + + var superSecure = success.Data.Items?.FirstOrDefault(x => x.Kind.Value.StartsWith("super-secure-", StringComparison.Ordinal)); + if (superSecure is not null) + { + var superSecureId = ExtractTrailingSegment(superSecure.Links?.Self); + superSecureId.ShouldNotBeNullOrWhiteSpace(); + + if (superSecure.Kind.Value.Contains("beneficial-owner", StringComparison.Ordinal)) + { + if (!TryGetSuccess(await _client.GetSuperSecureBeneficialOwnerAsync(company, superSecureId!), out var bo)) + { + return; + } + + bo.Kind.ShouldStartWith("super-secure-"); + } + else + { + if (!TryGetSuccess(await _client.GetSuperSecurePersonWithSignificantControlAsync(company, superSecureId!), out var psc)) + { + return; + } + + psc.Kind.ShouldStartWith("super-secure-"); + } + } + + if (legal is not null || superSecure is not null) + { + return; + } + } + + return; + } + + private static bool TryGetSuccess(CompaniesHouseResponse response, out T data) + { + switch (response) + { + case CompaniesHouseResponse.Success success: + data = success.Data; + return true; + case CompaniesHouseResponse.RateLimited: + data = default!; + return false; + default: + response.ShouldBeOfType.Success>(); + data = default!; + return false; + } + } + + private static string? ExtractTrailingSegment(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return null; + } + + var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + return parts.Length == 0 ? null : parts[^1]; + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/PscDetailsScenarios.cs b/tests/CompaniesHouse.ScenarioTests/PscDetailsScenarios.cs new file mode 100644 index 0000000..1605242 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/PscDetailsScenarios.cs @@ -0,0 +1,100 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response.PersonsWithSignificantControl; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class PscDetailsScenarios + { + [Fact] + public void IndividualPscDetail_DeserializesObservedFields() + { + var value = JsonSerializer.Deserialize(IndividualJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Kind.ShouldBe(new PersonWithSignificantControlKind("individual-person-with-significant-control")); + value.Name.ShouldBe("Chris Brown"); + value.DateOfBirth?.Year.ShouldBe(1979); + (value.NaturesOfControl ?? []).ShouldContain(new PersonWithSignificantControlNatureOfControl("ownership-of-shares-25-to-50-percent")); + } + + [Fact] + public void PscStatementList_DeserializesEnvelopeAndStatement() + { + var value = JsonSerializer.Deserialize(StatementListJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.TotalResults.ShouldBe(1); + value.Items.Length.ShouldBe(1); + value.Items[0].Statement.ShouldBe("psc-has-failed-to-confirm-changed-details"); + value.Items[0].NotifiedOn.ShouldBe(new DateTime(2016, 6, 30)); + } + + [Fact] + public void SuperSecurePsc_DeserializesStatementDates() + { + var value = JsonSerializer.Deserialize(SuperSecureJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Kind.ShouldBe("super-secure-person-with-significant-control"); + value.Description.ShouldBe("super-secure-person-with-significant-control"); + value.IdentityVerificationDetails.ShouldNotBeNull(); + value.IdentityVerificationDetails.AppointmentVerificationStatementDate.ShouldBe(new DateTime(2026, 7, 1)); + value.IdentityVerificationDetails.AppointmentVerificationStatementDueOn.ShouldBe(new DateTime(2026, 9, 1)); + } + + private const string IndividualJson = """ + { + "etag":"ef3d935f77e2f6b1dfacf1f3f7289a594f16f8e1", + "notified_on":"2019-01-16", + "kind":"individual-person-with-significant-control", + "country_of_residence":"United Kingdom", + "date_of_birth":{"month":7,"year":1979}, + "name":"Chris Brown", + "name_elements":{"forename":"Chris","surname":"Brown"}, + "links":{"self":"/company/11790215/persons-with-significant-control/individual/SGX6zLwNkq2YrjsYXPSVnmYi6SE"}, + "nationality":"British", + "address":{"address_line_1":"1 Street","locality":"London","postal_code":"W1A 1AA"}, + "natures_of_control":["ownership-of-shares-25-to-50-percent"] + } + """; + + private const string StatementListJson = """ + { + "items_per_page":25, + "items":[ + { + "etag":"95ca7497819e5fbc1144b6a3ef09f477228f3f5f", + "kind":"persons-with-significant-control-statement", + "notified_on":"2016-06-30", + "statement":"psc-has-failed-to-confirm-changed-details", + "links":{ + "self":"/company/05124262/persons-with-significant-control-statements/8xxEeFpu5Xmpf1ce1FmwM-sK8J8", + "person_with_significant_control":"/company/05124262/persons-with-significant-control/individual/KdK4nMdcYtuJV_Ax0s8_5JJmQdw" + } + } + ], + "start_index":0, + "total_results":1, + "active_count":1, + "ceased_count":0, + "links":{"self":"/company/05124262/persons-with-significant-control-statements"} + } + """; + + private const string SuperSecureJson = """ + { + "etag":"fa0f4f2a1b8186f26bc65820c7a66ab6cf05b43e", + "kind":"super-secure-person-with-significant-control", + "description":"super-secure-person-with-significant-control", + "identity_verification_details":{ + "appointment_verification_statement_date":"2026-07-01", + "appointment_verification_statement_due_on":"2026-09-01" + }, + "links":{"self":"/company/1/persons-with-significant-control/super-secure/2"} + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlDetailsClientTests/CompaniesHousePersonsWithSignificantControlDetailsClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlDetailsClientTests/CompaniesHousePersonsWithSignificantControlDetailsClientTests.cs new file mode 100644 index 0000000..0b05469 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlDetailsClientTests/CompaniesHousePersonsWithSignificantControlDetailsClientTests.cs @@ -0,0 +1,131 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Response.PersonsWithSignificantControl; +using CompaniesHouse.UriBuilders; +using Moq; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHousePersonsWithSignificantControlDetailsClientTests +{ + public class CompaniesHousePersonsWithSignificantControlDetailsClientTests + { + [Fact] + public async Task GivenCapturedIndividualDetail_WhenGettingIndividual_ThenObservedFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/company/11790215/persons-with-significant-control/individual/1"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, IndividualJson); + var uriBuilder = CreateUriBuilder(uri); + + var client = new CompaniesHousePersonsWithSignificantControlDetailsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetIndividualPersonWithSignificantControlAsync("11790215", "1"); + + result.Data.Kind.ShouldBe(new PersonWithSignificantControlKind("individual-person-with-significant-control")); + result.Data.Name.ShouldBe("Chris Brown"); + result.Data.Links?.Self.ShouldBe("/company/11790215/persons-with-significant-control/individual/SGX6zLwNkq2YrjsYXPSVnmYi6SE"); + (result.Data.NaturesOfControl ?? []).ShouldContain(new PersonWithSignificantControlNatureOfControl("ownership-of-shares-25-to-50-percent")); + } + + [Fact] + public async Task GivenCapturedStatementList_WhenGettingStatementsList_ThenEnvelopeAndItemsDeserialize() + { + var uri = new Uri("https://wibble.com/company/05124262/persons-with-significant-control-statements"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, StatementListJson); + var uriBuilder = CreateUriBuilder(uri); + + var client = new CompaniesHousePersonsWithSignificantControlDetailsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetPersonsWithSignificantControlStatementsAsync("05124262", 0, 25); + + result.Data.TotalResults.ShouldBe(1); + result.Data.Items.Length.ShouldBe(1); + result.Data.Items[0].Statement.ShouldBe("psc-has-failed-to-confirm-changed-details"); + result.Data.Items[0].Links.Self.ShouldBe("/company/05124262/persons-with-significant-control-statements/8xxEeFpu5Xmpf1ce1FmwM-sK8J8"); + } + + [Fact] + public async Task GivenCapturedSuperSecure_WhenGettingSuperSecurePsc_ThenObservedFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/company/1/persons-with-significant-control/super-secure/2"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, SuperSecureJson); + var uriBuilder = CreateUriBuilder(uri); + + var client = new CompaniesHousePersonsWithSignificantControlDetailsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetSuperSecurePersonWithSignificantControlAsync("1", "2"); + + result.Data.Kind.ShouldBe("super-secure-person-with-significant-control"); + result.Data.Description.ShouldBe("super-secure-person-with-significant-control"); + result.Data.Links.Self.ShouldBe("/company/1/persons-with-significant-control/super-secure/2"); + result.Data.IdentityVerificationDetails.ShouldNotBeNull(); + result.Data.IdentityVerificationDetails.AppointmentVerificationStatementDate.ShouldBe(new DateTime(2026, 7, 1)); + } + + private static Mock CreateUriBuilder(Uri uri) + { + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.BuildIndividual(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildIndividualBeneficialOwner(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildCorporateEntity(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildCorporateEntityBeneficialOwner(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildLegalPerson(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildLegalPersonBeneficialOwner(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildStatementsList(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildStatement(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildSuperSecure(It.IsAny(), It.IsAny())).Returns(uri); + uriBuilder.Setup(x => x.BuildSuperSecureBeneficialOwner(It.IsAny(), It.IsAny())).Returns(uri); + return uriBuilder; + } + + private const string IndividualJson = """ + { + "etag":"ef3d935f77e2f6b1dfacf1f3f7289a594f16f8e1", + "notified_on":"2019-01-16", + "kind":"individual-person-with-significant-control", + "country_of_residence":"United Kingdom", + "date_of_birth":{"month":7,"year":1979}, + "name":"Chris Brown", + "name_elements":{"forename":"Chris","surname":"Brown"}, + "links":{"self":"/company/11790215/persons-with-significant-control/individual/SGX6zLwNkq2YrjsYXPSVnmYi6SE"}, + "nationality":"British", + "address":{"address_line_1":"1 Street","locality":"London","postal_code":"W1A 1AA"}, + "natures_of_control":["ownership-of-shares-25-to-50-percent"] + } + """; + + private const string StatementListJson = """ + { + "items_per_page":25, + "items":[ + { + "etag":"95ca7497819e5fbc1144b6a3ef09f477228f3f5f", + "kind":"persons-with-significant-control-statement", + "notified_on":"2016-06-30", + "statement":"psc-has-failed-to-confirm-changed-details", + "links":{ + "self":"/company/05124262/persons-with-significant-control-statements/8xxEeFpu5Xmpf1ce1FmwM-sK8J8", + "person_with_significant_control":"/company/05124262/persons-with-significant-control/individual/KdK4nMdcYtuJV_Ax0s8_5JJmQdw" + } + } + ], + "start_index":0, + "total_results":1, + "active_count":1, + "ceased_count":0, + "links":{"self":"/company/05124262/persons-with-significant-control-statements"} + } + """; + + private const string SuperSecureJson = """ + { + "etag":"fa0f4f2a1b8186f26bc65820c7a66ab6cf05b43e", + "kind":"super-secure-person-with-significant-control", + "description":"super-secure-person-with-significant-control", + "identity_verification_details":{ + "appointment_verification_statement_date":"2026-07-01", + "appointment_verification_statement_due_on":"2026-09-01" + }, + "links":{"self":"/company/1/persons-with-significant-control/super-secure/2"} + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlDetailsUriBuilderTests/PersonsWithSignificantControlDetailsUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlDetailsUriBuilderTests/PersonsWithSignificantControlDetailsUriBuilderTests.cs new file mode 100644 index 0000000..e09ad3a --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/PersonsWithSignificantControlDetailsUriBuilderTests/PersonsWithSignificantControlDetailsUriBuilderTests.cs @@ -0,0 +1,82 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.PersonsWithSignificantControlDetailsUriBuilderTests +{ + public class PersonsWithSignificantControlDetailsUriBuilderTests + { + private readonly PersonsWithSignificantControlDetailsUriBuilder _builder = new(); + + [Fact] + public void BuildIndividual_EncodesIds() + { + _builder.BuildIndividual("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/individual/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildIndividualBeneficialOwner_EncodesIds() + { + _builder.BuildIndividualBeneficialOwner("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/individual-beneficial-owner/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildCorporateEntity_EncodesIds() + { + _builder.BuildCorporateEntity("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/corporate-entity/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildCorporateEntityBeneficialOwner_EncodesIds() + { + _builder.BuildCorporateEntityBeneficialOwner("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/corporate-entity-beneficial-owner/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildLegalPerson_EncodesIds() + { + _builder.BuildLegalPerson("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/legal-person/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildLegalPersonBeneficialOwner_EncodesIds() + { + _builder.BuildLegalPersonBeneficialOwner("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/legal-person-beneficial-owner/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildStatementsList_IncludesPagingAndOptionalRegisterView() + { + _builder.BuildStatementsList("00/123", 25, 10, true) + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control-statements?items_per_page=10&start_index=25®ister_view=true", UriKind.Relative)); + } + + [Fact] + public void BuildStatement_EncodesStatementId() + { + _builder.BuildStatement("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control-statements/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildSuperSecure_EncodesId() + { + _builder.BuildSuperSecure("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/super-secure/a%2Fb", UriKind.Relative)); + } + + [Fact] + public void BuildSuperSecureBeneficialOwner_EncodesId() + { + _builder.BuildSuperSecureBeneficialOwner("00/123", "a/b") + .ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/super-secure-beneficial-owner/a%2Fb", UriKind.Relative)); + } + } +} From 51c3bf1fd666407f314902d25d0d09c5bf1af492 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 23:45:46 +0100 Subject: [PATCH 36/38] Add company exemptions endpoint and tests --- .plans/completed/09k-exemptions.md | 40 ++++++++++++++ .../09-endpoint-catalogue-remaining.md | 2 +- src/CompaniesHouse/CompaniesHouseClient.cs | 8 +++ .../CompaniesHouseExemptionsClient.cs | 29 ++++++++++ src/CompaniesHouse/ICompaniesHouseClient.cs | 3 +- .../ICompaniesHouseExemptionsClient.cs | 11 ++++ .../Exemptions/CompanyExemptionPeriod.cs | 14 +++++ .../Response/Exemptions/CompanyExemptions.cs | 19 +++++++ .../Exemptions/CompanyExemptionsCategory.cs | 13 +++++ .../Exemptions/CompanyExemptionsDetail.cs | 22 ++++++++ .../Exemptions/CompanyExemptionsLinks.cs | 10 ++++ .../CompanyExemptionsUriBuilder.cs | 13 +++++ .../ICompanyExemptionsUriBuilder.cs | 9 ++++ .../ExemptionsTests/ExemptionsTestsValid.cs | 34 ++++++++++++ .../ExemptionsScenarios.cs | 45 ++++++++++++++++ .../CompaniesHouseExemptionsClientTests.cs | 53 +++++++++++++++++++ .../CompanyExemptionsUriBuilderTests.cs | 18 +++++++ 17 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 .plans/completed/09k-exemptions.md create mode 100644 src/CompaniesHouse/CompaniesHouseExemptionsClient.cs create mode 100644 src/CompaniesHouse/ICompaniesHouseExemptionsClient.cs create mode 100644 src/CompaniesHouse/Response/Exemptions/CompanyExemptionPeriod.cs create mode 100644 src/CompaniesHouse/Response/Exemptions/CompanyExemptions.cs create mode 100644 src/CompaniesHouse/Response/Exemptions/CompanyExemptionsCategory.cs create mode 100644 src/CompaniesHouse/Response/Exemptions/CompanyExemptionsDetail.cs create mode 100644 src/CompaniesHouse/Response/Exemptions/CompanyExemptionsLinks.cs create mode 100644 src/CompaniesHouse/UriBuilders/CompanyExemptionsUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/ICompanyExemptionsUriBuilder.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/ExemptionsTests/ExemptionsTestsValid.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/ExemptionsScenarios.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseExemptionsClientTests/CompaniesHouseExemptionsClientTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/CompanyExemptionsUriBuilderTests/CompanyExemptionsUriBuilderTests.cs diff --git a/.plans/completed/09k-exemptions.md b/.plans/completed/09k-exemptions.md new file mode 100644 index 0000000..8122155 --- /dev/null +++ b/.plans/completed/09k-exemptions.md @@ -0,0 +1,40 @@ +# 09k — Endpoint: Exemptions + +**Status:** completed +**Depends on:** `01-core`, `03-value-types` +**Blocks:** nothing + +## Goal + +Build the company exemptions endpoint from the live API docs. + +Docs: +- + +Path: +- `GET /company/{companyNumber}/exemptions` + +## Scope + +- Add a focused exemptions sub-client, URI builder and response model. +- Decide whether any exemption-description values should come from generated string-backed value types. + +## Tasks + +- [x] Confirm the live docs and payload schema. +- [x] Build client, builder and response model. +- [x] Add unit, scenario and integration coverage. + +## Open questions + +- Which exemption-description group in `api-enumerations` should back any enum-ish fields? + - Answered for now: no generated exemption-specific value type exists in this repo yet, so exemption type fields are modelled as raw strings and preserve wire values. + +## Acceptance criteria + +- A real exemptions response deserializes cleanly from the live API. + +## References + +- No existing master implementation - build from the live API docs only. +- diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md index 6d35c54..82dce6d 100644 --- a/.plans/outstanding/09-endpoint-catalogue-remaining.md +++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md @@ -17,7 +17,7 @@ Completed in this change: - `..\completed\09h-registers.md` - `..\completed\09i-disqualified-officers-detail.md` - `..\completed\09j-psc-detail-types.md` +- `..\completed\09k-exemptions.md` Still outstanding: -- `09k-exemptions.md` - `09l-uk-establishments.md` diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index 334bcd9..9b1fffa 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -8,6 +8,7 @@ using CompaniesHouse.Response.CompanyFiling; using CompaniesHouse.Response.CompanyProfile; using CompaniesHouse.Response.DisqualifiedOfficers; +using CompaniesHouse.Response.Exemptions; using CompaniesHouse.Response.Insolvency; using CompaniesHouse.Response.Officers; using CompaniesHouse.Response.PersonsWithSignificantControl; @@ -40,6 +41,7 @@ public class CompaniesHouseClient : ICompaniesHouseClient, IDisposable private readonly ICompaniesHouseRegistersClient _companiesHouseRegistersClient; private readonly ICompaniesHouseDisqualifiedOfficerDetailsClient _companiesHouseDisqualifiedOfficerDetailsClient; private readonly ICompaniesHousePersonsWithSignificantControlDetailsClient _companiesHousePersonsWithSignificantControlDetailsClient; + private readonly ICompaniesHouseExemptionsClient _companiesHouseExemptionsClient; private readonly HttpClient _httpClient; public CompaniesHouseClient(HttpClient httpClient) @@ -58,6 +60,7 @@ public CompaniesHouseClient(HttpClient httpClient) _companiesHouseRegistersClient = new CompaniesHouseRegistersClient(_httpClient, new CompanyRegistersUriBuilder()); _companiesHouseDisqualifiedOfficerDetailsClient = new CompaniesHouseDisqualifiedOfficerDetailsClient(_httpClient, new DisqualifiedOfficerUriBuilder()); _companiesHousePersonsWithSignificantControlDetailsClient = new CompaniesHousePersonsWithSignificantControlDetailsClient(_httpClient, new PersonsWithSignificantControlDetailsUriBuilder()); + _companiesHouseExemptionsClient = new CompaniesHouseExemptionsClient(_httpClient, new CompanyExemptionsUriBuilder()); } public CompaniesHouseClient(ICompaniesHouseSettings settings) @@ -194,6 +197,11 @@ public Task> Get return _companiesHousePersonsWithSignificantControlDetailsClient.GetSuperSecureBeneficialOwnerAsync(companyNumber, superSecureId, cancellationToken); } + public Task> GetCompanyExemptionsAsync(string companyNumber, CancellationToken cancellationToken = default) + { + return _companiesHouseExemptionsClient.GetCompanyExemptionsAsync(companyNumber, cancellationToken); + } + public Task> GetChargesListAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default) { return _companiesHouseChargesClient.GetChargesListAsync(companyNumber, startIndex, pageSize, cancellationToken); diff --git a/src/CompaniesHouse/CompaniesHouseExemptionsClient.cs b/src/CompaniesHouse/CompaniesHouseExemptionsClient.cs new file mode 100644 index 0000000..552948e --- /dev/null +++ b/src/CompaniesHouse/CompaniesHouseExemptionsClient.cs @@ -0,0 +1,29 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.Exemptions; +using CompaniesHouse.UriBuilders; + +namespace CompaniesHouse +{ + using CompaniesHouse.Extensions; + + public class CompaniesHouseExemptionsClient : ICompaniesHouseExemptionsClient + { + private readonly HttpClient _httpClient; + private readonly ICompanyExemptionsUriBuilder _uriBuilder; + + public CompaniesHouseExemptionsClient(HttpClient httpClient, ICompanyExemptionsUriBuilder uriBuilder) + { + _httpClient = httpClient; + _uriBuilder = uriBuilder; + } + + public async Task> GetCompanyExemptionsAsync(string companyNumber, CancellationToken cancellationToken = default) + { + var requestUri = _uriBuilder.Build(companyNumber); + var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/CompaniesHouse/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index 8264f85..dedc3a2 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -18,7 +18,8 @@ public interface ICompaniesHouseClient : ICompaniesHouseRegisteredOfficeAddressClient, ICompaniesHouseRegistersClient, ICompaniesHouseDisqualifiedOfficerDetailsClient, - ICompaniesHousePersonsWithSignificantControlDetailsClient + ICompaniesHousePersonsWithSignificantControlDetailsClient, + ICompaniesHouseExemptionsClient { } diff --git a/src/CompaniesHouse/ICompaniesHouseExemptionsClient.cs b/src/CompaniesHouse/ICompaniesHouseExemptionsClient.cs new file mode 100644 index 0000000..0749b17 --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHouseExemptionsClient.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.Exemptions; + +namespace CompaniesHouse +{ + public interface ICompaniesHouseExemptionsClient + { + Task> GetCompanyExemptionsAsync(string companyNumber, CancellationToken cancellationToken = default); + } +} diff --git a/src/CompaniesHouse/Response/Exemptions/CompanyExemptionPeriod.cs b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionPeriod.cs new file mode 100644 index 0000000..f36c9fc --- /dev/null +++ b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionPeriod.cs @@ -0,0 +1,14 @@ +using System; +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Exemptions +{ + public class CompanyExemptionPeriod + { + [JsonPropertyName("exempt_from")] + public DateTime ExemptFrom { get; set; } + + [JsonPropertyName("exempt_to")] + public DateTime? ExemptTo { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Exemptions/CompanyExemptions.cs b/src/CompaniesHouse/Response/Exemptions/CompanyExemptions.cs new file mode 100644 index 0000000..1d74e71 --- /dev/null +++ b/src/CompaniesHouse/Response/Exemptions/CompanyExemptions.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Exemptions +{ + public class CompanyExemptions + { + [JsonPropertyName("links")] + public CompanyExemptionsLinks Links { get; set; } = new(); + + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("etag")] + public string Etag { get; set; } = string.Empty; + + [JsonPropertyName("exemptions")] + public CompanyExemptionsDetail Exemptions { get; set; } = new(); + } +} diff --git a/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsCategory.cs b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsCategory.cs new file mode 100644 index 0000000..2e9174d --- /dev/null +++ b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsCategory.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Exemptions +{ + public class CompanyExemptionsCategory + { + [JsonPropertyName("items")] + public CompanyExemptionPeriod[] Items { get; set; } = []; + + [JsonPropertyName("exemption_type")] + public string ExemptionType { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsDetail.cs b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsDetail.cs new file mode 100644 index 0000000..bb03cad --- /dev/null +++ b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsDetail.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Exemptions +{ + public class CompanyExemptionsDetail + { + [JsonPropertyName("psc_exempt_as_trading_on_regulated_market")] + public CompanyExemptionsCategory? PscExemptAsTradingOnRegulatedMarket { get; set; } + + [JsonPropertyName("psc_exempt_as_shares_admitted_on_market")] + public CompanyExemptionsCategory? PscExemptAsSharesAdmittedOnMarket { get; set; } + + [JsonPropertyName("psc_exempt_as_trading_on_uk_regulated_market")] + public CompanyExemptionsCategory? PscExemptAsTradingOnUkRegulatedMarket { get; set; } + + [JsonPropertyName("psc_exempt_as_trading_on_eu_regulated_market")] + public CompanyExemptionsCategory? PscExemptAsTradingOnEuRegulatedMarket { get; set; } + + [JsonPropertyName("disclosure_transparency_rules_chapter_five_applies")] + public CompanyExemptionsCategory? DisclosureTransparencyRulesChapterFiveApplies { get; set; } + } +} diff --git a/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsLinks.cs b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsLinks.cs new file mode 100644 index 0000000..08780ae --- /dev/null +++ b/src/CompaniesHouse/Response/Exemptions/CompanyExemptionsLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.Exemptions +{ + public class CompanyExemptionsLinks + { + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/UriBuilders/CompanyExemptionsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/CompanyExemptionsUriBuilder.cs new file mode 100644 index 0000000..d803fcc --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/CompanyExemptionsUriBuilder.cs @@ -0,0 +1,13 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public class CompanyExemptionsUriBuilder : ICompanyExemptionsUriBuilder + { + public Uri Build(string companyNumber) + { + var path = $"company/{Uri.EscapeDataString(companyNumber)}/exemptions"; + return new Uri(path, UriKind.Relative); + } + } +} diff --git a/src/CompaniesHouse/UriBuilders/ICompanyExemptionsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/ICompanyExemptionsUriBuilder.cs new file mode 100644 index 0000000..3b6e8dd --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/ICompanyExemptionsUriBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public interface ICompanyExemptionsUriBuilder + { + Uri Build(string companyNumber); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/ExemptionsTests/ExemptionsTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ExemptionsTests/ExemptionsTestsValid.cs new file mode 100644 index 0000000..4ea003e --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ExemptionsTests/ExemptionsTestsValid.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using CompaniesHouse.Response.Exemptions; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.ExemptionsTests +{ + public class ExemptionsTestsValid + { + private readonly CompaniesHouseClient _client; + + public ExemptionsTestsValid() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + + [IntegrationFact] + public async Task ThenKnownCompanyExemptionsAreReturned() + { + var response = await _client.GetCompanyExemptionsAsync("00445790"); + if (response is CompaniesHouseResponse.RateLimited) + { + return; + } + + var result = response.ShouldBeOfType.Success>().Data; + + result.Kind.ShouldBe("exemptions"); + result.Links.Self.ShouldBe("/company/00445790/exemptions"); + result.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.ShouldNotBeNull(); + result.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.Items.ShouldNotBeEmpty(); + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/ExemptionsScenarios.cs b/tests/CompaniesHouse.ScenarioTests/ExemptionsScenarios.cs new file mode 100644 index 0000000..43afbd8 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/ExemptionsScenarios.cs @@ -0,0 +1,45 @@ +using System; +using System.Text.Json; +using CompaniesHouse.Response.Exemptions; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class ExemptionsScenarios + { + [Fact] + public void CompanyExemptions_DeserializesObservedLivePayload() + { + var value = JsonSerializer.Deserialize(ExemptionsJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Kind.ShouldBe("exemptions"); + value.Etag.ShouldBe("95753161ed97c525df753458c24b372ec2909393"); + value.Links.Self.ShouldBe("/company/00445790/exemptions"); + value.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.ShouldNotBeNull(); + value.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.ExemptionType.ShouldBe("psc-exempt-as-trading-on-uk-regulated-market"); + value.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.Items[0].ExemptFrom.ShouldBe(new DateTime(2018, 6, 18)); + value.Exemptions.DisclosureTransparencyRulesChapterFiveApplies.ShouldNotBeNull(); + value.Exemptions.DisclosureTransparencyRulesChapterFiveApplies.Items[0].ExemptTo.ShouldBe(new DateTime(2023, 2, 2)); + } + + private const string ExemptionsJson = """ + { + "links":{"self":"/company/00445790/exemptions"}, + "kind":"exemptions", + "etag":"95753161ed97c525df753458c24b372ec2909393", + "exemptions":{ + "psc_exempt_as_trading_on_uk_regulated_market":{ + "items":[{"exempt_from":"2018-06-18"}], + "exemption_type":"psc-exempt-as-trading-on-uk-regulated-market" + }, + "disclosure_transparency_rules_chapter_five_applies":{ + "items":[{"exempt_from":"2017-06-07","exempt_to":"2023-02-02"}], + "exemption_type":"disclosure-transparency-rules-chapter-five-applies" + } + } + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseExemptionsClientTests/CompaniesHouseExemptionsClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseExemptionsClientTests/CompaniesHouseExemptionsClientTests.cs new file mode 100644 index 0000000..c53c2c6 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseExemptionsClientTests/CompaniesHouseExemptionsClientTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Response.Exemptions; +using CompaniesHouse.UriBuilders; +using Moq; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseExemptionsClientTests +{ + public class CompaniesHouseExemptionsClientTests + { + [Fact] + public async Task GivenCapturedExemptionsPayload_WhenGettingExemptions_ThenObservedFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/company/00445790/exemptions"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, ExemptionsJson); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny())).Returns(uri); + + var client = new CompaniesHouseExemptionsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetCompanyExemptionsAsync("00445790"); + + result.Data.ShouldNotBeNull(); + result.Data.Kind.ShouldBe("exemptions"); + result.Data.Links.Self.ShouldBe("/company/00445790/exemptions"); + result.Data.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.ShouldNotBeNull(); + result.Data.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.ExemptionType.ShouldBe("psc-exempt-as-trading-on-uk-regulated-market"); + result.Data.Exemptions.PscExemptAsTradingOnUkRegulatedMarket.Items[0].ExemptFrom.ShouldBe(new DateTime(2018, 6, 18)); + result.Data.Exemptions.DisclosureTransparencyRulesChapterFiveApplies.ShouldNotBeNull(); + result.Data.Exemptions.DisclosureTransparencyRulesChapterFiveApplies.Items[0].ExemptTo.ShouldBe(new DateTime(2023, 2, 2)); + } + + private const string ExemptionsJson = """ + { + "links":{"self":"/company/00445790/exemptions"}, + "kind":"exemptions", + "etag":"95753161ed97c525df753458c24b372ec2909393", + "exemptions":{ + "psc_exempt_as_trading_on_uk_regulated_market":{ + "items":[{"exempt_from":"2018-06-18"}], + "exemption_type":"psc-exempt-as-trading-on-uk-regulated-market" + }, + "disclosure_transparency_rules_chapter_five_applies":{ + "items":[{"exempt_from":"2017-06-07","exempt_to":"2023-02-02"}], + "exemption_type":"disclosure-transparency-rules-chapter-five-applies" + } + } + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/CompanyExemptionsUriBuilderTests/CompanyExemptionsUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/CompanyExemptionsUriBuilderTests/CompanyExemptionsUriBuilderTests.cs new file mode 100644 index 0000000..c8842b9 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/CompanyExemptionsUriBuilderTests/CompanyExemptionsUriBuilderTests.cs @@ -0,0 +1,18 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.CompanyExemptionsUriBuilderTests +{ + public class CompanyExemptionsUriBuilderTests + { + [Fact] + public void Build_EncodesCompanyNumber() + { + var uri = new CompanyExemptionsUriBuilder().Build("00/123"); + + uri.ShouldBe(new Uri("company/00%2F123/exemptions", UriKind.Relative)); + } + } +} From f0aee3e61432c4ebbda4963ece580bc1d75888f9 Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Fri, 3 Jul 2026 23:47:52 +0100 Subject: [PATCH 37/38] Add UK establishments endpoint and tests --- .plans/completed/09l-uk-establishments.md | 40 ++++++++++++++ .../09-endpoint-catalogue-remaining.md | 2 +- src/CompaniesHouse/CompaniesHouseClient.cs | 8 +++ .../CompaniesHouseUkEstablishmentsClient.cs | 29 +++++++++++ src/CompaniesHouse/ICompaniesHouseClient.cs | 3 +- .../ICompaniesHouseUkEstablishmentsClient.cs | 11 ++++ .../CompanyUkEstablishment.cs | 22 ++++++++ .../CompanyUkEstablishmentLinks.cs | 10 ++++ .../CompanyUkEstablishments.cs | 19 +++++++ .../CompanyUkEstablishmentsLinks.cs | 10 ++++ .../CompanyUkEstablishmentsUriBuilder.cs | 13 +++++ .../ICompanyUkEstablishmentsUriBuilder.cs | 9 ++++ .../UkEstablishmentsTestsValid.cs | 33 ++++++++++++ .../UkEstablishmentsScenarios.cs | 43 +++++++++++++++ ...mpaniesHouseUkEstablishmentsClientTests.cs | 52 +++++++++++++++++++ .../CompanyUkEstablishmentsUriBuilderTests.cs | 18 +++++++ 16 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 .plans/completed/09l-uk-establishments.md create mode 100644 src/CompaniesHouse/CompaniesHouseUkEstablishmentsClient.cs create mode 100644 src/CompaniesHouse/ICompaniesHouseUkEstablishmentsClient.cs create mode 100644 src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishment.cs create mode 100644 src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentLinks.cs create mode 100644 src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishments.cs create mode 100644 src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentsLinks.cs create mode 100644 src/CompaniesHouse/UriBuilders/CompanyUkEstablishmentsUriBuilder.cs create mode 100644 src/CompaniesHouse/UriBuilders/ICompanyUkEstablishmentsUriBuilder.cs create mode 100644 tests/CompaniesHouse.IntegrationTests/Tests/UkEstablishmentsTests/UkEstablishmentsTestsValid.cs create mode 100644 tests/CompaniesHouse.ScenarioTests/UkEstablishmentsScenarios.cs create mode 100644 tests/CompaniesHouse.Tests/CompaniesHouseUkEstablishmentsClientTests/CompaniesHouseUkEstablishmentsClientTests.cs create mode 100644 tests/CompaniesHouse.Tests/UriBuilders/CompanyUkEstablishmentsUriBuilderTests/CompanyUkEstablishmentsUriBuilderTests.cs diff --git a/.plans/completed/09l-uk-establishments.md b/.plans/completed/09l-uk-establishments.md new file mode 100644 index 0000000..8120b36 --- /dev/null +++ b/.plans/completed/09l-uk-establishments.md @@ -0,0 +1,40 @@ +# 09l — Endpoint: UK establishments + +**Status:** completed +**Depends on:** `01-core`, `07-company-profile` +**Blocks:** nothing + +## Goal + +Build the UK establishments endpoint from the live API docs. + +Docs: +- + +Path: +- `GET /company/{companyNumber}/uk-establishments` + +## Scope + +- Add a focused UK-establishments sub-client, URI builder and response model. +- Verify the live payload shape, especially address and linkage fields. + +## Tasks + +- [x] Confirm the docs and live schema. +- [x] Build client, builder and model. +- [x] Add unit, scenario and integration coverage. + +## Open questions + +- Are UK establishments returned as a simple list or a paged envelope? + - Answered: live responses are a simple list envelope (`etag`, `kind`, `links`, `items`) without paging fields. + +## Acceptance criteria + +- A real UK establishments response deserializes from the live API. + +## References + +- No existing master implementation - build from the live API docs only. +- diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md index 82dce6d..f1fcaaa 100644 --- a/.plans/outstanding/09-endpoint-catalogue-remaining.md +++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md @@ -18,6 +18,6 @@ Completed in this change: - `..\completed\09i-disqualified-officers-detail.md` - `..\completed\09j-psc-detail-types.md` - `..\completed\09k-exemptions.md` +- `..\completed\09l-uk-establishments.md` Still outstanding: -- `09l-uk-establishments.md` diff --git a/src/CompaniesHouse/CompaniesHouseClient.cs b/src/CompaniesHouse/CompaniesHouseClient.cs index 9b1fffa..e515fae 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -21,6 +21,7 @@ using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch; using CompaniesHouse.Response.Search.DissolvedCompaniesSearch; using CompaniesHouse.Response.Search.OfficerSearch; +using CompaniesHouse.Response.UkEstablishments; using CompaniesHouse.UriBuilders; using Officer = CompaniesHouse.Response.Officers.Officer; @@ -42,6 +43,7 @@ public class CompaniesHouseClient : ICompaniesHouseClient, IDisposable private readonly ICompaniesHouseDisqualifiedOfficerDetailsClient _companiesHouseDisqualifiedOfficerDetailsClient; private readonly ICompaniesHousePersonsWithSignificantControlDetailsClient _companiesHousePersonsWithSignificantControlDetailsClient; private readonly ICompaniesHouseExemptionsClient _companiesHouseExemptionsClient; + private readonly ICompaniesHouseUkEstablishmentsClient _companiesHouseUkEstablishmentsClient; private readonly HttpClient _httpClient; public CompaniesHouseClient(HttpClient httpClient) @@ -61,6 +63,7 @@ public CompaniesHouseClient(HttpClient httpClient) _companiesHouseDisqualifiedOfficerDetailsClient = new CompaniesHouseDisqualifiedOfficerDetailsClient(_httpClient, new DisqualifiedOfficerUriBuilder()); _companiesHousePersonsWithSignificantControlDetailsClient = new CompaniesHousePersonsWithSignificantControlDetailsClient(_httpClient, new PersonsWithSignificantControlDetailsUriBuilder()); _companiesHouseExemptionsClient = new CompaniesHouseExemptionsClient(_httpClient, new CompanyExemptionsUriBuilder()); + _companiesHouseUkEstablishmentsClient = new CompaniesHouseUkEstablishmentsClient(_httpClient, new CompanyUkEstablishmentsUriBuilder()); } public CompaniesHouseClient(ICompaniesHouseSettings settings) @@ -202,6 +205,11 @@ public Task> GetCompanyExemptionsAsync return _companiesHouseExemptionsClient.GetCompanyExemptionsAsync(companyNumber, cancellationToken); } + public Task> GetCompanyUkEstablishmentsAsync(string companyNumber, CancellationToken cancellationToken = default) + { + return _companiesHouseUkEstablishmentsClient.GetCompanyUkEstablishmentsAsync(companyNumber, cancellationToken); + } + public Task> GetChargesListAsync(string companyNumber, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default) { return _companiesHouseChargesClient.GetChargesListAsync(companyNumber, startIndex, pageSize, cancellationToken); diff --git a/src/CompaniesHouse/CompaniesHouseUkEstablishmentsClient.cs b/src/CompaniesHouse/CompaniesHouseUkEstablishmentsClient.cs new file mode 100644 index 0000000..9004cc9 --- /dev/null +++ b/src/CompaniesHouse/CompaniesHouseUkEstablishmentsClient.cs @@ -0,0 +1,29 @@ +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.UkEstablishments; +using CompaniesHouse.UriBuilders; + +namespace CompaniesHouse +{ + using CompaniesHouse.Extensions; + + public class CompaniesHouseUkEstablishmentsClient : ICompaniesHouseUkEstablishmentsClient + { + private readonly HttpClient _httpClient; + private readonly ICompanyUkEstablishmentsUriBuilder _uriBuilder; + + public CompaniesHouseUkEstablishmentsClient(HttpClient httpClient, ICompanyUkEstablishmentsUriBuilder uriBuilder) + { + _httpClient = httpClient; + _uriBuilder = uriBuilder; + } + + public async Task> GetCompanyUkEstablishmentsAsync(string companyNumber, CancellationToken cancellationToken = default) + { + var requestUri = _uriBuilder.Build(companyNumber); + var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/CompaniesHouse/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index dedc3a2..b095156 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -19,7 +19,8 @@ public interface ICompaniesHouseClient : ICompaniesHouseRegistersClient, ICompaniesHouseDisqualifiedOfficerDetailsClient, ICompaniesHousePersonsWithSignificantControlDetailsClient, - ICompaniesHouseExemptionsClient + ICompaniesHouseExemptionsClient, + ICompaniesHouseUkEstablishmentsClient { } diff --git a/src/CompaniesHouse/ICompaniesHouseUkEstablishmentsClient.cs b/src/CompaniesHouse/ICompaniesHouseUkEstablishmentsClient.cs new file mode 100644 index 0000000..0484579 --- /dev/null +++ b/src/CompaniesHouse/ICompaniesHouseUkEstablishmentsClient.cs @@ -0,0 +1,11 @@ +using System.Threading; +using System.Threading.Tasks; +using CompaniesHouse.Response.UkEstablishments; + +namespace CompaniesHouse +{ + public interface ICompaniesHouseUkEstablishmentsClient + { + Task> GetCompanyUkEstablishmentsAsync(string companyNumber, CancellationToken cancellationToken = default); + } +} diff --git a/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishment.cs b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishment.cs new file mode 100644 index 0000000..cdbbb10 --- /dev/null +++ b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishment.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.UkEstablishments +{ + public class CompanyUkEstablishment + { + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } = string.Empty; + + [JsonPropertyName("company_name")] + public string CompanyName { get; set; } = string.Empty; + + [JsonPropertyName("company_status")] + public CompanyStatus CompanyStatus { get; set; } + + [JsonPropertyName("locality")] + public string? Locality { get; set; } + + [JsonPropertyName("links")] + public CompanyUkEstablishmentLinks Links { get; set; } = new(); + } +} diff --git a/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentLinks.cs b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentLinks.cs new file mode 100644 index 0000000..57e50e7 --- /dev/null +++ b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.UkEstablishments +{ + public class CompanyUkEstablishmentLinks + { + [JsonPropertyName("company")] + public string Company { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishments.cs b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishments.cs new file mode 100644 index 0000000..f428e92 --- /dev/null +++ b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishments.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.UkEstablishments +{ + public class CompanyUkEstablishments + { + [JsonPropertyName("etag")] + public string Etag { get; set; } = string.Empty; + + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; + + [JsonPropertyName("links")] + public CompanyUkEstablishmentsLinks Links { get; set; } = new(); + + [JsonPropertyName("items")] + public CompanyUkEstablishment[] Items { get; set; } = []; + } +} diff --git a/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentsLinks.cs b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentsLinks.cs new file mode 100644 index 0000000..7ebf947 --- /dev/null +++ b/src/CompaniesHouse/Response/UkEstablishments/CompanyUkEstablishmentsLinks.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace CompaniesHouse.Response.UkEstablishments +{ + public class CompanyUkEstablishmentsLinks + { + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + } +} diff --git a/src/CompaniesHouse/UriBuilders/CompanyUkEstablishmentsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/CompanyUkEstablishmentsUriBuilder.cs new file mode 100644 index 0000000..6da8f42 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/CompanyUkEstablishmentsUriBuilder.cs @@ -0,0 +1,13 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public class CompanyUkEstablishmentsUriBuilder : ICompanyUkEstablishmentsUriBuilder + { + public Uri Build(string companyNumber) + { + var path = $"company/{Uri.EscapeDataString(companyNumber)}/uk-establishments"; + return new Uri(path, UriKind.Relative); + } + } +} diff --git a/src/CompaniesHouse/UriBuilders/ICompanyUkEstablishmentsUriBuilder.cs b/src/CompaniesHouse/UriBuilders/ICompanyUkEstablishmentsUriBuilder.cs new file mode 100644 index 0000000..74c5f54 --- /dev/null +++ b/src/CompaniesHouse/UriBuilders/ICompanyUkEstablishmentsUriBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace CompaniesHouse.UriBuilders +{ + public interface ICompanyUkEstablishmentsUriBuilder + { + Uri Build(string companyNumber); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/UkEstablishmentsTests/UkEstablishmentsTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/UkEstablishmentsTests/UkEstablishmentsTestsValid.cs new file mode 100644 index 0000000..9330756 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/UkEstablishmentsTests/UkEstablishmentsTestsValid.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using CompaniesHouse.Response.UkEstablishments; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.IntegrationTests.Tests.UkEstablishmentsTests +{ + public class UkEstablishmentsTestsValid + { + private readonly CompaniesHouseClient _client; + + public UkEstablishmentsTestsValid() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); + } + + [IntegrationFact] + public async Task ThenKnownForeignCompanyReturnsUkEstablishments() + { + var response = await _client.GetCompanyUkEstablishmentsAsync("FC040879"); + if (response is CompaniesHouseResponse.RateLimited) + { + return; + } + + var result = response.ShouldBeOfType.Success>().Data; + result.Kind.ShouldBe("related-companies"); + result.Links.Self.ShouldBe("/company/FC040879"); + result.Items.ShouldNotBeEmpty(); + result.Items[0].Links.Company.ShouldNotBeNullOrWhiteSpace(); + } + } +} diff --git a/tests/CompaniesHouse.ScenarioTests/UkEstablishmentsScenarios.cs b/tests/CompaniesHouse.ScenarioTests/UkEstablishmentsScenarios.cs new file mode 100644 index 0000000..9583121 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/UkEstablishmentsScenarios.cs @@ -0,0 +1,43 @@ +using System.Text.Json; +using CompaniesHouse.Response; +using CompaniesHouse.Response.UkEstablishments; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.ScenarioTests +{ + public class UkEstablishmentsScenarios + { + [Fact] + public void CompanyUkEstablishments_DeserializesObservedLivePayload() + { + var value = JsonSerializer.Deserialize(UkEstablishmentsJson, CompaniesHouseJsonSerializerOptions.Default); + + value.ShouldNotBeNull(); + value.Kind.ShouldBe("related-companies"); + value.Etag.ShouldBe("7d23ba7a5bc001b8bbe553b879ed445c342a9353"); + value.Links.Self.ShouldBe("/company/FC040879"); + value.Items.Length.ShouldBe(1); + value.Items[0].CompanyName.ShouldBe("ABSA UK PERMANENT ESTABLISHMENT"); + value.Items[0].CompanyStatus.ShouldBe(new CompanyStatus("open")); + value.Items[0].Links.Company.ShouldBe("/company/BR025996"); + } + + private const string UkEstablishmentsJson = """ + { + "etag":"7d23ba7a5bc001b8bbe553b879ed445c342a9353", + "kind":"related-companies", + "links":{"self":"/company/FC040879"}, + "items":[ + { + "company_name":"ABSA UK PERMANENT ESTABLISHMENT", + "company_number":"BR025996", + "company_status":"open", + "locality":"London", + "links":{"company":"/company/BR025996"} + } + ] + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseUkEstablishmentsClientTests/CompaniesHouseUkEstablishmentsClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseUkEstablishmentsClientTests/CompaniesHouseUkEstablishmentsClientTests.cs new file mode 100644 index 0000000..a3a7345 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseUkEstablishmentsClientTests/CompaniesHouseUkEstablishmentsClientTests.cs @@ -0,0 +1,52 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Response; +using CompaniesHouse.Response.UkEstablishments; +using CompaniesHouse.UriBuilders; +using Moq; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.CompaniesHouseUkEstablishmentsClientTests +{ + public class CompaniesHouseUkEstablishmentsClientTests + { + [Fact] + public async Task GivenCapturedUkEstablishmentsPayload_WhenGettingUkEstablishments_ThenObservedFieldsDeserialize() + { + var uri = new Uri("https://wibble.com/company/FC040879/uk-establishments"); + HttpMessageHandler handler = new StubHttpMessageHandler(uri, UkEstablishmentsJson); + var uriBuilder = new Mock(); + uriBuilder.Setup(x => x.Build(It.IsAny())).Returns(uri); + + var client = new CompaniesHouseUkEstablishmentsClient(new HttpClient(handler), uriBuilder.Object); + var result = await client.GetCompanyUkEstablishmentsAsync("FC040879"); + + result.Data.ShouldNotBeNull(); + result.Data.Kind.ShouldBe("related-companies"); + result.Data.Links.Self.ShouldBe("/company/FC040879"); + result.Data.Items.Length.ShouldBe(1); + result.Data.Items[0].CompanyNumber.ShouldBe("BR025996"); + result.Data.Items[0].CompanyStatus.ShouldBe(new CompanyStatus("open")); + result.Data.Items[0].Links.Company.ShouldBe("/company/BR025996"); + } + + private const string UkEstablishmentsJson = """ + { + "etag":"7d23ba7a5bc001b8bbe553b879ed445c342a9353", + "kind":"related-companies", + "links":{"self":"/company/FC040879"}, + "items":[ + { + "company_name":"ABSA UK PERMANENT ESTABLISHMENT", + "company_number":"BR025996", + "company_status":"open", + "locality":"London", + "links":{"company":"/company/BR025996"} + } + ] + } + """; + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/CompanyUkEstablishmentsUriBuilderTests/CompanyUkEstablishmentsUriBuilderTests.cs b/tests/CompaniesHouse.Tests/UriBuilders/CompanyUkEstablishmentsUriBuilderTests/CompanyUkEstablishmentsUriBuilderTests.cs new file mode 100644 index 0000000..d71e9a1 --- /dev/null +++ b/tests/CompaniesHouse.Tests/UriBuilders/CompanyUkEstablishmentsUriBuilderTests/CompanyUkEstablishmentsUriBuilderTests.cs @@ -0,0 +1,18 @@ +using System; +using CompaniesHouse.UriBuilders; +using Shouldly; +using Xunit; + +namespace CompaniesHouse.Tests.UriBuilders.CompanyUkEstablishmentsUriBuilderTests +{ + public class CompanyUkEstablishmentsUriBuilderTests + { + [Fact] + public void Build_EncodesCompanyNumber() + { + var uri = new CompanyUkEstablishmentsUriBuilder().Build("FC/040879"); + + uri.ShouldBe(new Uri("company/FC%2F040879/uk-establishments", UriKind.Relative)); + } + } +} From 0e9e57ee61e2649641fa13381ad9ba9bf3adf0fe Mon Sep 17 00:00:00 2001 From: Kevin Smith Date: Mon, 6 Jul 2026 23:13:22 +0100 Subject: [PATCH 38/38] Update CI versioning and add ad-hoc prerelease dispatch --- .../continuous-integration-workflow.yml | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 9607a34..c4cc2af 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -1,13 +1,33 @@ name: Continuous Integration Workflow -on: [push, pull_request] +on: + push: + pull_request: + workflow_dispatch: + inputs: + version: + description: "NuGet package version (SemVer, e.g. 9.1.0-pre.1)" + required: true + type: string + publish_package: + description: "Publish packages and create a release" + required: true + default: true + type: boolean + prerelease: + description: "Mark the GitHub release as prerelease" + required: true + default: true + type: boolean jobs: build: runs-on: ubuntu-latest env: - VERSION: ${{ github.ref == 'refs/heads/master' && format('8.0.{0}', github.run_number) || format('9.0.0-pre{0}', github.run_number) }} - PUBLISH_PACKAGE: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/prerelease' }} + VERSION: ${{ github.ref == 'refs/heads/master' && 'v9.0.0' || (github.event_name == 'workflow_dispatch' && format('v{0}', github.event.inputs.version) || format('v9.0.0-pre{0}', github.run_number)) }} + PACKAGE_VERSION: ${{ github.ref == 'refs/heads/master' && '9.0.0' || (github.event_name == 'workflow_dispatch' && github.event.inputs.version || format('9.0.0-pre{0}', github.run_number)) }} + PUBLISH_PACKAGE: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_package == 'true') || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/prerelease' }} + IS_PRERELEASE: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.prerelease == 'true') || github.ref != 'refs/heads/master' }} DOCKER_BUILDKIT: 1 BUILDKIT_PROGRESS: plain steps: @@ -22,7 +42,7 @@ jobs: env: COMPANIES_HOUSE_API_KEY: ${{ secrets.COMPANIES_HOUSE_API_KEY }} run: | - docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.VERSION }} --secret id=companies_house_api_key,env=COMPANIES_HOUSE_API_KEY -f ./Dockerfile --output ./ . + docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.PACKAGE_VERSION }} --secret id=companies_house_api_key,env=COMPANIES_HOUSE_API_KEY -f ./Dockerfile --output ./ . - name: Validate NuGet package metadata run: | mapfile -t packages < <(find ./artifacts -maxdepth 1 -type f -name '*.nupkg' ! -name '*.snupkg' | sort) @@ -60,7 +80,7 @@ jobs: - name: NuGet.Org push if: ${{ env.PUBLISH_PACKAGE }} run: | - dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }} + dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate - name: Generate release notes with NuGet links if: ${{ env.PUBLISH_PACKAGE }} run: | @@ -69,8 +89,8 @@ jobs: '' \ 'This release includes the following NuGet packages:' \ '' \ - "- [CompaniesHouse](https://www.nuget.org/packages/CompaniesHouse/${{ env.VERSION }}) - Core .NET client for Companies House API" \ - "- [CompaniesHouse.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/CompaniesHouse.Extensions.Microsoft.DependencyInjection/${{ env.VERSION }}) - DI helpers for ASP.NET Core / generic-host apps" \ + "- [CompaniesHouse](https://www.nuget.org/packages/CompaniesHouse/${{ env.PACKAGE_VERSION }}) - Core .NET client for Companies House API" \ + "- [CompaniesHouse.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/CompaniesHouse.Extensions.Microsoft.DependencyInjection/${{ env.PACKAGE_VERSION }}) - DI helpers for ASP.NET Core / generic-host apps" \ '' \ 'Install via:' \ '```' \ @@ -94,4 +114,4 @@ jobs: ./artifacts/*.nupkg ./artifacts/*.snupkg draft: false - prerelease: false + prerelease: ${{ env.IS_PRERELEASE == 'true' }}