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/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index bb92a10..c4cc2af 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -1,21 +1,73 @@ 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: 8.0.${{ github.run_number }} + 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: - - 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 + env: + COMPANIES_HOUSE_API_KEY: ${{ secrets.COMPANIES_HOUSE_API_KEY }} + run: | + 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: | - docker build --build-arg NUGET_PACKAGE_VERSION=${{ env.VERSION }} --build-arg COMPANIES_HOUSE_API_KEY=${{ secrets.COMPANIES_HOUSE_API_KEY }} -f ./Dockerfile --output ./ . + 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: dorny/test-reporter@v1 if: always() @@ -26,19 +78,40 @@ jobs: fail-on-error: true fail-on-empty: true - 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 }} --skip-duplicate + - name: Generate release notes with NuGet links + if: ${{ env.PUBLISH_PACKAGE }} run: | - dotnet nuget push ./artifacts/*.nupkg --source NuGet.org --api-key ${{ secrets.NUGET_API_KEY }} + printf '%s\n' \ + '## NuGet Packages' \ + '' \ + 'This release includes the following NuGet packages:' \ + '' \ + "- [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:' \ + '```' \ + 'dotnet add package CompaniesHouse' \ + 'dotnet add package CompaniesHouse.Extensions.Microsoft.DependencyInjection' \ + '```' \ + '' \ + 'See the [README](https://github.com/kevbite/CompaniesHouse.NET#readme) for usage instructions.' \ + > release_notes.md - name: Create Release id: create_release - if: github.ref == 'refs/heads/master' - uses: actions/create-release@v1 + if: ${{ env.PUBLISH_PACKAGE }} + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ env.VERSION }} - release_name: Release ${{ env.VERSION }} - body: | - Release ${{ env.VERSION }} + name: Release ${{ env.VERSION }} + body_path: release_notes.md + files: | + ./artifacts/*.nupkg + ./artifacts/*.snupkg draft: false - prerelease: false + prerelease: ${{ env.IS_PRERELEASE == '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/.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/completed/01-core-client-architecture.md b/.plans/completed/01-core-client-architecture.md new file mode 100644 index 0000000..6a4f895 --- /dev/null +++ b/.plans/completed/01-core-client-architecture.md @@ -0,0 +1,120 @@ +# 01 — Core client architecture + +**Status:** complete +**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/completed/02-di-extensions-ioptions.md b/.plans/completed/02-di-extensions-ioptions.md new file mode 100644 index 0000000..e496b25 --- /dev/null +++ b/.plans/completed/02-di-extensions-ioptions.md @@ -0,0 +1,94 @@ +# 02 — DI extensions with IOptions<> + +**Status:** complete +**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/completed/03-string-backed-value-types.md b/.plans/completed/03-string-backed-value-types.md new file mode 100644 index 0000000..d785b70 --- /dev/null +++ b/.plans/completed/03-string-backed-value-types.md @@ -0,0 +1,116 @@ +# 03 — String-backed value types (replace all enums) + +**Status:** complete +**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/completed/04-enum-source-generator.md b/.plans/completed/04-enum-source-generator.md new file mode 100644 index 0000000..ab5149a --- /dev/null +++ b/.plans/completed/04-enum-source-generator.md @@ -0,0 +1,124 @@ +# 04 — Enum source generator + +**Status:** complete +**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/completed/05-api-enumerations-submodule.md b/.plans/completed/05-api-enumerations-submodule.md new file mode 100644 index 0000000..5f71edf --- /dev/null +++ b/.plans/completed/05-api-enumerations-submodule.md @@ -0,0 +1,85 @@ +# 05 — api-enumerations submodule & local extras + +**Status:** complete +**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/completed/06-endpoint-search.md b/.plans/completed/06-endpoint-search.md new file mode 100644 index 0000000..8f7dd58 --- /dev/null +++ b/.plans/completed/06-endpoint-search.md @@ -0,0 +1,140 @@ +# 06 — Endpoint: Search (start here) + +**Status:** complete +**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). + +## Delivered + +- Fixed the long-standing `restrictions` query bug in + `SearchCompanyUriBuilder`: the parameter is now emitted only when a + non-empty value is supplied, it is URL-escaped consistently with the base `q` + handling, and `SearchCompanyRequest.Restrictions` is now nullable to reflect + the documented optional contract. +- Added the three missing Search endpoints to `CompaniesHouseClient` and DI: + `SearchCompaniesAlphabeticallyAsync` (`GET /alphabetical-search/companies`), + `SearchDissolvedCompaniesAsync` (`GET /dissolved-search/companies`) and + `AdvancedCompanySearchAsync` (`GET /advanced-search/companies`), each with a + dedicated request model, URI builder, response envelope and item models wired + through the existing `CompaniesHouseSearchClient` / search-builder factory + pattern. +- Modelled the new endpoint-specific query contracts from the live docs rather + than forcing them into the older `q/items_per_page/start_index` shape: + alphabetical search uses `search_above` / `search_below` / `size`, + dissolved search adds `search_type` plus its paging variants, and advanced + search emits only the supplied filters, formatting list filters as + comma-delimited query values and dates as `yyyy-MM-dd`. +- Migrated `CompanyType` from the hand-written wire enum to the Roslyn + generator by adding `company_type` and `company_subtype` entries to + `enum-map.txt`, deleting the old `Response/CompanyType.cs`, and consuming the + generated string-backed `CompanyType` / `CompanySubtype` value types in + search/company-profile models and advanced-search filters. This keeps unknown + type/subtype values non-breaking in the same way `CompanyStatus` already is. +- Added unit/integration coverage for the new surface: URI-builder tests for + the restrictions fix plus the new builders, search-client deserialization + tests for the 3 new endpoints, value-type round-trip tests for generated + `CompanyType` / `CompanySubtype`, DI resolution coverage for the new granular + interfaces, and new real-API integration tests for alphabetical, dissolved + and advanced company search. +- Verified: full solution build (`CompaniesHouse.slnx`, Release) with 0 errors; + `CompaniesHouse.Tests` passing; `CompaniesHouse.ScenarioTests` passing; + `CompaniesHouse.SourceGenerator.Tests` 28/28 after adding a regression test + for multiple enum-map entries; `CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests` + passing; `dotnet format --verify-no-changes` clean on all touched files. The + full integration suite still has unrelated pre-existing failures in older + invalid-case tests, but the 3 new search integration tests pass when run + directly against a configured API key. diff --git a/.plans/completed/07-endpoint-company-profile.md b/.plans/completed/07-endpoint-company-profile.md new file mode 100644 index 0000000..2d50b8c --- /dev/null +++ b/.plans/completed/07-endpoint-company-profile.md @@ -0,0 +1,100 @@ +# 07 — Endpoint: Company Profile + +**Status:** complete +**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). + +## Delivered + +- Rebuilt the company-profile enum-ish fields onto the Roslyn-generated + string-backed value-type pattern: `CompanyStatusDetail` now comes from the + `company_status_detail` YAML group in the root `CompaniesHouse.Response` + namespace, `Jurisdiction` now comes from the `jurisdiction` YAML group in + `CompaniesHouse.Response.CompanyProfile`, and both hand-written wire enums + were removed. This brings company profile into the same unknown-value-safe + model already used by `CompanyStatus`/`CompanyType`. +- Added two new generated company-profile value types from the live + `api-enumerations` data: `ForeignAccountType` and + `TermsOfAccountPublication`. `foreign_company_details.accounting_requirement` + now uses these value types directly, so future new wire values round-trip + without deserialization failures. +- Extended `Response.CompanyProfile.CompanyProfile` to match the confirmed live + schema gaps: `subtype` (wired to generated `CompanySubtype`), + `has_super_secure_pscs`, `external_registration_number`, and the full + `foreign_company_details` object graph. The foreign-company model reuses the + existing `{day, month}` partial-date shape via `AccountingReferenceDate` for + `account_period_from` / `account_period_to`, and models + `must_file_within.months` as the raw string count returned by the API. +- Extended `CompanyProfileLinks` with the missing `exemptions` and + `uk_establishments` links confirmed by real API payloads. +- Added coverage across the stack: generated value-type round-trip tests for + `CompanyStatusDetail` and `Jurisdiction`; client-level company-profile tests + for realistic deserialization plus explicit 404 semantics; scenario + deserialization tests using captured plain/foreign/CIC payloads; and + integration assertions for the standard (`00445790`), foreign (`FC040879`) + and subtype (`13507518`) company profiles. +- Verified: `dotnet build CompaniesHouse.slnx -c Release` with 0 errors; + `dotnet test tests\CompaniesHouse.Tests\CompaniesHouse.Tests.csproj -c Release` + passing; `dotnet test tests\CompaniesHouse.ScenarioTests\CompaniesHouse.ScenarioTests.csproj -c Release` + passing; `dotnet test tests\CompaniesHouse.SourceGenerator.Tests\CompaniesHouse.SourceGenerator.Tests.csproj -c Release` + passing 28/28 after the enum-map additions; whitespace formatting clean on + all touched files via `dotnet format whitespace --verify-no-changes`; and the + targeted company-profile integration tests passing 5/5 with a configured API + key. diff --git a/.plans/completed/08-endpoint-officers.md b/.plans/completed/08-endpoint-officers.md new file mode 100644 index 0000000..70f1571 --- /dev/null +++ b/.plans/completed/08-endpoint-officers.md @@ -0,0 +1,112 @@ +# 08 — Endpoint: Officers + +**Status:** complete +**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). + +## Delivered + +- Rebuilt the officers wire enums onto the Roslyn-generated string-backed value + type pattern. `OfficerRole` now comes from the `officer_role` YAML group and + `OfficerIdentification.IdentificationType` now uses a generated + `IdentificationType` value type from the `identification_type` YAML group, + replacing the old hand-written enum/string model and preserving unknown future + wire values without deserialization failures. +- Extended the officers response models to match the confirmed live schema: + list envelopes now include `etag`, `kind`, `links.self`, `inactive_count` and + `items_per_page`; officer/appointment items now include `etag`, + `person_number`, `is_pre_1992_appointment`, `identity_verification_details`, + `links.self`, and the live `appointed_before` field seen on historic + appointments. `total_results` remains a non-nullable `int`, matching repeated + real API responses. +- Restored the `OfficerId` convenience on `Response.Officers.Officer` as a + computed, `[JsonIgnore]`d property derived from + `links.officer.appointments`, and hardened the shared parsing logic so missing + or malformed links return `null` rather than throwing. +- Extended `GetOfficersAsync` and `OfficersUriBuilder` with the documented + optional query parameters `register_type`, `register_view` and `order_by`, + while keeping the established "only emit supplied optional parameters" + builder pattern. The officers endpoint default page size is now 35 instead of + 25 to match Companies House's documented behaviour. +- Added coverage across the stack: URI-builder tests for the new query + parameters; client/unit tests using captured live list + appointment payloads; + new value-type round-trip tests for `OfficerRole` and `IdentificationType`; + scenario deserialization tests for the confirmed Tesco and Informa samples; + and live integration assertions for `00445790` and `03610056`, including the + identity-verification and corporate-identification shapes. +- Verified: `dotnet build CompaniesHouse.slnx -c Release` with 0 errors; + `dotnet test tests\CompaniesHouse.Tests\CompaniesHouse.Tests.csproj -c Release` + passing; `dotnet test tests\CompaniesHouse.ScenarioTests\CompaniesHouse.ScenarioTests.csproj -c Release` + passing; `dotnet test tests\CompaniesHouse.SourceGenerator.Tests\CompaniesHouse.SourceGenerator.Tests.csproj -c Release` + passing 28/28 after the enum-map additions; `dotnet test tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests\CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj -c Release` + passing; whitespace formatting clean on all touched files via + `dotnet format whitespace --verify-no-changes`; the new officers integration + tests passing 3/3 against the real API; and repeated live + `CompaniesHouseClient.GetOfficersAsync` / `GetOfficerByAppointmentIdAsync` + calls deserializing the confirmed payloads without throwing. diff --git a/.plans/completed/09a-registered-office-address.md b/.plans/completed/09a-registered-office-address.md new file mode 100644 index 0000000..ee51a60 --- /dev/null +++ b/.plans/completed/09a-registered-office-address.md @@ -0,0 +1,57 @@ +# 09a — Endpoint: Registered office address + +**Status:** complete +**Depends on:** `01-core`, `07-company-profile` +**Blocks:** nothing + +## Goal + +Rebuild the registered office address endpoint from the current docs and repeated live payloads. + +Docs: +- + +Path: +- `GET /company/{companyNumber}/registered-office-address` + +## Scope + +### Client +- `ICompaniesHouseRegisteredOfficeAddressClient.GetRegisteredOfficeAddress(string companyNumber, CancellationToken)` hung off `CompaniesHouseClient` and DI. +- Keep `RegisteredOfficeAddressUriBuilder`. + +### Response model +- Faithful address shape with `kind`, `etag`, `links.self` and nullable address lines. +- Preserve live `country` as a raw string, not an enum, because real payloads include values such as `South Africa`. + +## Tasks + +- [x] Confirm live payloads for UK and foreign companies. +- [x] Remove enum assumptions from `country`. +- [x] Expose the sub-client publicly and register it in DI. +- [x] Add unit, scenario and integration coverage. + +## Open questions + +- None after live verification. + +## Acceptance criteria + +- UK and foreign registered-office payloads deserialize without enum failures. +- The sub-client resolves from `CompaniesHouseClient` and DI. + +## References + +- Existing master implementation to replicate/modernise: + - `src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs` + - `src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs` + - `src/CompaniesHouse/UriBuilders/RegisteredOfficeAddressUriBuilder.cs` + - `src/CompaniesHouse/Response/RegisteredOfficeAddress/OfficeAddress.cs` + - `src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs` + +## Delivered + +- Verified live payloads for `00445790`, `FC040879` and `13507518` and rebuilt the model around the observed nullable contract. +- Replaced the old `OfficeAddressCountry` wire enum with `string?` after confirming real API values are open-ended. +- Made `ICompaniesHouseRegisteredOfficeAddressClient` public, added it to `ICompaniesHouseClient`, and registered it in the DI extension package. +- Added client/unit coverage for captured live JSON, scenario deserialization coverage, DI resolution assertions, and integration assertions against the real API. diff --git a/.plans/completed/09b-filing-history.md b/.plans/completed/09b-filing-history.md new file mode 100644 index 0000000..d51a251 --- /dev/null +++ b/.plans/completed/09b-filing-history.md @@ -0,0 +1,64 @@ +# 09b — Endpoint: Filing history + +**Status:** complete +**Depends on:** `01-core`, `03-value-types` +**Blocks:** nothing + +## Goal + +Rebuild filing-history list and single-item endpoints against live Companies House payloads. + +Docs: +- +- + +Paths: +- `GET /company/{companyNumber}/filing-history` +- `GET /company/{companyNumber}/filing-history/{transactionId}` + +## Scope + +### Client +- `ICompaniesHouseCompanyFilingHistoryClient` list + single-item methods. +- Keep `CompanyFilingHistoryUriBuilder`. + +### Response model +- Full filing-history envelope including paging fields and `links.self`. +- Filing items with `action_date`, `links.document_metadata`, annotations, associated filings and resolutions. +- Replace legacy wire enums with generated string-backed value types for filing category/status/subcategory/resolution category. +- Support `subcategory` arriving as either a single string or an array. + +## Tasks + +- [x] Validate multiple live companies and single transactions. +- [x] Migrate filing wire enums to generated value types. +- [x] Harden single-or-array subcategory deserialization. +- [x] Add unit, scenario, generator and integration coverage. + +## Open questions + +- None after live verification. + +## Acceptance criteria + +- Filing history list and single-item payloads deserialize from real API responses. +- Unknown filing category/subcategory values round-trip without throwing. + +## References + +- Existing master implementation to replicate/modernise: + - `src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs` + - `src/CompaniesHouse/ICompaniesHouseCompanyFilingHistoryClient.cs` + - `src/CompaniesHouse/UriBuilders/CompanyFilingHistoryUriBuilder.cs` + - `src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs` + - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs` + - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs` + - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs` + - `src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs` + +## Delivered + +- Verified live list and single-item payloads for `00445790`, `00002065` and `SC171417`, including mortgage filings and document links. +- Migrated the old filing-history wire enums onto generated string-backed value types backed by new enum-map entries and generator overlay YAML. +- Updated `EnumArrayOrSingleJsonConverterFactory` so generated value-type arrays can deserialize from either a single string or an array, matching live `subcategory` payloads. +- Added client/unit tests, scenario deserialization coverage, value-type round-trip tests, generator inputs, and real-API integration assertions for list and single-item calls. diff --git a/.plans/completed/09c-officer-appointments-list.md b/.plans/completed/09c-officer-appointments-list.md new file mode 100644 index 0000000..f8e712b --- /dev/null +++ b/.plans/completed/09c-officer-appointments-list.md @@ -0,0 +1,56 @@ +# 09c — Endpoint: Officer appointments list + +**Status:** complete +**Depends on:** `01-core`, `03-value-types`, `08-officers` +**Blocks:** nothing + +## Goal + +Modernise the officer appointments list endpoint and verify it against live natural-person and corporate-officer payloads. + +Docs: +- + +Path: +- `GET /officers/{officerId}/appointments` + +## Scope + +### Client +- `ICompaniesHouseAppointmentsClient.GetAppointmentsAsync(...)` hung off `CompaniesHouseClient`. +- Add a dedicated `AppointmentsUriBuilder` rather than inlined string concatenation. + +### Response model +- Envelope counts, paging fields, `kind`, `name`, `links.self`, `is_corporate_officer` and `date_of_birth`. +- Appointment items with `links.company`, `identification`, `is_pre_1992_appointment` and generated `CompanyStatus` / `OfficerRole` values. + +## Tasks + +- [x] Validate live natural-person and corporate-officer appointment lists. +- [x] Move URI construction into a dedicated builder. +- [x] Expand the response envelope and item models to match live payloads. +- [x] Add unit, scenario and integration coverage. + +## Open questions + +- None after live verification. + +## Acceptance criteria + +- Natural and corporate appointment lists deserialize fully from the live API. +- URI construction follows the standard builder pattern. + +## References + +- Existing master implementation to replicate/modernise: + - `src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs` + - `src/CompaniesHouse/ICompaniesHouseAppointmentsClient.cs` + - `src/CompaniesHouse/Response/Appointments/Appointments.cs` + - `src/CompaniesHouse/Response/Appointments/Appointment.cs` + +## Delivered + +- Verified real officer appointment payloads for `uQNQ-blSo-8PiOaehWClTPmbZNI` and `YwIOmduyS6PW5axJgQQrsTGyRD0`. +- Introduced `IAppointmentsUriBuilder` / `AppointmentsUriBuilder` and updated the client to use the shared URI-builder pattern. +- Expanded the appointments envelope and item models with the observed counts, links, identification and corporate-officer fields. +- Added dedicated client/unit tests, URI-builder tests, scenario deserialization coverage and integration assertions for both officer shapes. diff --git a/.plans/completed/09d-persons-with-significant-control-list.md b/.plans/completed/09d-persons-with-significant-control-list.md new file mode 100644 index 0000000..a6b458f --- /dev/null +++ b/.plans/completed/09d-persons-with-significant-control-list.md @@ -0,0 +1,59 @@ +# 09d — Endpoint: Persons with significant control list + +**Status:** complete +**Depends on:** `01-core`, `03-value-types`, `07-company-profile` +**Blocks:** `09j-psc-detail-types` + +## Goal + +Rebuild the existing PSC list endpoint against live payloads and align it with generated value types. + +Docs: +- + +Path: +- `GET /company/{companyNumber}/persons-with-significant-control` + +## Scope + +### Client +- `ICompaniesHousePersonsWithSignificantControlClient.GetPersonsWithSignificantControlAsync(...)`. +- Keep `PersonsWithSignificantControlBuilder`. + +### Response model +- Full envelope with paging fields, `links.self`, `active_count`, `ceased_count` and `total_results`. +- Items for individuals and corporate entities, including `ceased`, `ceased_on`, identification, links and generated PSC kind/nature-of-control value types. + +## Tasks + +- [x] Validate companies with different live PSC shapes. +- [x] Migrate PSC kind and nature-of-control wire enums to generated value types. +- [x] Fill the envelope/item schema gaps found in live responses. +- [x] Add unit, scenario, generator and integration coverage. + +## Open questions + +- PSC statements and detail endpoints remain for `09j`. + +## Acceptance criteria + +- Live PSC list payloads deserialize for both corporate and individual records. +- Unknown PSC kind/nature values do not throw. + +## References + +- Existing master implementation to replicate/modernise: + - `src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs` + - `src/CompaniesHouse/ICompaniesHousePersonsWithSignificantControlClient.cs` + - `src/CompaniesHouse/UriBuilders/PersonsWithSignificantControlBuilder.cs` + - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs` + - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs` + - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs` + - `src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs` + +## Delivered + +- Verified repeated live PSC list payloads across companies including `03977902`, `03610056`, `09965459`, `06768813` and `07560766`. +- Migrated PSC kind and nature-of-control from hand-written wire enums to generated string-backed value types backed by new enum-map and overlay YAML entries. +- Expanded the list envelope and PSC item models with the missing paging, link, identification and ceased-state fields seen in the real API. +- Added client/unit tests, scenario deserialization coverage, value-type tests and real-API integration assertions for the list endpoint. diff --git a/.plans/completed/09e-charges.md b/.plans/completed/09e-charges.md new file mode 100644 index 0000000..7b85cd2 --- /dev/null +++ b/.plans/completed/09e-charges.md @@ -0,0 +1,60 @@ +# 09e — Endpoint: Charges + +**Status:** complete +**Depends on:** `01-core`, `03-value-types` +**Blocks:** nothing + +## Goal + +Rebuild company charges list and single-charge endpoints from live payloads and modernise the old wire-enum model. + +Docs: +- +- + +Paths: +- `GET /company/{companyNumber}/charges` +- `GET /company/{companyNumber}/charges/{chargeId}` + +## Scope + +### Client +- `ICompaniesHouseChargesClient` list + single methods. +- Keep `ChargesUriBuilder`. + +### Response model +- Envelope fields including `etag`, `unfiltered_count`, `satisfied_count` and `part_satisfied_count`. +- Charge item/detail fields including classification, particulars, secured details, transactions, insolvency cases and links. +- Generated value types for charge status, classification type, particulars type, secured-details type and assets-ceased/released. + +## Tasks + +- [x] Validate multiple live charge lists and single charges. +- [x] Fix JSON property-name mismatches and nullable gaps. +- [x] Migrate legacy charge wire enums to generated value types. +- [x] Add unit, scenario, generator and integration coverage. + +## Open questions + +- None after live verification. + +## Acceptance criteria + +- Charges list and single-charge payloads deserialize from the live API. +- Unknown charge enum-ish values do not throw. + +## References + +- Existing master implementation to replicate/modernise: + - `src/CompaniesHouse/CompaniesHouseChargesClient.cs` + - `src/CompaniesHouse/ICompaniesHouseChargesClient.cs` + - `src/CompaniesHouse/UriBuilders/ChargesUriBuilder.cs` + - `src/CompaniesHouse/Response/Charges/Charges.cs` + - `src/CompaniesHouse/Response/Charges/Charge.cs` + +## Delivered + +- Verified real charge payloads for `03977902` and `00002065`, including list and single-charge calls. +- Corrected the legacy schema mismatches (`etag`, `unfiltered_count`, nullable nested shapes) and aligned the charge models with the observed API contract. +- Migrated the old charge wire enums to generated string-backed value types using new enum-map entries and overlay YAML inputs. +- Added client/unit tests, URI-builder tests, scenario deserialization coverage, value-type tests and integration assertions for both charge endpoints. diff --git a/.plans/completed/09f-insolvency.md b/.plans/completed/09f-insolvency.md new file mode 100644 index 0000000..cfd4f66 --- /dev/null +++ b/.plans/completed/09f-insolvency.md @@ -0,0 +1,61 @@ +# 09f — Endpoint: Insolvency + +**Status:** complete +**Depends on:** `01-core`, `03-value-types` +**Blocks:** nothing + +## Goal + +Modernise the insolvency endpoint against real API responses and move its wire enums onto generated value types. + +Docs: +- + +Path: +- `GET /company/{companyNumber}/insolvency` + +## Scope + +### Client +- `ICompaniesHouseCompanyInsolvencyInformationClient.GetCompanyInsolvencyInformationAsync(...)`. +- Add a dedicated `CompanyInsolvencyInformationUriBuilder`. + +### Response model +- `status[]`, `cases[]`, case dates, practitioners, addresses and links. +- Generated value types for insolvency status, case-date type and case type. +- Nullable handling that matches observed live payloads. + +## Tasks + +- [x] Validate multiple live insolvency payloads. +- [x] Move URI construction into a builder. +- [x] Migrate insolvency wire enums to generated value types. +- [x] Add unit, scenario, generator and integration coverage. + +## Open questions + +- None after live verification. + +## Acceptance criteria + +- Insolvency payloads deserialize from real Companies House responses. +- Unknown case/status/date-type values do not throw. + +## References + +- Existing master implementation to replicate/modernise: + - `src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs` + - `src/CompaniesHouse/ICompaniesHouseCompanyInsolvencyInformationClient.cs` + - `src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs` + - `src/CompaniesHouse/Response/Insolvency/Case.cs` + - `src/CompaniesHouse/Response/Insolvency/CaseDate.cs` + - `src/CompaniesHouse/Response/Insolvency/Practitioner.cs` + - `src/CompaniesHouse/Response/Insolvency/Address.cs` + - `src/CompaniesHouse/Response/Insolvency/Links.cs` + +## Delivered + +- Verified live insolvency payloads for `08749409` and `07560766` and rebuilt the models around the observed nullable shapes. +- Introduced `ICompanyInsolvencyInformationUriBuilder` / `CompanyInsolvencyInformationUriBuilder` and updated the client to use the shared URI-builder pattern. +- Migrated insolvency status, case-date type and case type from hand-written wire enums/strings to generated string-backed value types. +- Added client/unit tests, URI-builder tests, scenario deserialization coverage, value-type tests and real-API integration assertions. diff --git a/.plans/completed/09g-documents.md b/.plans/completed/09g-documents.md new file mode 100644 index 0000000..123447c --- /dev/null +++ b/.plans/completed/09g-documents.md @@ -0,0 +1,64 @@ +# 09g — Endpoint: Documents + +**Status:** complete +**Depends on:** `01-core` +**Blocks:** nothing + +## Goal + +Verify the Document API metadata and download endpoints, including the separate host and content-download behavior. + +Docs: +- +- + +Paths: +- `GET /document/{documentId}` (metadata, via the document API host) +- `GET /document/{documentId}/content` + +## Scope + +### Client +- `ICompaniesHouseDocumentMetadataClient` and `ICompaniesHouseDocumentDownloadClient` exposed through `ICompaniesHouseDocumentClient` / `CompaniesHouseClient`. +- Keep dedicated metadata/content URI builders. + +### Response model +- Metadata with `filename`, `created_at`, nullable `significant_date`, `links`, and resource content lengths large enough for real files. +- Download handling that preserves content headers and stream length. + +## Tasks + +- [x] Validate real metadata responses and at least one live content download. +- [x] Fix metadata field types and nullability based on the real API. +- [x] Add unit, scenario and integration coverage. +- [x] Verify URI/host construction for the separate document API host. + +## Open questions + +- None after live verification. + +## Acceptance criteria + +- Document metadata and download calls succeed against the live API. +- Metadata fields match the observed real payload types. + +## References + +- Existing master implementation to replicate/modernise: + - `src/CompaniesHouse/CompaniesHouseDocumentClient.cs` + - `src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs` + - `src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs` + - `src/CompaniesHouse/ICompaniesHouseDocumentClient.cs` + - `src/CompaniesHouse/ICompaniesHouseDocumentMetadataClient.cs` + - `src/CompaniesHouse/ICompaniesHouseDocumentDownloadClient.cs` + - `src/CompaniesHouse/UriBuilders/DocumentMetadataUriBuilder.cs` + - `src/CompaniesHouse/UriBuilders/DocumentContentUriBuilder.cs` + - `src/CompaniesHouse/Response/Document/DocumentMetadata.cs` + - `src/CompaniesHouse/Response/Document/DocumentDownload.cs` + +## Delivered + +- Verified live metadata responses for filing-history documents and confirmed live content download behavior against the document API host. +- Updated document metadata types to match real payloads: `CreatedAt` and `SignificantDate` are typed dates, `Filename` is modelled explicitly, and resource content lengths now support large values. +- Added URI-builder tests, client/unit tests, scenario deserialization coverage and live integration assertions for both metadata and content download. +- Confirmed that document download succeeds without forcing an `Accept: application/json` header, which the live endpoint rejects with `406`. 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/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/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/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/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/completed/10-testing-strategy.md b/.plans/completed/10-testing-strategy.md new file mode 100644 index 0000000..472aa4c --- /dev/null +++ b/.plans/completed/10-testing-strategy.md @@ -0,0 +1,131 @@ +# 10 — Testing strategy + +**Status:** complete +**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`. + +## Delivered + +Most of this plan's tasks were already satisfied incrementally by plans +`00`/`04`/`06`/`07`/`08`/`09a`-`09g` (xUnit + Shouldly test stack, nullable +enabled, `net10.0`-targeted test projects, no `Newtonsoft.Json` anywhere in +`tests/`, value-type unit tests with unknown-value coverage, real-payload +scenario tests per endpoint). This pass closed the remaining gaps: + +- **Integration tests now skip cleanly without `COMPANIES_HOUSE_API_KEY`** + (previously they'd fail with auth/deserialization errors). Added + `IntegrationFactAttribute`/`IntegrationTheoryAttribute` + (`tests/CompaniesHouse.IntegrationTests/IntegrationFactAttribute.cs`, + `IntegrationTheoryAttribute.cs`) which set `Skip` at attribute-construction + time when `Keys.ApiKeyOrNull` is null/empty, and applied them to every + `[Fact]`/`[Theory]` across the whole `CompaniesHouse.IntegrationTests` + project. Verified: 73/73 pass with the key present, all tests skip cleanly + (0 failed) with the env var unset. +- **Generator snapshot test** added + (`tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs`) + - asserts the *entire* generated value-type + JSON-converter source text + for a representative enum group against a fixed expected string, on top + of the existing spot-check (`ShouldContain`) assertions in + `EnumValueTypeGeneratorTests`. No new snapshot-testing package dependency + was introduced (a plain Shouldly `ShouldBe` on the full source string is + sufficient and keeps the dependency footprint the same). +- Confirmed already-satisfied items via direct inspection rather than + re-doing them: `Directory.Build.props` enables `Nullable`/`ImplicitUsings` + repo-wide; no `Newtonsoft` references anywhere under `tests/`; 429/`RetryAfter` + and non-2xx/server-error transport behaviour already has dedicated coverage + in `HttpResponseMessageExtensionsTests`; every endpoint shipped so far + (search, company profile, officers, and the plan `09a`-`09g` catalogue) + already carries URI-builder + scenario + integration coverage as part of + its own plan. + +Verification: `dotnet build CompaniesHouse.slnx -c Release` (0 errors), +`CompaniesHouse.Tests` 458/458, `ScenarioTests` 20/20, `SourceGenerator.Tests` +29/29, DI tests 6/6, `IntegrationTests` 73/73 with a key / all skip cleanly +without one, `dotnet format --verify-no-changes` scoped to touched files +(pre-existing unrelated whitespace issues on untouched lines in a handful of +older integration test files were left as-is, consistent with not fixing +unrelated pre-existing issues). diff --git a/.plans/outstanding/09-endpoint-catalogue-remaining.md b/.plans/outstanding/09-endpoint-catalogue-remaining.md new file mode 100644 index 0000000..f1fcaaa --- /dev/null +++ b/.plans/outstanding/09-endpoint-catalogue-remaining.md @@ -0,0 +1,23 @@ +# 09 — Remaining endpoint catalogue (split index) + +**Status:** split +**Depends on:** `01-core`, `03-value-types` +**Blocks:** nothing + +## 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` +- `..\completed\09i-disqualified-officers-detail.md` +- `..\completed\09j-psc-detail-types.md` +- `..\completed\09k-exemptions.md` +- `..\completed\09l-uk-establishments.md` + +Still outstanding: diff --git a/.plans/outstanding/11-docs-samples-migration.md b/.plans/outstanding/11-docs-samples-migration.md new file mode 100644 index 0000000..5427551 --- /dev/null +++ b/.plans/outstanding/11-docs-samples-migration.md @@ -0,0 +1,74 @@ +# 11 — Docs, samples & migration guide + +**Status:** in progress - README, sample project and MIGRATION.md rewritten +for the endpoints landed so far (search, company profile, officers, +appointments, filing history, insolvency, PSC, charges, registered office +address, documents). Still needs a pass once the remaining endpoints in +`09h`-`09l` land (registers, disqualified officer detail, PSC detail types, +exemptions, UK establishments), and a final cross-check near release. +**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 + +- [x] Rewrite README for v-next (progressive, per-endpoint) - done for all + endpoints landed so far; add a snippet for each of `09h`-`09l` as they land. +- [x] Update the sample project (direct construction, DI, search, company + profile, officers, graceful unknown-enum handling). +- [x] Write the migration guide with before/after snippets (`MIGRATION.md`). +- [x] Fix stale badges/links (AppVeyor → GitHub Actions badge, developer + portal link, base URI). No stale copyright text was present in the README. +- [ ] 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/12-response-discriminated-union.md b/.plans/outstanding/12-response-discriminated-union.md new file mode 100644 index 0000000..8508e32 --- /dev/null +++ b/.plans/outstanding/12-response-discriminated-union.md @@ -0,0 +1,295 @@ +# 12 — Discriminated union response type (issue #189) + +**Status:** outstanding +**Depends on:** `01-core-client-architecture` (complete) +**Blocks:** nothing — but improves ergonomics for all endpoint consumers + +## Goal + +Replace the flat `CompaniesHouseClientResponse` (with a nullable `Data` and +an `IsSuccess` flag callers must remember to check) with a proper **discriminated +union** — an abstract base type `CompaniesHouseResponse` whose sealed nested +subtypes represent every distinct HTTP outcome the API produces. Consumers +pattern-match on the concrete type; the compiler guides them rather than silent +null-reference bugs. + +This directly implements the original intent of issue #189 ("a base class … +switching based on the concrete class") and supersedes the interim shape shipped +in plan `01`. + +## Why + +The current flat class has two problems: + +1. **`Data` is always nullable.** Even on success, callers must write + `if (response.IsSuccess && response.Data is not null)`. There is nothing + stopping them from reading `response.Data` on a 404 and getting `null` + silently. +2. **Semantics are collapsed.** A 404, a 429, and a 401 are three very different + situations with different recovery paths. Today they all look the same to the + compiler: `IsSuccess == false`. Callers must remember to inspect `StatusCode` + themselves. + +A sealed type hierarchy solves both: `Success.Data` is always non-null (no `?`), +and the switch/is-pattern forces the caller to reason about each outcome. + +## Proposed shape + +```csharp +/// +/// 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 constructor — no external subclassing. + 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})."); + + // ─── Subtypes ──────────────────────────────────────────────────────────── + + /// 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 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; } + } +} +``` + +All HTTP-level outcomes — including 5xx — are returned as subtypes. Genuine +transport failures (network errors, DNS, timeout) still surface as +`HttpRequestException` from the underlying `HttpClient` and are not caught by +this library. + +### How consumers use this + +**Simple happy path** — just grab `.Data` and let it throw on failure: + +```csharp +var company = (await client.GetCompanyProfileAsync("12345678")).Data; +Console.WriteLine(company.CompanyName); +``` + +**Full branching** — pattern match when you need to handle each outcome: + +switch (response) +{ + 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; + + default: + Console.WriteLine($"Unexpected response: {response.StatusCode}"); + break; +} +``` + +### Decision: keep or rename the type? + +`CompaniesHouseClientResponse` → `CompaniesHouseResponse`. + +The `Client` infix adds no value and the shorter name reads more naturally as a +return type. This is a deliberate breaking-change rename. + +## Scope + +### Breaking changes (expected — new major version) + +- `CompaniesHouseClientResponse` removed and replaced by + `CompaniesHouseResponse` with the subtype hierarchy above. +- All sub-client interfaces change from + `Task>` to `Task>`. +- All sub-client implementations updated. +- `HttpResponseMessageExtensions.ToCompaniesHouseClientResponseAsync` renamed + and updated to return the appropriate subtype. + +### In-scope + +- New `CompaniesHouseResponse` abstract class with **six** sealed subtypes + (`Success`, `NotFound`, `RateLimited`, `Unauthorized`, `ClientError`, + `ServerError`). +- Update `HttpResponseMessageExtensions` pipeline to build the correct subtype + from the `HttpResponseMessage`. +- Update every `ICompaniesHouseX` interface and implementation to use the new + return type. +- Tests: unit tests for each subtype (factory method / pipeline), plus scenario + tests verifying that a real 404 returns `NotFound`, a real success returns + `Success`, and so on. + +### Out of scope + +- No changes to request models, URI builders, or the `CompaniesHouseSettings` + hierarchy. + +## Tasks + +- [ ] Define `CompaniesHouseResponse` abstract class with the **six** subtypes in + `src/CompaniesHouse/CompaniesHouseResponse.cs`. Delete + `CompaniesHouseClientResponse.cs` and `CompaniesHouseApiException.cs`. +- [ ] Update `HttpResponseMessageExtensions.ToCompaniesHouseResponseAsync` to + classify the response: + - 2xx → `Success` (deserialize body, expose full `Headers`) + - 404 → `NotFound` + - 429 → `RateLimited` (parse `Retry-After`) + - 401/403 → `Unauthorized` + - 5xx → `ServerError` (parse `Retry-After` — e.g. 503) + - other 4xx → `ClientError` +- [ ] Update every `ICompaniesHouseX` interface return type. +- [ ] Update every sub-client implementation return type (callers of the pipeline + need no other changes since the extension method does the heavy lifting). +- [ ] Update unit tests in `CompaniesHouse.Tests` for the new type/subtypes. +- [ ] Add scenario-level tests: + - Valid company number → `Success` with non-null `Data`. + - Nonexistent company number → `NotFound`. + - Confirm `RateLimited` surfaces `RetryAfter` (may need a mock/stub if + hitting the real API isn't reliable here). + - Confirm `ServerError` surfaces `RetryAfter` for a mocked 503. +- [ ] Update the sample project (`samples/SampleProject`) to use pattern matching + on the new type. +- [ ] Update `99-recurring-issues-backlog.md`: mark the #189 / #181 / #182 + response-ergonomics row as resolved. + +## Design decisions + +- **`.Data` on the base throws for non-success** — provides a one-liner for the + happy path (`response.Data`) while making the failure explicit via + `InvalidOperationException` rather than silently returning null. Callers who + need to branch use pattern matching; callers who expect success use `.Data` + directly. +- **Private base constructor** — prevents external subclassing; the compiler + knows the hierarchy is closed (exhaustiveness via `default` in switch). +- **`T Data` not `T? Data`** — `Success.Data` is non-nullable; callers get a + compile-time guarantee that Data is present on success. +- **5xx returns `ServerError`, not throws** — 5xx responses can carry a + `Retry-After` header (e.g. 503) and callers may have valid recovery logic. + Returning a type keeps all HTTP-level outcomes consistent. Genuine transport + failures (`HttpRequestException`) still propagate naturally from `HttpClient`. +- **`CompaniesHouseApiException` removed** — all HTTP-level errors are now + represented as subtypes; the exception class is no longer needed. +- **`Success` exposes full `HttpResponseHeaders`** — raw headers are surfaced + on `Success` so callers can inspect anything the API returns; parsed + well-known values (`RetryAfter`) are exposed as typed properties on the + relevant error subtypes. +- **No non-generic `CompaniesHouseResponse` base** — callers always work with + the typed `CompaniesHouseResponse` variant; a non-generic base would add a + layer with no tangible benefit. +- **`CompaniesHouseResponse` not `Result`** — domain-namespaced name is + clearer to consumers unfamiliar with result-type patterns. + +## Acceptance criteria + +- All existing tests pass with updated type names. +- `CompaniesHouseClientResponse` and `CompaniesHouseApiException` do not + exist anywhere in the solution. +- A switch over `CompaniesHouseResponse` without a `default` + branch produces a compiler warning (exhaustiveness via sealed hierarchy). +- Scenario tests confirm: 404 → `NotFound`, 2xx → `Success` with non-null + `Data`, mocked 503 → `ServerError` with `RetryAfter` populated. + +## References + +- Issue #189 — "Ideas for changing the CompaniesHouseClientResponse class" +- Issues #181/#182 — include StatusCode, ReasonPhrase, RetryAfter on failures +- Plan `01` — core architecture (response wrapper originally introduced here) 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..8c7862f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,159 @@ +# 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 +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. +- **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: + +## 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 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..8084acf --- /dev/null +++ b/CompaniesHouse.slnx @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Directory.Build.props b/Directory.Build.props index 221bfca..8f4969c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,13 +3,18 @@ true latest enable + enable true + + $(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 +25,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..a8734d2 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,35 @@ + + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dockerfile b/Dockerfile index b8001b1..583df9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,25 @@ +# 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:9.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 . 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 @@ -28,11 +31,14 @@ ARG NUGET_PACKAGE_VERSION COPY ./src/ ./src/ COPY ./tests/ ./tests/ COPY ./samples/ ./samples/ -RUN dotnet build --configuration $CONFIGURATION +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; exit 0 +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 RUN mkdir -p artifacts diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..5d68415 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,219 @@ +# 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. + +## Response type: discriminated union + +**Before:** the response wrapper exposed only the deserialized body: + +```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); +- 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 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}", ++ }; +``` + +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 + +**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 c515aa5..58aaf69 100644 --- a/README.md +++ b/README.md @@ -1,175 +1,330 @@ # 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 an API key, which can be created via the [CompaniesHouse API website](https://developer.company-information.service.gov.uk/manage-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. -For example if we wanted to use the `ICompaniesHouseClient` which is the main facade interface, we could inject this in to our page model. +### `IConfiguration` example + +```json +{ + "CompaniesHouse": { + "ApiKey": "your-api-key", + "BaseUri": "https://api.company-information.service.gov.uk/" + } +} +``` ```csharp -public class MyPageModel : PageModel +services.AddCompaniesHouseClient(builder.Configuration); +``` + +## Enum/value-type handling + +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 +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 `CompaniesHouseResponse` — a discriminated +union whose concrete subtype tells you exactly what happened: + +| 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`. + +### 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); ``` -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`. +### Full branching + +Use a switch expression when you need to handle specific outcomes: + +```csharp +var result = await client.GetCompanyProfileAsync(companyNumber); + +var message = result switch +{ + CompaniesHouseResponse.Success { Data: var company } => + $"Found: {company.CompanyName}", + + CompaniesHouseResponse.NotFound => + "Company not found.", + + CompaniesHouseResponse.RateLimited { RetryAfter: var delay } => + $"Rate limited — retry after {delay}.", + + CompaniesHouseResponse.Unauthorized => + "Check your API key.", + + CompaniesHouseResponse.ServerError { StatusCode: var code, RetryAfter: var delay } => + $"Server error {code} — retry after {delay}.", + + _ => $"Unexpected response: {result.StatusCode}", +}; +``` ## 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. - -### Getting company officer list +`result` is a `NotFound` subtype if there was no match for that company number. -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` is a `NotFound` subtype 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. +`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. + +## 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 +332,24 @@ 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. + +## 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 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/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/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/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/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/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 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/Program.cs b/samples/SampleProject/Program.cs index b2c56e7..43aa2ab 100644 --- a/samples/SampleProject/Program.cs +++ b/samples/SampleProject/Program.cs @@ -1,71 +1,155 @@ -using CompaniesHouse; +using CompaniesHouse; using CompaniesHouse.Request; -using CompaniesHouse.Response.Search.OfficerSearch; +using CompaniesHouse.Response; +using CompaniesHouse.Response.CompanyProfile; +using CompaniesHouse.Response.Search.AllSearch; using CompaniesHouse.Response.Search.CompanySearch; using CompaniesHouse.Response.Search.DisqualifiedOfficersSearch; -using System; -using System.Linq; -using System.Threading.Tasks; -using CompaniesHouse.Response.Search.AllSearch; +using CompaniesHouse.Response.Search.OfficerSearch; +using Microsoft.Extensions.DependencyInjection; +using Officers = CompaniesHouse.Response.Officers.Officers; + +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 searchResult = await client.SearchAllAsync(new SearchAllRequest { + Query = nameToSearchFor, + StartIndex = 0, + ItemsPerPage = 10 + }); + + DisplaySearchResults(searchResult, nameToSearchFor); - //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 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.). + /// + private static async Task RunWithDependencyInjectionAsync(string companyNumber) + { + var services = new ServiceCollection(); + services.AddCompaniesHouseClient(ApiKey); + await using var provider = services.BuildServiceProvider(); + + var client = provider.GetRequiredService(); + + var result = await client.GetCompanyProfileAsync(companyNumber); + DisplayCompanyProfile(result, companyNumber); + } + + private static void DisplaySearchResults(CompaniesHouseResponse result, string query) + { + Console.WriteLine($"\n----------------------------------------------"); + + // .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($"Search failed (HTTP {result.StatusCode})."); + return; } + + Console.WriteLine($"Companies matching '{query}':"); + 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. + 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, string companyNumber) + { + Console.WriteLine($"\n----------------------------------------------"); + Console.WriteLine($"Officers for {companyNumber}:"); + + if (result is not CompaniesHouseResponse.Success { Data: var data }) + { + 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, string companyNumber) + { + Console.WriteLine($"\n----------------------------------------------"); + + // Switch expression — the compiler guides you through every outcome. + var summary = result switch + { + 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); + } + + /// + /// 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.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 f5aafc9..bc3e02d 100644 --- a/samples/SampleProject/SampleProject.csproj +++ b/samples/SampleProject/SampleProject.csproj @@ -1,12 +1,17 @@ Exe - net7.0 + net10.0 false + + + + + 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.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj index 7faeb94..b60ae5b 100644 --- a/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj +++ b/src/CompaniesHouse.Extensions.Microsoft.DependencyInjection/CompaniesHouse.Extensions.Microsoft.DependencyInjection.csproj @@ -1,29 +1,37 @@ - netstandard2.0 + net8.0;net9.0;net10.0 true snupkg + true + README.md The CompaniesHouse extensions for ASP.NET Core - Copyright © Kevsoft 2020 + - - + + + + + + + + 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 f166c40..5633e79 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,7 +27,7 @@ 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); } /// @@ -32,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; }); } @@ -44,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); } /// @@ -56,60 +71,296 @@ 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.TryAddSingleton(provider => - { - var options = new CompaniesHouseClientOptions(); - configure.Invoke(provider, options); - return options; - }); + 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.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()); - 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.GetRequiredService()); + services.TryAddTransient(provider => + provider.GetRequiredService()); + services.TryAddTransient(provider => + 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()); - services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); + services.TryAddTransient( + provider => provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); services.TryAddTransient(provider => - provider.GetService()); + provider.GetRequiredService()); + services.TryAddTransient(provider => + 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)); + 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/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..9492077 --- /dev/null +++ b/src/CompaniesHouse.SourceGenerator/ValueTypeEmitter.cs @@ -0,0 +1,178 @@ +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 (memberName, _) in members) + { + 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) + { + // 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(" };"); + } + + 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 532c91d..4a179d4 100644 --- a/src/CompaniesHouse/CompaniesHouse.csproj +++ b/src/CompaniesHouse/CompaniesHouse.csproj @@ -1,19 +1,16 @@  - netstandard1.1;netstandard2.0;net45 + net8.0;net9.0;net10.0 true snupkg + true + README.md - - - - - CompaniesHouse.NET CompaniesHouse.NET @@ -21,18 +18,33 @@ A simple .NET API client wrapper for CompaniesHouse - + + - - + + + + + + + - + + + diff --git a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs index aca17e8..fe64f29 100644 --- a/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs +++ b/src/CompaniesHouse/CompaniesHouseAppointmentsClient.cs @@ -1,7 +1,8 @@ -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; using CompaniesHouse.Response.Appointments; +using CompaniesHouse.UriBuilders; namespace CompaniesHouse { @@ -10,23 +11,21 @@ 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) + 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); - response.EnsureSuccessStatusCode2(); - - var result = await response.Content.ReadAsJsonAsync().ConfigureAwait(false); - - return new CompaniesHouseClientResponse(result); + 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 cfe0c75..af25537 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; @@ -20,34 +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); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var data = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync() - : null; - - return new CompaniesHouseClientResponse(data); + 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); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var data = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync() - : null; - - return new CompaniesHouseClientResponse(data); + 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 a223c82..e515fae 100644 --- a/src/CompaniesHouse/CompaniesHouseClient.cs +++ b/src/CompaniesHouse/CompaniesHouseClient.cs @@ -1,19 +1,28 @@ -using System.Net.Http; +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Appointments; using CompaniesHouse.Response.Charges; 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; +using CompaniesHouse.Response.Registers; using CompaniesHouse.Response.RegisteredOfficeAddress; -using CompaniesHouse.Response.Search.AdvancedCompanySearch; 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.Response.UkEstablishments; using CompaniesHouse.UriBuilders; -using CompanySearch = CompaniesHouse.Response.Search.CompanySearch.CompanySearch; using Officer = CompaniesHouse.Response.Officers.Officer; namespace CompaniesHouse @@ -30,6 +39,11 @@ public class CompaniesHouseClient : ICompaniesHouseClient, IDisposable private readonly ICompaniesHouseChargesClient _companiesHouseChargesClient; private readonly ICompaniesHouseRegisteredOfficeAddressClient _companiesHouseRegisteredOfficeAddressClient; private readonly ICompaniesHouseOfficerByAppointmentClient _companiesHouseOfficerByAppointmentClient; + private readonly ICompaniesHouseRegistersClient _companiesHouseRegistersClient; + private readonly ICompaniesHouseDisqualifiedOfficerDetailsClient _companiesHouseDisqualifiedOfficerDetailsClient; + private readonly ICompaniesHousePersonsWithSignificantControlDetailsClient _companiesHousePersonsWithSignificantControlDetailsClient; + private readonly ICompaniesHouseExemptionsClient _companiesHouseExemptionsClient; + private readonly ICompaniesHouseUkEstablishmentsClient _companiesHouseUkEstablishmentsClient; private readonly HttpClient _httpClient; public CompaniesHouseClient(HttpClient httpClient) @@ -39,100 +53,198 @@ 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()); _companiesHouseOfficerByAppointmentClient = new CompaniesHouseOfficerByByAppointmentClient(_httpClient, new OfficersAppointmentUriBuilder()); + _companiesHouseRegistersClient = new CompaniesHouseRegistersClient(_httpClient, new CompanyRegistersUriBuilder()); + _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) - :this(new HttpClientFactory(settings).CreateHttpClient()) + : this(new HttpClientFactory(settings).CreateHttpClient()) { } - 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> SearchCompanyAdvancedAsync(AdvancedSearchCompanyRequest 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> GetCompanyProfileAsync(string companyNumber, 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)) + { + 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); } - 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); } - 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)) + 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> 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 _companiesHouseChargesClient.GetChargesListAsync(companyNumber,startIndex, pageSize, cancellationToken); + return _companiesHousePersonsWithSignificantControlDetailsClient.GetLegalPersonBeneficialOwnerAsync(companyNumber, notificationId, cancellationToken); } - public Task> GetChargeByIdAsync(string companyNumber, string chargeId, CancellationToken cancellationToken = default) + 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> GetCompanyExemptionsAsync(string companyNumber, CancellationToken cancellationToken = default) + { + 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); + } + + 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); } + public Task> GetCompanyRegistersAsync(string companyNumber, CancellationToken cancellationToken = default) + { + 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/CompaniesHouseClientResponse.cs b/src/CompaniesHouse/CompaniesHouseClientResponse.cs deleted file mode 100644 index 2b23722..0000000 --- a/src/CompaniesHouse/CompaniesHouseClientResponse.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CompaniesHouse -{ - public class CompaniesHouseClientResponse - { - public CompaniesHouseClientResponse(T data) - { - Data = data; - } - - public T Data { get; } - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs index 0a5359a..18689a4 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyFilingHistoryClient.cs @@ -19,38 +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 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.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 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.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs b/src/CompaniesHouse/CompaniesHouseCompanyInsolvencyInformationClient.cs index 4e3a764..cec78b5 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,23 +11,21 @@ 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); - - response.EnsureSuccessStatusCode2(); - var result = await response.Content.ReadAsJsonAsync().ConfigureAwait(false); - - return new CompaniesHouseClientResponse(result); + 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 dfd6a85..3dc951a 100644 --- a/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs +++ b/src/CompaniesHouse/CompaniesHouseCompanyProfileClient.cs @@ -19,21 +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 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.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file 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/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 5358bcb..ae1b8b4 100644 --- a/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs +++ b/src/CompaniesHouse/CompaniesHouseDocumentDownloadClient.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -7,8 +6,6 @@ namespace CompaniesHouse { - using CompaniesHouse.Extensions; - public class CompaniesHouseDocumentDownloadClient : ICompaniesHouseDocumentDownloadClient { private readonly HttpClient _httpClient; @@ -20,24 +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); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); + 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 ?? string.Empty + }, + 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), - var data = response.IsSuccessStatusCode - ? new DocumentDownload - { - Content = await response.Content.ReadAsStreamAsync(), - ContentLength = response.Content.Headers.ContentLength, - ContentType = response.Content.Headers.ContentType.MediaType - } - : null; + >= 500 => new CompaniesHouseResponse.ServerError( + response.Headers.RetryAfter?.Delta, + statusCode, + reasonPhrase), - return new CompaniesHouseClientResponse(data); + _ => new CompaniesHouseResponse.ClientError(statusCode, reasonPhrase), + }; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs b/src/CompaniesHouse/CompaniesHouseDocumentMetadataClient.cs index cfa48aa..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,19 +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); - 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.ToCompaniesHouseResponseAsync(caneCancellationToken).ConfigureAwait(false); } } } \ No newline at end of file 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/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..6a2254c 100644 --- a/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs +++ b/src/CompaniesHouse/CompaniesHouseOfficerByByAppointmentClient.cs @@ -19,21 +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 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.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs index 2121e73..2be1b91 100644 --- a/src/CompaniesHouse/CompaniesHouseOfficersClient.cs +++ b/src/CompaniesHouse/CompaniesHouseOfficersClient.cs @@ -19,21 +19,20 @@ 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); - // 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.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs b/src/CompaniesHouse/CompaniesHousePersonsWithSignificantControlClient.cs index 5626f98..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,22 +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 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.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } - } } 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/CompaniesHouseRegisteredOfficeAddressClient.cs b/src/CompaniesHouse/CompaniesHouseRegisteredOfficeAddressClient.cs index b47b0c9..4db9af5 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; @@ -20,19 +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); - if (response.StatusCode != HttpStatusCode.NotFound) - response.EnsureSuccessStatusCode2(); - - var data = response.IsSuccessStatusCode - ? await response.Content.ReadAsJsonAsync() - : null; - - return new CompaniesHouseClientResponse(data); + return await response.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file 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/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 1269566..4551426 100644 --- a/src/CompaniesHouse/CompaniesHouseSearchClient.cs +++ b/src/CompaniesHouse/CompaniesHouseSearchClient.cs @@ -18,21 +18,15 @@ public CompaniesHouseSearchClient(HttpClient httpClient, ISearchUriBuilderFactor _searchUriBuilderFactory = searchUriBuilderFactory; } - public async Task> SearchAsync( - TSearchRequest request, + public async Task> SearchAsync(TSearchRequest request, CancellationToken cancellationToken = default(CancellationToken)) - where TSearchRequest : SearchRequest { var searchUriBuilder = _searchUriBuilderFactory.Create(); var requestUri = searchUriBuilder.Build(request); 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.ToCompaniesHouseResponseAsync(cancellationToken).ConfigureAwait(false); } } } \ No newline at end of file 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/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 ebaf27c..ff6a05c 100644 --- a/src/CompaniesHouse/Description/DescriptionProvider.cs +++ b/src/CompaniesHouse/Description/DescriptionProvider.cs @@ -1,41 +1,46 @@ -using System.Globalization; +using System.Text.Json; using System.Text.RegularExpressions; -using Newtonsoft.Json.Linq; namespace CompaniesHouse.Description { public class DescriptionProvider { - private const string _sourceDateFormat = "yyyy-MM-dd"; private static readonly Regex _pattern = new Regex(@"({[a-zA-Z0-9.-_]*})"); - private static readonly Regex _datePattern = new Regex(@"^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$"); - public static string GetDescription(string format, JObject values, string dateFormat = null) + 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 }) { - var value = variableValue.Value(); - if (!string.IsNullOrEmpty(dateFormat) && - _datePattern.IsMatch(value)) - { - var date = DateTime.ParseExact(value, _sourceDateFormat, CultureInfo.InvariantCulture); - value = date.ToString(dateFormat); - } - - format = format.Replace(placeHolder, 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/Description/IDescriptable.cs b/src/CompaniesHouse/Description/IDescriptable.cs index 7f1f12c..1e27c2f 100644 --- a/src/CompaniesHouse/Description/IDescriptable.cs +++ b/src/CompaniesHouse/Description/IDescriptable.cs @@ -2,6 +2,6 @@ { public interface IDescriptable { - string GetDescription(string format, string dateFormat = null); + string GetDescription(string format); } } diff --git a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs index a4f0db2..fbab8ff 100644 --- a/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs +++ b/src/CompaniesHouse/Extensions/HttpResponseMessageExtensions.cs @@ -1,23 +1,51 @@ -namespace CompaniesHouse.Extensions; +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: all HTTP responses +/// are returned as a subtype. +/// Transport failures (network errors, DNS, timeout) propagate as . +/// public static class HttpResponseMessageExtensions { - public static HttpResponseMessage EnsureSuccessStatusCode2(this HttpResponseMessage responseMessage) + /// + /// Classifies the and returns the appropriate + /// subtype. + /// + public static async Task> ToCompaniesHouseResponseAsync( + this HttpResponseMessage response, CancellationToken cancellationToken = default) { - try - { - responseMessage.EnsureSuccessStatusCode(); - } - catch (HttpRequestException e) + var statusCode = (int)response.StatusCode; + var reasonPhrase = response.ReasonPhrase; + + return statusCode switch { - e.Data["StatusCode"] = (int)responseMessage.StatusCode; - e.Data["ReasonPhrase"] = responseMessage.ReasonPhrase; - e.Data["RetryAfter"] = responseMessage.Headers?.RetryAfter?.ToString(); - throw; - } + >= 200 and < 300 => new CompaniesHouseResponse.Success( + 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), + + 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 responseMessage; + _ => new CompaniesHouseResponse.ClientError(statusCode, reasonPhrase), + }; } } diff --git a/src/CompaniesHouse/HttpContentExtensions.cs b/src/CompaniesHouse/HttpContentExtensions.cs deleted file mode 100644 index d32e58e..0000000 --- a/src/CompaniesHouse/HttpContentExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace CompaniesHouse -{ - public static class HttpContentExtensions - { - public static readonly JsonSerializer Serializer = new() - { - Converters = { new StringEnumConverter() } - }; - - 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); - - return Serializer.Deserialize(reader); - } - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs b/src/CompaniesHouse/ICompaniesHouseAdvancedCompanySearchClient.cs new file mode 100644 index 0000000..8f955a5 --- /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/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/ICompaniesHouseClient.cs b/src/CompaniesHouse/ICompaniesHouseClient.cs index e6d58e0..b095156 100644 --- a/src/CompaniesHouse/ICompaniesHouseClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseClient.cs @@ -2,17 +2,25 @@ { public interface ICompaniesHouseClient : ICompaniesHouseSearchCompanyClient, - ICompaniesHouseSearchCompanyAdvancedClient, ICompaniesHouseSearchOfficerClient, ICompaniesHouseSearchDisqualifiedOfficerClient, ICompaniesHouseSearchAllClient, + ICompaniesHouseSearchCompaniesAlphabeticallyClient, + ICompaniesHouseSearchDissolvedCompaniesClient, + ICompaniesHouseAdvancedCompanySearchClient, ICompaniesHouseCompanyProfileClient, ICompaniesHouseCompanyFilingHistoryClient, ICompaniesHouseOfficersClient, ICompaniesHouseCompanyInsolvencyInformationClient, ICompaniesHouseAppointmentsClient, ICompaniesHousePersonsWithSignificantControlClient, - ICompaniesHouseChargesClient + ICompaniesHouseChargesClient, + ICompaniesHouseRegisteredOfficeAddressClient, + ICompaniesHouseRegistersClient, + ICompaniesHouseDisqualifiedOfficerDetailsClient, + ICompaniesHousePersonsWithSignificantControlDetailsClient, + ICompaniesHouseExemptionsClient, + ICompaniesHouseUkEstablishmentsClient { } 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/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/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/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/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 f8c6316..e058d28 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/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/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/ICompaniesHouseRegisteredOfficeAddressClient.cs b/src/CompaniesHouse/ICompaniesHouseRegisteredOfficeAddressClient.cs index b2a3596..04b2cef 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/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/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 d1ca17f..e9a8e9a 100644 --- a/src/CompaniesHouse/ICompaniesHouseSearchClient.cs +++ b/src/CompaniesHouse/ICompaniesHouseSearchClient.cs @@ -6,8 +6,7 @@ namespace CompaniesHouse { public interface ICompaniesHouseSearchClient { - Task> SearchAsync(TSearchRequest request, - CancellationToken cancellationToken = default(CancellationToken)) - where TSearchRequest : SearchRequest; + 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 new file mode 100644 index 0000000..383f893 --- /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/ICompaniesHouseSearchCompanyAdvancedClient.cs b/src/CompaniesHouse/ICompaniesHouseSearchCompanyAdvancedClient.cs deleted file mode 100644 index 487f39b..0000000 --- a/src/CompaniesHouse/ICompaniesHouseSearchCompanyAdvancedClient.cs +++ /dev/null @@ -1,11 +0,0 @@ -using CompaniesHouse.Request; -using CompaniesHouse.Response.Search.AdvancedCompanySearch; -using CompaniesHouse.Response.Search.CompanySearch; - -namespace CompaniesHouse; - -public interface ICompaniesHouseSearchCompanyAdvancedClient -{ - Task> SearchCompanyAdvancedAsync( - AdvancedSearchCompanyRequest request, CancellationToken cancellationToken = default(CancellationToken)); -} \ No newline at end of file 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 new file mode 100644 index 0000000..c9c4b00 --- /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/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/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/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/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs b/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs new file mode 100644 index 0000000..e473304 --- /dev/null +++ b/src/CompaniesHouse/JsonConverters/EnumArrayOrSingleJsonConverterFactory.cs @@ -0,0 +1,77 @@ +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) + { + 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) + { + var elementType = typeToConvert.GetElementType()!; + var converterType = typeof(EnumArrayOrSingleJsonConverter<>).MakeGenericType(elementType); + return (JsonConverter)Activator.CreateInstance(converterType)!; + } + + private sealed class EnumArrayOrSingleJsonConverter : JsonConverter + { + public override TElement[]? 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, TElement[] 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 e6da821..d8a49d2 100644 --- a/src/CompaniesHouse/JsonConverters/SearchItemConverter.cs +++ b/src/CompaniesHouse/JsonConverters/SearchItemConverter.cs @@ -1,37 +1,42 @@ using System; +using System.Text.Json; +using System.Text.Json.Serialization; using CompaniesHouse.Response.Search; -using CompaniesHouse.Response.Search.AdvancedCompanySearch; 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 is "searchresults#company") - { - return new Company(); - } - if (kind is "search-results#company") - { - return new AdvancedSearchedCompany(); - } - else if (kind is "searchresults#officer") - { - return new Officer(); - } - else if (kind is "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/Request/AdvancedSearchCompanyRequest.cs b/src/CompaniesHouse/Request/AdvancedCompanySearchRequest.cs similarity index 50% rename from src/CompaniesHouse/Request/AdvancedSearchCompanyRequest.cs rename to src/CompaniesHouse/Request/AdvancedCompanySearchRequest.cs index 375f458..8156edf 100644 --- a/src/CompaniesHouse/Request/AdvancedSearchCompanyRequest.cs +++ b/src/CompaniesHouse/Request/AdvancedCompanySearchRequest.cs @@ -1,21 +1,35 @@ -#nullable enable +using System; +using System.Collections.Generic; using CompaniesHouse.Response; using CompaniesHouse.Response.Search.AdvancedCompanySearch; -using CompaniesHouse.Response.Search.CompanySearch; namespace CompaniesHouse.Request; -public class AdvancedSearchCompanyRequest : SearchRequest +public class AdvancedCompanySearchRequest { public string? CompanyNameIncludes { get; set; } + public string? CompanyNameExcludes { get; set; } - public IReadOnlyCollection CompanyStatus { get; set; } = []; - public IReadOnlyCollection CompanySubtype { get; set; } = []; - public IReadOnlyCollection CompanyType { 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; } = []; -} \ No newline at end of file + + public IReadOnlyCollection? SicCodes { get; set; } + + public int? Size { get; set; } + + public int? StartIndex { get; set; } +} diff --git a/src/CompaniesHouse/Request/IQuerySearchRequest.cs b/src/CompaniesHouse/Request/IQuerySearchRequest.cs deleted file mode 100644 index a051a75..0000000 --- a/src/CompaniesHouse/Request/IQuerySearchRequest.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace CompaniesHouse.Request; - -public interface IQuerySearchRequest : ISearchRequest -{ - string Query { get; } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Request/ISearchRequest.cs b/src/CompaniesHouse/Request/ISearchRequest.cs index 759ebb1..056dba0 100644 --- a/src/CompaniesHouse/Request/ISearchRequest.cs +++ b/src/CompaniesHouse/Request/ISearchRequest.cs @@ -2,6 +2,8 @@ public interface ISearchRequest { + string Query { get; } + int? ItemsPerPage { get; } int? StartIndex { get; } diff --git a/src/CompaniesHouse/Request/SearchAllRequest.cs b/src/CompaniesHouse/Request/SearchAllRequest.cs index e1b0c67..39f1f53 100644 --- a/src/CompaniesHouse/Request/SearchAllRequest.cs +++ b/src/CompaniesHouse/Request/SearchAllRequest.cs @@ -2,6 +2,6 @@ namespace CompaniesHouse.Request; -public class SearchAllRequest : QuerySearchRequest +public class SearchAllRequest : SearchRequest { } \ No newline at end of file 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 8799847..e7eb397 100644 --- a/src/CompaniesHouse/Request/SearchCompanyRequest.cs +++ b/src/CompaniesHouse/Request/SearchCompanyRequest.cs @@ -1,9 +1,8 @@ -#nullable enable -using CompaniesHouse.Response.Search.CompanySearch; +using CompaniesHouse.Response.Search.CompanySearch; namespace CompaniesHouse.Request; -public class SearchCompanyRequest : QuerySearchRequest +public class SearchCompanyRequest : SearchRequest { public string? Restrictions { get; set; } } \ No newline at end of file diff --git a/src/CompaniesHouse/Request/SearchDisqualifiedOfficerRequest.cs b/src/CompaniesHouse/Request/SearchDisqualifiedOfficerRequest.cs index 3bf5b92..f741918 100644 --- a/src/CompaniesHouse/Request/SearchDisqualifiedOfficerRequest.cs +++ b/src/CompaniesHouse/Request/SearchDisqualifiedOfficerRequest.cs @@ -2,6 +2,6 @@ namespace CompaniesHouse.Request; -public class SearchDisqualifiedOfficerRequest : QuerySearchRequest +public class SearchDisqualifiedOfficerRequest : SearchRequest { } \ 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/Request/SearchOfficerRequest.cs b/src/CompaniesHouse/Request/SearchOfficerRequest.cs index 44bbb13..4fff6d2 100644 --- a/src/CompaniesHouse/Request/SearchOfficerRequest.cs +++ b/src/CompaniesHouse/Request/SearchOfficerRequest.cs @@ -2,6 +2,6 @@ namespace CompaniesHouse.Request; -public class SearchOfficerRequest : QuerySearchRequest +public class SearchOfficerRequest : SearchRequest { } \ No newline at end of file diff --git a/src/CompaniesHouse/Request/SearchRequest.cs b/src/CompaniesHouse/Request/SearchRequest.cs index e39a81a..716c65f 100644 --- a/src/CompaniesHouse/Request/SearchRequest.cs +++ b/src/CompaniesHouse/Request/SearchRequest.cs @@ -1,12 +1,9 @@ namespace CompaniesHouse.Request { - public abstract class QuerySearchRequest : SearchRequest, IQuerySearchRequest + public abstract class SearchRequest : ISearchRequest { public string Query { get; set; } = ""; - } - public abstract class SearchRequest : ISearchRequest - { public int? ItemsPerPage { get; set; } public int? StartIndex { get; set; } diff --git a/src/CompaniesHouse/Response/Address.cs b/src/CompaniesHouse/Response/Address.cs index 145abb3..f48fe02 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")] - public string AddressLine1 { get; set; } + [JsonPropertyName("address_line_1")] + public string? AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] - public string AddressLine2 { get; set; } + [JsonPropertyName("address_line_2")] + public string? AddressLine2 { get; set; } - [JsonProperty(PropertyName = "care_of")] - public string CareOf { get; set; } + [JsonPropertyName("care_of")] + public string? CareOf { get; set; } - [JsonProperty(PropertyName = "country")] - public string Country { get; set; } + [JsonPropertyName("country")] + public string? Country { get; set; } - [JsonProperty(PropertyName = "locality")] - public string Locality { get; set; } + [JsonPropertyName("locality")] + public string? Locality { get; set; } - [JsonProperty(PropertyName = "po_box")] - public string PoBox { get; set; } + [JsonPropertyName("po_box")] + public string? PoBox { get; set; } - [JsonProperty(PropertyName = "postal_code")] - public string PostalCode { get; set; } + [JsonPropertyName("postal_code")] + public string? PostalCode { get; set; } - [JsonProperty(PropertyName = "Premises")] - public string Premises { get; set; } + [JsonPropertyName("premises")] + public string? Premises { get; set; } - [JsonProperty(PropertyName = "region")] - public string Region { get; set; } + [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..18d5e9d 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")] - public string CompanyStatus { get; set; } + [JsonPropertyName("company_status")] + public CompanyStatus CompanyStatus { get; set; } - [JsonProperty(PropertyName = "company_number")] - public string CompanyNumber { get; set; } + [JsonPropertyName("company_number")] + public string? CompanyNumber { get; set; } - [JsonProperty(PropertyName = "company_name")] - public string CompanyName { get; set; } + [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..d56e255 100644 --- a/src/CompaniesHouse/Response/Appointments/Appointment.cs +++ b/src/CompaniesHouse/Response/Appointments/Appointment.cs @@ -1,41 +1,51 @@ -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("etag")] + public string? ETag { get; set; } + + [JsonPropertyName("officer_role")] public OfficerRole OfficerRole { get; set; } - [JsonProperty(PropertyName = "name_elements")] - public NameElements NameElements { get; set; } + [JsonPropertyName("name_elements")] + public NameElements? NameElements { get; set; } - [JsonProperty(PropertyName = "name")] - public string Name { get; set; } + [JsonPropertyName("name")] + public string? Name { get; set; } - [JsonProperty(PropertyName = "appointed_to")] - public AppointedTo Appointed { get; set; } + [JsonPropertyName("appointed_to")] + public AppointedTo? Appointed { get; set; } - [JsonProperty(PropertyName = "nationality")] - public string Nationality { get; set; } + [JsonPropertyName("nationality")] + public string? Nationality { get; set; } - [JsonProperty(PropertyName = "country_of_residence")] - public string CountryOfResidence { get; set; } + [JsonPropertyName("country_of_residence")] + public string? CountryOfResidence { get; set; } - [JsonProperty(PropertyName = "occupation")] - public string Occupation { get; set; } + [JsonPropertyName("occupation")] + public string? Occupation { get; set; } - [JsonProperty(PropertyName = "address")] - public Address Address { get; set; } + [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; } + + [JsonPropertyName("is_pre_1992_appointment")] + public bool? IsPre1992Appointment { get; set; } + + [JsonPropertyName("identification")] + public OfficerIdentification? Identification { get; set; } + + [JsonPropertyName("links")] + public AppointmentLinks? Links { get; set; } } -} \ No newline at end of file +} 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 5fbb2ab..6378fa6 100644 --- a/src/CompaniesHouse/Response/Appointments/Appointments.cs +++ b/src/CompaniesHouse/Response/Appointments/Appointments.cs @@ -1,26 +1,49 @@ -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("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; } - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } + [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")] - public DateOfBirth DateOfBirth { get; set; } + [JsonPropertyName("date_of_birth")] + public DateOfBirth? DateOfBirth { get; set; } + + [JsonPropertyName("items")] + 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; } - [JsonProperty(PropertyName = "items")] - public Appointment[] Items { 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/Appointments/NameElements.cs b/src/CompaniesHouse/Response/Appointments/NameElements.cs index 7cd5f33..4579439 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")] - public string Title { get; set; } + [JsonPropertyName("title")] + public string? Title { get; set; } - [JsonProperty(PropertyName = "forename")] - public string Forename { get; set; } + [JsonPropertyName("forename")] + public string? Forename { get; set; } - [JsonProperty(PropertyName = "surname")] - public string Surname { get; set; } + [JsonPropertyName("surname")] + public string? Surname { get; set; } - [JsonProperty(PropertyName = "other_forenames")] - public string OtherForenames { get; set; } + [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 deleted file mode 100644 index 3c0218d..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 - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/ChargeStatus.cs b/src/CompaniesHouse/Response/ChargeStatus.cs deleted file mode 100644 index 2b20662..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, - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/Charges/Charge.cs b/src/CompaniesHouse/Response/Charges/Charge.cs index fc5372c..94e144d 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")] - public string ChargeCode { get; set; } + [JsonPropertyName("charge_code")] + public string? ChargeCode { get; set; } - [JsonProperty("charge_number")] + [JsonPropertyName("charge_number")] public int? ChargeNumber { get; set; } - [JsonProperty("classification")] - public Classification Classification { get; set; } + [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")] - public string Etag { get; set; } + [JsonPropertyName("etag")] + public string? Etag { get; set; } - [JsonProperty("id")] - public string Id { get; set; } + [JsonPropertyName("id")] + public string? Id { get; set; } - [JsonProperty("insolvency_cases")] - public InsolvencyCase[] InsolvencyCases { get; set; } + [JsonPropertyName("insolvency_cases")] + public InsolvencyCase[]? InsolvencyCases { get; set; } - [JsonProperty("links")] - public Links Links { get; set; } + [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")] - public Particular Particular { get; set; } + [JsonPropertyName("particulars")] + public Particular? Particular { get; set; } - [JsonProperty("persons_entitled")] - public PersonEntitled[] PersonsEntitled { get; set; } + [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")] - public ScottishAlterations ScottishAlterations { get; set; } + [JsonPropertyName("scottish_alterations")] + public ScottishAlterations? ScottishAlterations { get; set; } - [JsonProperty("secured_details")] - public SecuredDetail SecuredDetail { get; set; } + [JsonPropertyName("secured_details")] + public SecuredDetail? SecuredDetail { get; set; } - [JsonProperty("status")] - [JsonConverter(typeof(OptionalStringEnumConverter), ChargeStatus.None)] + [JsonPropertyName("status")] public ChargeStatus Status { get; set; } - [JsonProperty("transactions")] - public Transaction[] Transactions { get; set; } + [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..5ebc51d 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")] - public string Etag { get; set; } - - [JsonProperty("items")] - public Charge[] Items { get; set; } - - [JsonProperty("part_satisfied_count")] + [JsonPropertyName("etag")] + public string? Etag { get; set; } + + [JsonPropertyName("items")] + public Charge[]? Items { get; set; } + + [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")] - public int? UnfileteredCount { get; set; } + + [JsonPropertyName("unfiltered_count")] + public int? UnfilteredCount { 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..6c363f0 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")] - public string Description { get; set; } - - [JsonProperty("type")] - [JsonConverter(typeof(OptionalStringEnumConverter), ClassificationChargeType.None)] + [JsonPropertyName("description")] + public string? Description { get; set; } + + [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..0b69281 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")] - public string CaseNumber { get; set; } - - [JsonProperty("links")] - public InsolvencyCaseLinks Links { get; set; } - - [JsonProperty("transaction_id")] + [JsonPropertyName("case_number")] + public string? CaseNumber { get; set; } + + [JsonPropertyName("links")] + public InsolvencyCaseLinks? Links { get; set; } + + [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..71ccd1b 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")] - public string Case { get; set; } + [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..c13d808 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")] - public string Self { get; set; } + [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..f336eb1 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")] - public string Description { get; set; } + [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..724fc34 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")] - public string Name { get; set; } + [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..2770d2a 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")] - public string Description { get; set; } + [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..5801dca 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")] - public string FilingType { get; set; } + [JsonPropertyName("filing_type")] + public string? FilingType { get; set; } - [JsonProperty("insolvency_case_number")] + [JsonPropertyName("insolvency_case_number")] public int? InsolvencyCaseNumber { get; set; } - [JsonProperty("links")] - public TransactionLinks Links { get; set; } + [JsonPropertyName("links")] + public TransactionLinks? Links { get; set; } - [JsonProperty("transaction_id")] - public int? TransactionId { get; set; } + [JsonPropertyName("transaction_id")] + public long? 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..1ed5f02 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")] - public string Filing { get; set; } - - [JsonProperty("insolvency_case")] - public string InsolvencyCase { get; set; } + [JsonPropertyName("filing")] + public string? Filing { get; set; } + + [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 deleted file mode 100644 index f8a718a..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 - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs b/src/CompaniesHouse/Response/CompanyFiling/CompanyFilingHistory.cs index 5f0d79d..4318330 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")] - public string ETag { get; set; } + [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")] - public FilingHistoryItem[] Items { get; set; } + [JsonPropertyName("items")] + public FilingHistoryItem[]? Items { get; set; } - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } + [JsonPropertyName("kind")] + public string? Kind { get; set; } } } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs index bb1eb50..193ce08 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItem.cs @@ -1,61 +1,62 @@ -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))] - public FilingSubcategory[] Subcategory { get; set; } + [JsonPropertyName("subcategory")] + public FilingSubcategory[]? Subcategory { get; set; } - [JsonProperty(PropertyName = "transaction_id")] - public string TransactionId { get; set; } + [JsonPropertyName("transaction_id")] + public string? TransactionId { get; set; } - [JsonProperty(PropertyName = "type")] - public string FilingType { get; set; } + [JsonPropertyName("type")] + public string? FilingType { get; set; } - [JsonProperty(PropertyName = "barcode")] - public string Barcode { get; set; } + [JsonPropertyName("barcode")] + public string? Barcode { get; set; } - [JsonProperty(PropertyName = "date")] + [JsonPropertyName("date")] public DateTime? DateOfProcessing { get; set; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + [JsonPropertyName("action_date")] + public DateTime? ActionDate { get; set; } - [JsonProperty(PropertyName = "description_values")] - private JObject DescriptionValues { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - [JsonProperty(PropertyName = "pages")] + [JsonInclude] + [JsonPropertyName("description_values")] + private JsonElement? DescriptionValues { get; set; } + + [JsonPropertyName("pages")] public int? PageCount { get; set; } - [JsonProperty(PropertyName = "paper_filed")] + [JsonPropertyName("paper_filed")] public bool? PaperFiled { get; set; } - [JsonProperty(PropertyName = "annotations")] - public FilingHistoryItemAnnotation[] Annotations { get; set; } + [JsonPropertyName("annotations")] + public FilingHistoryItemAnnotation[]? Annotations { get; set; } - [JsonProperty(PropertyName = "associated_filings")] - public FilingHistoryItemAssociatedFiling[] AssociatedFilings { get; set; } + [JsonPropertyName("associated_filings")] + public FilingHistoryItemAssociatedFiling[]? AssociatedFilings { get; set; } - [JsonProperty(PropertyName = "resolutions")] - public FilingHistoryItemResolution[] Resolutions { get; set; } + [JsonPropertyName("resolutions")] + public FilingHistoryItemResolution[]? Resolutions { get; set; } - [JsonProperty(PropertyName = "links")] - public Links Links { get; set; } + [JsonPropertyName("links")] + public Links? Links { get; set; } - public string GetDescription(string format, string dateFormat = null) + public string GetDescription(string format) { - return DescriptionProvider.GetDescription(format, DescriptionValues, dateFormat); + return DescriptionProvider.GetDescription(format, DescriptionValues); } } } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs index 51abd79..cdd0d44 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAnnotation.cs @@ -1,27 +1,28 @@ -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")] - public string Annotation { get; set; } + [JsonPropertyName("annotation")] + public string? Annotation { get; set; } - [JsonProperty(PropertyName = "date")] + [JsonPropertyName("date")] public DateTime? DateOfAnnotation { get; set; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + [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, string dateFormat = null) + public string GetDescription(string format) { - return DescriptionProvider.GetDescription(format, DescriptionValues, dateFormat); + return DescriptionProvider.GetDescription(format, DescriptionValues); } } } diff --git a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs index 7c00b84..896597a 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemAssociatedFiling.cs @@ -1,21 +1,23 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyFiling { public class FilingHistoryItemAssociatedFiling { - [JsonProperty(PropertyName = "type")] - public string FilingType { get; set; } + [JsonPropertyName("type")] + public string? FilingType { get; set; } - [JsonProperty(PropertyName = "date")] + [JsonPropertyName("date")] public DateTime? Date { get; set; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - [JsonProperty(PropertyName = "description_values")] - private Dictionary DescriptionValues { get; set; } + [JsonInclude] + [JsonPropertyName("description_values")] + private JsonElement? 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 2eb4b7f..36c6bc1 100644 --- a/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs +++ b/src/CompaniesHouse/Response/CompanyFiling/FilingHistoryItemResolution.cs @@ -1,40 +1,38 @@ -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))] - public FilingSubcategory[] Subcategory { get; set; } + [JsonPropertyName("subcategory")] + public FilingSubcategory[]? Subcategory { get; set; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + [JsonPropertyName("description")] + public string? Description { get; set; } - [JsonProperty(PropertyName = "document_id")] - public string DocumentId { get; set; } + [JsonPropertyName("document_id")] + public string? DocumentId { get; set; } - [JsonProperty(PropertyName = "receive_date")] + [JsonPropertyName("receive_date")] public DateTime? DateOfProcessing { get; set; } - [JsonProperty(PropertyName = "type")] - public string ResolutionType { get; set; } + [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, string dateFormat = null) + public string GetDescription(string format) { - return DescriptionProvider.GetDescription(format, DescriptionValues, dateFormat); + return DescriptionProvider.GetDescription(format, DescriptionValues); } } } diff --git a/src/CompaniesHouse/Response/CompanyFiling/Links.cs b/src/CompaniesHouse/Response/CompanyFiling/Links.cs index 80b8d5b..2c5c426 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")] - public string Self { get; set; } + [JsonPropertyName("self")] + public string? Self { get; set; } - [JsonProperty(PropertyName = "document_metadata")] - public string DocumentMetaData { get; set; } + [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/AccountingRequirement.cs b/src/CompaniesHouse/Response/CompanyProfile/AccountingRequirement.cs deleted file mode 100644 index 4ed13b5..0000000 --- a/src/CompaniesHouse/Response/CompanyProfile/AccountingRequirement.cs +++ /dev/null @@ -1,15 +0,0 @@ -using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; - -namespace CompaniesHouse.Response.CompanyProfile; - -public class AccountingRequirement -{ - [JsonProperty("foreign_account_type")] - [JsonConverter(typeof(OptionalStringEnumConverter), ForeignAccountType.None)] - public ForeignAccountType ForeignAccountType { get; set; } - - [JsonProperty("terms_of_account_publication")] - [JsonConverter(typeof(OptionalStringEnumConverter), TermsOfAccountPublication.None)] - public TermsOfAccountPublication TermsOfAccountPublication { 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..2bac502 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")] - public AccountingReferenceDate AccountingReferenceDate { get; set; } + [JsonPropertyName("accounting_reference_date")] + public AccountingReferenceDate AccountingReferenceDate { get; set; } = new(); - [JsonProperty(PropertyName = "last_accounts")] - public LastAccounts LastAccounts { get; set; } + [JsonPropertyName("last_accounts")] + public LastAccounts? LastAccounts { get; set; } - [JsonProperty(PropertyName = "next_accounts")] - public NextAccounts NextAccounts { get; set; } + [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; } + public DateTime NextMadeUpTo { get; set; } - [JsonProperty(PropertyName = "overdue")] + [JsonPropertyName("overdue")] [Obsolete("Deprecated - use NextAccounts.Overdue")] - public bool? Overdue { get; set; } + 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..ee38013 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")] - public string BusinessActivity { get; set; } - [JsonProperty(PropertyName = "parent_company_name")] - public string ParentCompanyName { get; set; } - [JsonProperty(PropertyName = "parent_company_number")] - public string ParentCompanyNumber { get; set; } + [JsonPropertyName("business_activity")] + public string? BusinessActivity { get; set; } + [JsonPropertyName("parent_company_name")] + public string? ParentCompanyName { get; set; } + [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 ff0b905..3a88047 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfile.cs @@ -1,94 +1,98 @@ -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")] - public string ETag { get; set; } + [JsonPropertyName("etag")] + public string? ETag { get; set; } - [JsonProperty(PropertyName = "accounts")] - public Accounts Accounts { get; set; } + [JsonPropertyName("accounts")] + public Accounts? Accounts { get; set; } - [JsonProperty(PropertyName = "annual_return")] - public AnnualReturn AnnualReturn { get; set; } + [JsonPropertyName("annual_return")] + public AnnualReturn? AnnualReturn { get; set; } - [JsonProperty(PropertyName = "confirmation_statement")] - public ConfirmationStatement ConfirmationStatement { get; set; } + [JsonPropertyName("confirmation_statement")] + public ConfirmationStatement? ConfirmationStatement { get; set; } - [JsonProperty(PropertyName = "can_file")] - public bool? CanFile { get; set; } + [JsonPropertyName("can_file")] + public bool CanFile { get; set; } - [JsonProperty(PropertyName = "company_name")] - public string CompanyName { get; set; } + [JsonPropertyName("company_name")] + public string CompanyName { get; set; } = string.Empty; - [JsonProperty(PropertyName = "company_number")] - public string CompanyNumber { get; set; } + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } = string.Empty; - [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("subtype")] + public CompanySubtype Subtype { get; set; } + + [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("has_super_secure_pscs")] + public bool? HasSuperSecurePscs { get; set; } + + [JsonPropertyName("is_community_interest_company")] public bool? IsCommunityInterestCompany { get; set; } - [JsonProperty(PropertyName = "jurisdiction")] - [JsonConverter(typeof(StringEnumConverter))] + [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; } - [JsonProperty(PropertyName = "last_full_members_list_date")] + [JsonPropertyName("last_full_members_list_date")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? LastFullMembersListDate { get; set; } - [JsonProperty(PropertyName = "links")] - public CompanyProfileLinks Links { get; set; } + [JsonPropertyName("links")] + public CompanyProfileLinks Links { get; set; } = new(); - [JsonProperty(PropertyName = "previous_company_names")] - public PreviousCompanyName[] PreviousCompanyNames { get; set; } + [JsonPropertyName("previous_company_names")] + public PreviousCompanyName[]? PreviousCompanyNames { get; set; } - [JsonProperty(PropertyName = "registered_office_address")] - public Address RegisteredOfficeAddress { get; set; } + [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")] - public string[] SicCodes { get; set; } + [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")] - public BranchCompanyDetails BranchCompanyDetails { get; set; } - - [JsonProperty(PropertyName = "foreign_company_details")] - public ForeignCompanyDetails ForeignCompanyDetails { get; set; } + [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..73c1e47 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/CompanyProfileLinks.cs @@ -1,31 +1,37 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.CompanyProfile { public class CompanyProfileLinks { - [JsonProperty(PropertyName = "charges")] - public string Charges { get; set; } - - [JsonProperty(PropertyName = "filing_history")] - public string FilingHistory { get; set; } - - [JsonProperty(PropertyName = "insolvency")] - public string Insolvency { get; set; } - - [JsonProperty(PropertyName = "officers")] - public string Officers { get; set; } - - [JsonProperty(PropertyName = "persons_with_significant_control")] - public string PersonsWithSignificantControl { get; set; } - - [JsonProperty(PropertyName = "persons_with_significant_control_statements")] - public string PersonsWithSignificantControlStatements { get; set; } - - [JsonProperty(PropertyName = "registers")] - public string Registers { get; set; } - - [JsonProperty(PropertyName = "self")] - public string Self { get; set; } + [JsonPropertyName("charges")] + public string? Charges { get; set; } + + [JsonPropertyName("exemptions")] + public string? Exemptions { get; set; } + + [JsonPropertyName("filing_history")] + public string? FilingHistory { get; set; } + + [JsonPropertyName("insolvency")] + public string? Insolvency { get; set; } + + [JsonPropertyName("officers")] + public string? Officers { get; set; } + + [JsonPropertyName("persons_with_significant_control")] + public string? PersonsWithSignificantControl { get; set; } + + [JsonPropertyName("persons_with_significant_control_statements")] + public string? PersonsWithSignificantControlStatements { get; set; } + + [JsonPropertyName("registers")] + public string? Registers { get; set; } + + [JsonPropertyName("self")] + public string Self { get; set; } = string.Empty; + + [JsonPropertyName("uk_establishments")] + public string? UkEstablishments { 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/ForeignAccountType.cs b/src/CompaniesHouse/Response/CompanyProfile/ForeignAccountType.cs deleted file mode 100644 index 398b21f..0000000 --- a/src/CompaniesHouse/Response/CompanyProfile/ForeignAccountType.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.CompanyProfile; - -public enum ForeignAccountType -{ - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "accounting-requirements-of-originating-country-apply")] - AccountingRequirementsOfOriginatingCountryApply, - - [EnumMember(Value = "accounting-requirements-of-originating-country-do-not-apply")] - AccountingRequirementsOfOriginatingCountryDoNotApply -} \ No newline at end of file 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 index 32a4e4f..e8d7fe9 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccounts.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyAccounts.cs @@ -1,15 +1,16 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace CompaniesHouse.Response.CompanyProfile; - -public class ForeignCompanyAccounts +namespace CompaniesHouse.Response.CompanyProfile { - [JsonProperty("account_period_from:")] - public ForeignCompanyPeriodFrom AccountPeriodFrom { get; set; } + public class ForeignCompanyAccounts + { + [JsonPropertyName("account_period_from")] + public AccountingReferenceDate? AccountPeriodFrom { get; set; } - [JsonProperty("account_period_to")] - public ForeignCompanyPeriodTo AccountPeriodTo { get; set; } + [JsonPropertyName("account_period_to")] + public AccountingReferenceDate? AccountPeriodTo { get; set; } - [JsonProperty("must_file_within")] - public MustFileWithin MustFileWithin { get; set; } -} \ No newline at end of file + [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 index 975faee..e211215 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyDetails.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyDetails.cs @@ -1,30 +1,31 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace CompaniesHouse.Response.CompanyProfile; - -public class ForeignCompanyDetails +namespace CompaniesHouse.Response.CompanyProfile { - [JsonProperty("accounting_requirement")] - public AccountingRequirement AccountingRequirement { get; set; } + public class ForeignCompanyDetails + { + [JsonPropertyName("accounting_requirement")] + public ForeignCompanyAccountingRequirement? AccountingRequirement { get; set; } - [JsonProperty("accounts")] - public ForeignCompanyAccounts Accounts { get; set; } + [JsonPropertyName("accounts")] + public ForeignCompanyAccounts? Accounts { get; set; } - [JsonProperty("business_activity")] - public string BusinessActivity { get; set; } + [JsonPropertyName("business_activity")] + public string? BusinessActivity { get; set; } - [JsonProperty("company_type")] - public string CompanyType { get; set; } + [JsonPropertyName("governed_by")] + public string? GovernedBy { get; set; } - [JsonProperty("governed_by")] - public string GovernedBy { get; set; } + [JsonPropertyName("is_a_credit_financial_institution")] + public bool? IsACreditFinancialInstitution { get; set; } - [JsonProperty("is_a_credit_finance_institution")] - public bool? IsACreditFinanceInstitution { get; set; } + [JsonPropertyName("originating_registry")] + public ForeignCompanyOriginatingRegistry? OriginatingRegistry { get; set; } - [JsonProperty("originating_registry")] - public OriginatingRegistry OriginatingRegistry { get; set; } + [JsonPropertyName("registration_number")] + public string? RegistrationNumber { get; set; } - [JsonProperty("registration_number")] - public string RegistrationNumber { get; set; } -} \ No newline at end of file + [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/ForeignCompanyPeriodFrom.cs b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyPeriodFrom.cs deleted file mode 100644 index 57d0171..0000000 --- a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyPeriodFrom.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Newtonsoft.Json; - -namespace CompaniesHouse.Response.CompanyProfile; - -public class ForeignCompanyPeriodFrom -{ - [JsonProperty("day")] - public int? Day { get; set; } - - [JsonProperty("month")] - public int? Month { get; set; } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyPeriodTo.cs b/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyPeriodTo.cs deleted file mode 100644 index 7d45ade..0000000 --- a/src/CompaniesHouse/Response/CompanyProfile/ForeignCompanyPeriodTo.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Newtonsoft.Json; - -namespace CompaniesHouse.Response.CompanyProfile; - -public class ForeignCompanyPeriodTo -{ - [JsonProperty("day")] - public int? Day { get; set; } - - [JsonProperty("month")] - public int? Month { get; set; } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs b/src/CompaniesHouse/Response/CompanyProfile/Jurisdiction.cs deleted file mode 100644 index 3901b9d..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 - } -} \ 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/MustFileWithin.cs b/src/CompaniesHouse/Response/CompanyProfile/MustFileWithin.cs index 2ac2ac2..c8b28c6 100644 --- a/src/CompaniesHouse/Response/CompanyProfile/MustFileWithin.cs +++ b/src/CompaniesHouse/Response/CompanyProfile/MustFileWithin.cs @@ -1,9 +1,10 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; -namespace CompaniesHouse.Response.CompanyProfile; - -public class MustFileWithin +namespace CompaniesHouse.Response.CompanyProfile { - [JsonProperty("months")] - public int? Months { get; set; } -} \ No newline at end of file + public class MustFileWithin + { + [JsonPropertyName("months")] + public string? Months { get; set; } + } +} 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/OriginatingRegistry.cs b/src/CompaniesHouse/Response/CompanyProfile/OriginatingRegistry.cs deleted file mode 100644 index 8ae32b7..0000000 --- a/src/CompaniesHouse/Response/CompanyProfile/OriginatingRegistry.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Newtonsoft.Json; - -namespace CompaniesHouse.Response.CompanyProfile; - -public class OriginatingRegistry -{ - [JsonProperty("country")] - public string Country { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs b/src/CompaniesHouse/Response/CompanyProfile/PreviousCompanyName.cs index 3510b31..37e2ba3 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")] - public string Name { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - [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/CompanyProfile/TermsOfAccountPublication.cs b/src/CompaniesHouse/Response/CompanyProfile/TermsOfAccountPublication.cs deleted file mode 100644 index d5b0b39..0000000 --- a/src/CompaniesHouse/Response/CompanyProfile/TermsOfAccountPublication.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response.CompanyProfile; - -public enum TermsOfAccountPublication -{ - [EnumMember(Value = "")] - None = 0, - [EnumMember(Value = "accounts-publication-date-supplied-by-company")] - AccountsPublicationDateSuppliedByCompany, - [EnumMember(Value = "accounting-publication-date-does-not-need-to-be-supplied-by-company")] - AccountingPublicationDateDoesNotNeedToBeSuppliedByCompany, - [EnumMember(Value = "accounting-reference-date-allocated-by-companies-house")] - AccountingReferenceDateAllocatedByCompaniesHouse -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/CompanyStatus.cs b/src/CompaniesHouse/Response/CompanyStatus.cs deleted file mode 100644 index dfd2c75..0000000 --- a/src/CompaniesHouse/Response/CompanyStatus.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response -{ - public enum CompanyStatus - { - [EnumMember(Value = "")] - None = 0, - - [EnumMember(Value = "active")] - Active, - - [EnumMember(Value = "dissolved")] - Dissolved, - - [EnumMember(Value = "liquidation")] - Liquidation, - - [EnumMember(Value = "receivership")] - Receivership, - - [EnumMember(Value = "administration")] - Administration, - - [EnumMember(Value = "voluntary-arrangement")] - VoluntaryArrangement, - - [EnumMember(Value = "converted-closed")] - ConvertedClosed, - - [EnumMember(Value = "insolvency-proceedings")] - InsolvencyProceedings, - - [EnumMember(Value = "open")] - Open, - - [EnumMember(Value = "closed")] - Closed, - - [EnumMember(Value = "closed-on")] - ClosedOn, - - [EnumMember(Value = "registered")] - Registered, - - [EnumMember(Value = "removed")] - Removed, - } -} diff --git a/src/CompaniesHouse/Response/CompanyStatusDetail.cs b/src/CompaniesHouse/Response/CompanyStatusDetail.cs deleted file mode 100644 index e2d6cc8..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 - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/CompanySubType.cs b/src/CompaniesHouse/Response/CompanySubType.cs deleted file mode 100644 index dd2fcae..0000000 --- a/src/CompaniesHouse/Response/CompanySubType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Runtime.Serialization; - -namespace CompaniesHouse.Response; - -public enum CompanySubType -{ - [EnumMember(Value = "")] None = 0, - - [EnumMember(Value = "community-interest-company")] - CommunityInterestCompany, - - [EnumMember(Value = "private-fund-limited-partnership")] - PrivateFundLimitedPartnership, -} \ No newline at end of file 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/DateOfBirth.cs b/src/CompaniesHouse/Response/DateOfBirth.cs index 8a6f4b3..8a78656 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")] - public int? Month { get; set; } + [JsonPropertyName("month")] + public int Month { get; set; } - [JsonProperty(PropertyName = "year")] - public int? Year { get; set; } + [JsonPropertyName("year")] + public int Year { get; set; } } } 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/Response/Document/DocumentDownload.cs b/src/CompaniesHouse/Response/Document/DocumentDownload.cs index e674590..a7e6fce 100644 --- a/src/CompaniesHouse/Response/Document/DocumentDownload.cs +++ b/src/CompaniesHouse/Response/Document/DocumentDownload.cs @@ -1,11 +1,11 @@ -using System.IO; +using System.IO; namespace CompaniesHouse.Response.Document { public class DocumentDownload { - public Stream Content { get; set; } - public string ContentType { get; set; } + public Stream? Content { get; set; } + 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..7e1d187 100644 --- a/src/CompaniesHouse/Response/Document/DocumentMetadata.cs +++ b/src/CompaniesHouse/Response/Document/DocumentMetadata.cs @@ -1,29 +1,31 @@ -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")] - public string CompanyNumber { get; set; } - [JsonProperty("barcode")] - public string Barcode { get; set; } - [JsonProperty("significant_date")] - public object SignificantDate { get; set; } - [JsonProperty("significant_date_type")] - public string SignificantDateType { get; set; } - [JsonProperty("category")] - public string Category { get; set; } - [JsonProperty("pages")] + [JsonPropertyName("company_number")] + public string? CompanyNumber { get; set; } + [JsonPropertyName("barcode")] + public string? Barcode { get; set; } + [JsonPropertyName("significant_date")] + public DateTime? SignificantDate { get; set; } + [JsonPropertyName("significant_date_type")] + public string? SignificantDateType { get; set; } + [JsonPropertyName("category")] + public string? Category { get; set; } + [JsonPropertyName("pages")] public int Pages { get; set; } - [JsonProperty("created_at")] - public string CreatedAt { get; set; } - [JsonProperty("etag")] - public string Etag { get; set; } - [JsonProperty("links")] - public Links Links { get; set; } - [JsonProperty("resources")] - public Dictionary Resources { get; set; } + [JsonPropertyName("filename")] + public string? Filename { get; set; } + [JsonPropertyName("created_at")] + public DateTime? CreatedAt { get; set; } + [JsonPropertyName("etag")] + public string? Etag { get; set; } + [JsonPropertyName("links")] + public Links? Links { get; set; } + [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..9e2976f 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")] - public int ContentLength { get; set; } + [JsonPropertyName("content_length")] + public long 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..b5bf85f 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")] - public string Self { get; set; } - [JsonProperty("document")] - public string Document { get; set; } + [JsonPropertyName("self")] + public string? Self { get; set; } + [JsonPropertyName("document")] + public string? Document { get; set; } } -} \ No newline at end of file +} 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/Response/FilingCategory.cs b/src/CompaniesHouse/Response/FilingCategory.cs deleted file mode 100644 index 5535d53..0000000 --- a/src/CompaniesHouse/Response/FilingCategory.cs +++ /dev/null @@ -1,93 +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, - - [EnumMember(Value = "officer")] - Officer, - - [EnumMember(Value = "social-landlord")] - SocialLandlord, - } -} 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 888c469..0000000 --- a/src/CompaniesHouse/Response/FilingSubcategory.cs +++ /dev/null @@ -1,99 +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, - - [EnumMember(Value = "social-landlord")] - SocialLandlord, - - [EnumMember(Value = "investment-company")] - InvestmentCompany, - } -} diff --git a/src/CompaniesHouse/Response/Insolvency/Address.cs b/src/CompaniesHouse/Response/Insolvency/Address.cs index 2125d12..8c85715 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")] - public string AddressLine1 { get; set; } + [JsonPropertyName("address_line_1")] + public string? AddressLine1 { get; set; } - [JsonProperty("address_line_2")] - public string AddressLine2 { get; set; } + [JsonPropertyName("address_line_2")] + public string? AddressLine2 { get; set; } - [JsonProperty("country")] - public string Country { get; set; } + [JsonPropertyName("country")] + public string? Country { get; set; } - [JsonProperty("locality")] - public string Locality { get; set; } + [JsonPropertyName("locality")] + public string? Locality { get; set; } - [JsonProperty("postal_code")] - public string PostalCode { get; set; } + [JsonPropertyName("postal_code")] + public string? PostalCode { get; set; } - [JsonProperty("region")] - public string Region { get; set; } + [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..702697a 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")] - public CaseDate[] Dates { get; set; } + [JsonPropertyName("dates")] + public CaseDate[]? Dates { get; set; } - [JsonProperty("links")] - public Links Links { get; set; } + [JsonPropertyName("links")] + public Links? Links { get; set; } - [JsonProperty("notes")] - public string[] Notes { get; set; } + [JsonPropertyName("notes")] + public string[]? Notes { get; set; } - [JsonProperty("number")] + [JsonPropertyName("number")] public int Number { get; set; } - [JsonProperty("practitioners")] - public Practitioner[] Practitioners { get; set; } + [JsonPropertyName("practitioners")] + public Practitioner[]? Practitioners { get; set; } - [JsonProperty("type")] - public string Type { get; set; } + [JsonPropertyName("type")] + public InsolvencyCaseType 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..23aa357 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")] - public DateTime Date { get; set; } + [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 deleted file mode 100644 index e5e07b1..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, - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs b/src/CompaniesHouse/Response/Insolvency/CompanyInsolvencyInformation.cs index 5052ac8..4a59571 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")] - public Case[] Cases { get; set; } + [JsonPropertyName("cases")] + public Case[]? Cases { get; set; } - [JsonProperty("etag")] - public string Etag { get; set; } + [JsonPropertyName("etag")] + public string? Etag { get; set; } - [JsonProperty("status")] - public InsolvencyStatus[] Status { get; set; } + [JsonPropertyName("status")] + 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 db486e0..0000000 --- a/src/CompaniesHouse/Response/Insolvency/InsolvencyStatus.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Runtime.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace CompaniesHouse.Response.Insolvency -{ - [JsonConverter(typeof(StringEnumConverter))] - 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, - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/Insolvency/Links.cs b/src/CompaniesHouse/Response/Insolvency/Links.cs index bb3cf21..5acb0c8 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")] - public string Charge { get; set; } + [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..dbba3a1 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")] - public Address Address { get; set; } + [JsonPropertyName("address")] + public Address? Address { get; set; } - [JsonProperty("appointed_on")] - public DateTime AppointedOn { get; set; } + [JsonPropertyName("appointed_on")] + public DateTime? AppointedOn { get; set; } - [JsonProperty("ceased_to_act_on")] - public DateTime CeasedToActOn { get; set; } + [JsonPropertyName("ceased_to_act_on")] + public DateTime? CeasedToActOn { get; set; } - [JsonProperty("name")] - public string Name { get; set; } + [JsonPropertyName("name")] + public string? Name { get; set; } - [JsonProperty("role")] - public string Role { get; set; } + [JsonPropertyName("role")] + public string? Role { get; set; } } -} \ 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..1a45078 --- /dev/null +++ b/src/CompaniesHouse/Response/Officers/IdentityVerificationDetails.cs @@ -0,0 +1,38 @@ +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("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; } + + [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 7636053..a86ddeb 100644 --- a/src/CompaniesHouse/Response/Officers/Officer.cs +++ b/src/CompaniesHouse/Response/Officers/Officer.cs @@ -1,50 +1,64 @@ -using System; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using System; +using System.Text.Json.Serialization; +using CompaniesHouse.JsonConverters; namespace CompaniesHouse.Response.Officers { public class Officer { - [JsonProperty(PropertyName = "appointed_on")] + [JsonPropertyName("etag")] + public string? ETag { get; set; } + + [JsonPropertyName("appointed_on")] public DateTime? AppointedOn { get; set; } - [JsonProperty(PropertyName = "resigned_on")] + [JsonPropertyName("appointed_before")] + [JsonConverter(typeof(OptionalDateJsonConverter))] + public DateTime? AppointedBefore { get; set; } + + [JsonPropertyName("resigned_on")] public DateTime? ResignedOn { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] - public OfficerDateOfBirth DateOfBirth { get; set; } + [JsonPropertyName("date_of_birth")] + public OfficerDateOfBirth? DateOfBirth { get; set; } - [JsonProperty(PropertyName = "name")] - public string Name { get; set; } + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; - [JsonProperty(PropertyName = "officer_role")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("officer_role")] public OfficerRole OfficerRole { get; set; } - [JsonProperty(PropertyName = "nationality")] - public string Nationality { get; set; } + [JsonPropertyName("nationality")] + public string? Nationality { get; set; } + + [JsonPropertyName("occupation")] + public string? Occupation { get; set; } + + [JsonPropertyName("address")] + public Address? Address { get; set; } + + [JsonPropertyName("country_of_residence")] + public string? CountryOfResidence { get; set; } - [JsonProperty(PropertyName = "occupation")] - public string Occupation { get; set; } + [JsonPropertyName("former_names")] + public OfficerFormerName[]? FormerNames { get; set; } - [JsonProperty(PropertyName = "address")] - public Address Address { get; set; } + [JsonPropertyName("identification")] + public OfficerIdentification? Identification { get; set; } - [JsonProperty(PropertyName = "country_of_residence")] - public string CountryOfResidence { get; set; } + [JsonPropertyName("links")] + public OfficerLinks Links { get; set; } = new(); - [JsonProperty(PropertyName = "former_names")] - public OfficerFormerName[] FormerNames { get; set; } + [JsonPropertyName("person_number")] + public string? PersonNumber { get; set; } - [JsonProperty(PropertyName = "identification")] - public OfficerIdentification Identification { get; set; } + [JsonPropertyName("is_pre_1992_appointment")] + public bool? IsPre1992Appointment { get; set; } - [JsonProperty(PropertyName = "links")] - public OfficerLinks Links { get; set; } + [JsonPropertyName("identity_verification_details")] + public IdentityVerificationDetails? IdentityVerificationDetails { get; set; } - [JsonProperty(PropertyName = "person_number")] - public string PersonNumber { get; set; } + [JsonIgnore] + public string? OfficerId => Links.Officer?.OfficerId; } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs b/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs index 6c54051..5dd2261 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerAppointmentLink.cs @@ -1,12 +1,42 @@ -using Newtonsoft.Json; +using System; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class OfficerAppointmentLink { - [JsonProperty(PropertyName = "appointments")] - public string AppointmentsResource { get; set; } + [JsonPropertyName("appointments")] + 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); + } + } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs b/src/CompaniesHouse/Response/Officers/OfficerDateOfBirth.cs index 8a50c3c..ece519e 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")] - public int? Month { get; set; } + [JsonPropertyName("month")] + public int Month { get; set; } - [JsonProperty(PropertyName = "year")] - public int? Year { get; set; } + [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..fd873b6 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")] - public string ForeNames { get; set; } + [JsonPropertyName("forenames")] + public string? ForeNames { get; set; } - [JsonProperty(PropertyName = "surname")] - public string Surname { get; set; } + [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..b5b6dd9 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")] - public string IdentificationType { get; set; } + [JsonPropertyName("identification_type")] + public IdentificationType IdentificationType { get; set; } - [JsonProperty(PropertyName = "legal_authority")] - public string LegalAuthority { get; set; } + [JsonPropertyName("legal_authority")] + public string? LegalAuthority { get; set; } - [JsonProperty(PropertyName = "legal_form")] - public string LegalForm { get; set; } + [JsonPropertyName("legal_form")] + public string? LegalForm { get; set; } - [JsonProperty(PropertyName = "place_registered")] - public string PlaceRegistered { get; set; } + [JsonPropertyName("place_registered")] + public string? PlaceRegistered { get; set; } - [JsonProperty(PropertyName = "registration_number")] - public string RegistrationNumber { get; set; } + [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..e0fa606 100644 --- a/src/CompaniesHouse/Response/Officers/OfficerLinks.cs +++ b/src/CompaniesHouse/Response/Officers/OfficerLinks.cs @@ -1,10 +1,13 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class OfficerLinks { - [JsonProperty(PropertyName = "officer")] - public OfficerAppointmentLink Officer { get; set; } + [JsonPropertyName("self")] + public string? Self { get; set; } + + [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 deleted file mode 100644 index af1ff61..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 - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/Officers/Officers.cs b/src/CompaniesHouse/Response/Officers/Officers.cs index 2114001..57e3cab 100644 --- a/src/CompaniesHouse/Response/Officers/Officers.cs +++ b/src/CompaniesHouse/Response/Officers/Officers.cs @@ -1,22 +1,37 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Officers { public class Officers { - [JsonProperty(PropertyName = "active_count")] - public int? ActiveCount { get; set; } + [JsonPropertyName("etag")] + public string ETag { get; set; } = string.Empty; - [JsonProperty(PropertyName = "items")] - public Officer[] Items { get; set; } + [JsonPropertyName("active_count")] + public int ActiveCount { get; set; } - [JsonProperty(PropertyName = "resigned_count")] - public int? ResignedCount { get; set; } - - [JsonProperty(PropertyName = "total_results")] + [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; } = string.Empty; + + [JsonPropertyName("links")] + public OfficersListLinks Links { get; set; } = new(); + + [JsonPropertyName("resigned_count")] + public int ResignedCount { get; set; } + + [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/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/Response/ParticularType.cs b/src/CompaniesHouse/Response/ParticularType.cs deleted file mode 100644 index c908eba..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 - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs index 49f03d2..61d5356 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControl.cs @@ -1,50 +1,51 @@ -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")] - public Address Address { get; set; } + [JsonPropertyName("address")] + public Address? Address { get; set; } - [JsonProperty(PropertyName = "ceased_on")] - public DateTime CeasedOn { get; set; } + [JsonPropertyName("ceased")] + public bool? Ceased { get; set; } - [JsonProperty(PropertyName = "country_of_residence")] - public string CountryOfResidence { get; set; } + [JsonPropertyName("ceased_on")] + public DateTime? CeasedOn { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] - public DateOfBirth DateOfBirth { get; set; } + [JsonPropertyName("country_of_residence")] + public string? CountryOfResidence { get; set; } - [JsonProperty(PropertyName = "etag")] - public string ETag { get; set; } + [JsonPropertyName("date_of_birth")] + public DateOfBirth? DateOfBirth { get; set; } - [JsonProperty(PropertyName = "kind")] - [JsonConverter(typeof(StringEnumConverter))] + [JsonPropertyName("etag")] + public string? ETag { get; set; } + + [JsonPropertyName("kind")] public PersonWithSignificantControlKind Kind { get; set; } - [JsonProperty(PropertyName = "links")] - public PersonWithSignificantControlLinks Links { get; set; } + [JsonPropertyName("links")] + public PersonWithSignificantControlLinks? Links { get; set; } - [JsonProperty(PropertyName = "name")] - public string Name { get; set; } + [JsonPropertyName("name")] + public string? Name { get; set; } - [JsonProperty(PropertyName = "name_elements")] - public NameElements NameElements { get; set; } + [JsonPropertyName("name_elements")] + public NameElements? NameElements { get; set; } - [JsonProperty(PropertyName = "nationality")] - public string Nationality { get; set; } + [JsonPropertyName("nationality")] + public string? Nationality { get; set; } - [JsonProperty(PropertyName = "natures_of_control", ItemConverterType = typeof(StringEnumConverter))] - public PersonWithSignificantControlNatureOfControl[] NaturesOfControl { get; set; } + [JsonPropertyName("natures_of_control")] + public PersonWithSignificantControlNatureOfControl[]? NaturesOfControl { get; set; } - [JsonProperty(PropertyName = "notified_on")] - public DateTime NotifiedOn { get; set; } + [JsonPropertyName("notified_on")] + public DateTime? NotifiedOn { get; set; } - [JsonProperty(PropertyName = "identification")] - public PersonWithSignificantControlIdentification Identification { get; set; } + [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..6718bf4 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlIdentification.cs @@ -1,21 +1,22 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.PersonsWithSignificantControl { public class PersonWithSignificantControlIdentification { - [JsonProperty(PropertyName = "legal_authority")] - public string LegalAuthority { get; set; } + [JsonPropertyName("legal_authority")] + public string? LegalAuthority { get; set; } - [JsonProperty(PropertyName = "legal_form")] - public string LegalForm { get; set; } + [JsonPropertyName("legal_form")] + public string? LegalForm { get; set; } - [JsonProperty(PropertyName = "place_registered")] - public string PlaceRegistered { get; set; } + [JsonPropertyName("place_registered")] + public string? PlaceRegistered { get; set; } - [JsonProperty(PropertyName = "registration_number")] - public string RegistrationNumber { get; set; } + [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 deleted file mode 100644 index 79a67fa..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, - } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonWithSignificantControlLinks.cs index 4dfb17f..ee43bb4 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")] - public string Self { get; set; } + [JsonPropertyName("self")] + public string? Self { get; set; } - [JsonProperty(PropertyName = "statement")] - public string Statement { get; set; } + [JsonPropertyName("statement")] + 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 c89c1ed..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, - } -} \ No newline at end of file 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/PersonsWithSignificantControl.cs b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs index 1615fac..d18cee1 100644 --- a/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs +++ b/src/CompaniesHouse/Response/PersonsWithSignificantControl/PersonsWithSignificantControl.cs @@ -1,25 +1,28 @@ -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")] - public PersonWithSignificantControl[] Items { get; set; } + [JsonPropertyName("items")] + public PersonWithSignificantControl[]? Items { get; set; } - [JsonProperty(PropertyName = "ceased_count")] + [JsonPropertyName("ceased_count")] public int? CeasedCount { get; set; } - - [JsonProperty(PropertyName = "items_per_page")] - public int ItemsPerPage { get; set; } - - [JsonProperty(PropertyName = "start_index")] - public int StartIndex { get; set; } - [JsonProperty(PropertyName = "total_results")] - public int TotalResults { 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/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/Response/RegisteredOfficeAddress/Links.cs b/src/CompaniesHouse/Response/RegisteredOfficeAddress/Links.cs index 7c3aa6f..be0db9a 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")] - public string Self { get; set; } + [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..9f4d107 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")] - public string AddressLine1 { get; set; } - - [JsonProperty(PropertyName = "address_line_2")] - public string AddressLine2 { get; set; } - - [JsonProperty(PropertyName = "country")] - [JsonConverter(typeof(StringEnumConverter))] - public OfficeAddressCountry Country { get; set; } - - [JsonProperty(PropertyName = "etag")] - public string Etag { get; set; } - - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } - - [JsonProperty(PropertyName = "links")] - public Links Links { get; set; } - - [JsonProperty(PropertyName = "locality")] - public string Locality { get; set; } - - [JsonProperty(PropertyName = "po_box")] - public string PoBox { get; set; } - - [JsonProperty(PropertyName = "postal_code")] - public string PostalCode { get; set; } - - [JsonProperty(PropertyName = "Premises")] - public string Premises { get; set; } - - [JsonProperty(PropertyName = "region")] - public string Region { get; set; } + [JsonPropertyName("address_line_1")] + public string? AddressLine1 { get; set; } + + [JsonPropertyName("address_line_2")] + public string? AddressLine2 { get; set; } + + [JsonPropertyName("country")] + public string? Country { get; set; } + + [JsonPropertyName("etag")] + public string? Etag { get; set; } + + [JsonPropertyName("kind")] + public string? Kind { get; set; } + + [JsonPropertyName("links")] + public Links? Links { get; set; } + + [JsonPropertyName("locality")] + public string? Locality { get; set; } + + [JsonPropertyName("po_box")] + public string? PoBox { get; set; } + + [JsonPropertyName("postal_code")] + public string? PostalCode { get; set; } + + [JsonPropertyName("premises")] + public string? Premises { get; set; } + + [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 deleted file mode 100644 index 2d0a903..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 - } -} \ No newline at end of file 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/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/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs index 0c14624..84da946 100644 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs +++ b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedCompanySearch.cs @@ -1,22 +1,22 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.AdvancedCompanySearch { public class AdvancedCompanySearch { - [JsonProperty(PropertyName = "etag")] - public string ETag { get; set; } + [JsonPropertyName("etag")] + public string? ETag { get; set; } - [JsonProperty(PropertyName = "items")] - public AdvancedSearchedCompany[] Companies { get; set; } + [JsonPropertyName("hits")] + public int Hits { get; set; } - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } + [JsonPropertyName("items")] + public Company[] Items { get; set; } = []; - [JsonProperty(PropertyName = "hits")] - public int? Hits { get; set; } + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; - [JsonProperty(PropertyName = "top_hit")] - public AdvancedSearchedCompany TopHit { get; set; } + [JsonPropertyName("top_hit")] + public Company TopHit { get; set; } = new(); } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedSearchedCompany.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedSearchedCompany.cs deleted file mode 100644 index 9d774f9..0000000 --- a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/AdvancedSearchedCompany.cs +++ /dev/null @@ -1,39 +0,0 @@ -using CompaniesHouse.JsonConverters; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace CompaniesHouse.Response.Search.AdvancedCompanySearch; - -public class AdvancedSearchedCompany : SearchItem -{ - [JsonProperty(PropertyName = "company_name")] - public string CompanyName { get; set; } - - [JsonProperty(PropertyName = "registered_office_address")] - public Address RegisteredOfficeAddress { get; set; } - - [JsonProperty(PropertyName = "company_number")] - public string CompanyNumber { get; set; } - - [JsonProperty(PropertyName = "company_status")] - [JsonConverter(typeof(OptionalStringEnumConverter), CompanyStatus.None)] - public CompanyStatus CompanyStatus { get; set; } - - [JsonProperty(PropertyName = "company_type")] - [JsonConverter(typeof(StringEnumConverter))] - public CompanyType CompanyType { get; set; } - - [JsonProperty(PropertyName = "company_subtype")] - [JsonConverter(typeof(StringEnumConverter))] - public CompanySubType CompanySubType { get; set; } - - [JsonProperty(PropertyName = "date_of_cessation")] - [JsonConverter(typeof(OptionalDateJsonConverter))] - public DateTime? DateOfCessation { get; set; } - - [JsonProperty(PropertyName = "date_of_creation")] - public DateTime? DateOfCreation { get; set; } - - [JsonProperty(PropertyName = "sic_codes")] - public string[] SicCodes { get; set; } -} \ No newline at end of file diff --git a/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/AdvancedCompanySearch/Company.cs new file mode 100644 index 0000000..4207c7d --- /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; } = string.Empty; + + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } = string.Empty; + + [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; } = string.Empty; + + [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/AllSearch/Address.cs b/src/CompaniesHouse/Response/Search/AllSearch/Address.cs index 4881785..4cc6074 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")] - public string AddressLine1 { get; set; } + [JsonPropertyName("address_line_1")] + public string? AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] - public string AddressLine2 { get; set; } + [JsonPropertyName("address_line_2")] + public string? AddressLine2 { get; set; } - [JsonProperty(PropertyName = "care_of")] - public string CareOf { get; set; } + [JsonPropertyName("care_of")] + public string? CareOf { get; set; } - [JsonProperty(PropertyName = "country")] - public string Country { get; set; } + [JsonPropertyName("country")] + public string? Country { get; set; } - [JsonProperty(PropertyName = "locality")] - public string Locality { get; set; } + [JsonPropertyName("locality")] + public string? Locality { get; set; } - [JsonProperty(PropertyName = "po_box")] - public string PoBox { get; set; } + [JsonPropertyName("po_box")] + public string? PoBox { get; set; } - [JsonProperty(PropertyName = "postal_code")] - public string PostalCode { get; set; } + [JsonPropertyName("postal_code")] + public string? PostalCode { get; set; } - [JsonProperty(PropertyName = "region")] - public string Region { get; set; } + [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 2a27a8b..66af41a 100644 --- a/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs +++ b/src/CompaniesHouse/Response/Search/AllSearch/AllSearch.cs @@ -1,26 +1,29 @@ -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")] - public string Etag { get; set; } + [JsonPropertyName("etag")] + public string? Etag { get; set; } - [JsonProperty(PropertyName = "items")] - public SearchItem[] Items { get; set; } + [JsonPropertyName("items")] + public SearchItem[]? Items { get; set; } - [JsonProperty(PropertyName = "items_per_page")] - public int ItemsPerPage { get; set; } + [JsonPropertyName("items_per_page")] + public int? ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } + [JsonPropertyName("kind")] + public string? Kind { get; set; } - [JsonProperty(PropertyName = "start_index")] - public int StartIndex { get; set; } + [JsonPropertyName("page_number")] + public int? PageNumber { get; set; } - [JsonProperty(PropertyName = "total_results")] - public int TotalResults { get; set; } + [JsonPropertyName("start_index")] + public int? StartIndex { 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 new file mode 100644 index 0000000..61b1113 --- /dev/null +++ b/src/CompaniesHouse/Response/Search/AllSearch/Item.cs @@ -0,0 +1,15 @@ +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; } + } +} diff --git a/src/CompaniesHouse/Response/Search/AllSearch/Links.cs b/src/CompaniesHouse/Response/Search/AllSearch/Links.cs index 81d24b0..d99b587 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")] - public string Self { get; set; } + [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..58fca43 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")] - public string[] AddressSnippet { get; set; } - [JsonProperty(PropertyName = "snippet")] - public string[] Snippet { get; set; } - [JsonProperty(PropertyName = "title")] - public string[] Title { get; set; } + [JsonPropertyName("address_snippet")] + public string[]? AddressSnippet { get; set; } + [JsonPropertyName("snippet")] + public string[]? Snippet { get; set; } + [JsonPropertyName("title")] + public string[]? Title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs b/src/CompaniesHouse/Response/Search/CompaniesAlphabeticallySearch/CompaniesAlphabeticallySearch.cs new file mode 100644 index 0000000..a56bed4 --- /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..7ebb23a --- /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; } = string.Empty; + + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } = string.Empty; + + [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; } = new(); + + [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..30d067a --- /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/CompanySearch/Company.cs b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs index e08bd10..16e5688 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Company.cs @@ -1,50 +1,49 @@ 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")] - public Address Address { get; set; } + [JsonPropertyName("address")] + public Address Address { get; set; } = new(); - [JsonProperty(PropertyName = "company_number")] - public string CompanyNumber { get; set; } + [JsonPropertyName("address_snippet")] + public string AddressSnippet { get; set; } = string.Empty; - [JsonProperty(PropertyName = "company_status")] - [JsonConverter(typeof(OptionalStringEnumConverter), CompanyStatus.None)] + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } = string.Empty; + + [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 = "company_subtype")] - [JsonConverter(typeof(StringEnumConverter))] - public CompanySubType CompanySubType { get; set; } - - [JsonProperty(PropertyName = "date_of_cessation")] + + [JsonPropertyName("date_of_cessation")] [JsonConverter(typeof(OptionalDateJsonConverter))] public DateTime? DateOfCessation { get; set; } - [JsonProperty(PropertyName = "date_of_creation")] - public DateTime? DateOfCreation { get; set; } + [JsonPropertyName("date_of_creation")] + public DateTime DateOfCreation { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + [JsonPropertyName("description_identifier")] + public string[]? DescriptionIdentifier { get; set; } - [JsonProperty(PropertyName = "description_identifier")] - public object[] DescriptionIdentifier { get; set; } + [JsonPropertyName("external_registration_number")] + public string? ExternalRegistrationNumber { get; set; } - [JsonProperty(PropertyName = "matches")] - public Matches Matches { get; set; } + [JsonPropertyName("matches")] + public Matches? Matches { get; set; } - [JsonProperty(PropertyName = "snippet")] - public string Snippet { get; set; } + [JsonPropertyName("snippet")] + public string? Snippet { get; set; } - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; } -} \ 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 b026ecb..e7a8667 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")] - public string ETag { get; set; } + [JsonPropertyName("etag")] + public string? ETag { get; set; } - [JsonProperty(PropertyName = "items")] - public Company[] Companies { get; set; } + [JsonPropertyName("items")] + public Company[]? Companies { get; set; } - [JsonProperty(PropertyName = "items_per_page")] - public int ItemsPerPage { get; set; } + [JsonPropertyName("items_per_page")] + public int? ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } + [JsonPropertyName("kind")] + public string? Kind { get; set; } - [JsonProperty(PropertyName = "page_number")] - public int PageNumber { get; set; } + [JsonPropertyName("page_number")] + public int? PageNumber { get; set; } - [JsonProperty(PropertyName = "start_index")] - public int StartIndex { get; set; } + [JsonPropertyName("start_index")] + public int? StartIndex { get; set; } - [JsonProperty(PropertyName = "total_results")] - public int TotalResults { get; set; } + [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..62291ef 100644 --- a/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs +++ b/src/CompaniesHouse/Response/Search/CompanySearch/Matches.cs @@ -1,11 +1,17 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.CompanySearch { public class Matches { - [JsonProperty(PropertyName = "title")] - public int[] Title { get; set; } + [JsonPropertyName("address_snippet")] + public int[]? AddressSnippet { get; set; } + + [JsonPropertyName("snippet")] + public int[]? Snippet { get; set; } + + [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..41c3d4e 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")] - public string AddressLine1 { get; set; } + [JsonPropertyName("address_line_1")] + public string? AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] - public string AddressLine2 { get; set; } + [JsonPropertyName("address_line_2")] + public string? AddressLine2 { get; set; } - [JsonProperty(PropertyName = "country")] - public string Country { get; set; } + [JsonPropertyName("country")] + public string? Country { get; set; } - [JsonProperty(PropertyName = "locality")] - public string Locality { get; set; } + [JsonPropertyName("locality")] + public string? Locality { get; set; } - [JsonProperty(PropertyName = "postal_code")] - public string PostalCode { get; set; } + [JsonPropertyName("postal_code")] + public string? PostalCode { get; set; } - [JsonProperty(PropertyName = "premises")] - public string Premises { get; set; } + [JsonPropertyName("premises")] + public string? Premises { get; set; } - [JsonProperty(PropertyName = "region")] - public string Region { get; set; } + [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..623d23c 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")] - public Address Address { get; set; } + [JsonPropertyName("address")] + public Address Address { get; set; } = new(); - [JsonProperty(PropertyName = "address_snippet")] - public string AddressSnippet { get; set; } + [JsonPropertyName("address_snippet")] + public string AddressSnippet { get; set; } = string.Empty; - [JsonProperty(PropertyName = "date_of_birth")] + [JsonPropertyName("date_of_birth")] public DateTime DateOfBirth { get; set; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - [JsonProperty(PropertyName = "description_identifiers")] - public string[] DescriptionIdentifiers { get; set; } + [JsonPropertyName("description_identifiers")] + public string[]? DescriptionIdentifiers { get; set; } - [JsonProperty(PropertyName = "matches")] - public Match Matches { get; set; } + [JsonPropertyName("matches")] + public Match? Matches { get; set; } - [JsonProperty(PropertyName = "snippet")] - public string Snippet { get; set; } + [JsonPropertyName("snippet")] + public string? Snippet { get; set; } - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; } -} \ 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..1e8e3a3 100644 --- a/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/DisqualifiedOfficersSearch/DisqualifiedOfficerSearch.cs @@ -1,22 +1,25 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.DisqualifiedOfficersSearch { public class DisqualifiedOfficerSearch { - [JsonProperty(PropertyName = "items")] - public DisqualifiedOfficer[] DisqualifiedOfficers { get; set; } + [JsonPropertyName("items")] + public DisqualifiedOfficer[]? DisqualifiedOfficers { get; set; } - [JsonProperty(PropertyName = "items_per_page")] + [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; - [JsonProperty(PropertyName = "start_index")] + [JsonPropertyName("page_number")] + public int? PageNumber { get; set; } + + [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..19f0379 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")] - public string[] AddressSnippet { get; set; } + [JsonPropertyName("address_snippet")] + public string[]? AddressSnippet { get; set; } - [JsonProperty(PropertyName = "snippet")] - public string[] Snippet { get; set; } + [JsonPropertyName("snippet")] + public string[]? Snippet { get; set; } - [JsonProperty(PropertyName = "title")] - public string[] Title { get; set; } + [JsonPropertyName("title")] + public string[]? Title { get; set; } } -} \ No newline at end of file +} diff --git a/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs new file mode 100644 index 0000000..cb9d072 --- /dev/null +++ b/src/CompaniesHouse/Response/Search/DissolvedCompaniesSearch/Company.cs @@ -0,0 +1,39 @@ +using System; +using System.Text.Json.Serialization; +using CompaniesHouse.Response; + +namespace CompaniesHouse.Response.Search.DissolvedCompaniesSearch +{ + public class Company + { + [JsonPropertyName("company_name")] + public string CompanyName { get; set; } = string.Empty; + + [JsonPropertyName("company_number")] + public string CompanyNumber { get; set; } = string.Empty; + + [JsonPropertyName("company_status")] + public CompanyStatus CompanyStatus { get; set; } + + [JsonPropertyName("date_of_cessation")] + 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..c0002dd --- /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..4b85107 --- /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/Response/Search/Links.cs b/src/CompaniesHouse/Response/Search/Links.cs index b88f055..93abc51 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")] - public string Self { get; set; } + [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..163469a 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")] - public string AddressLine1 { get; set; } + [JsonPropertyName("address_line_1")] + public string? AddressLine1 { get; set; } - [JsonProperty(PropertyName = "address_line_2")] - public string AddressLine2 { get; set; } + [JsonPropertyName("address_line_2")] + public string? AddressLine2 { get; set; } - [JsonProperty(PropertyName = "care_of")] - public string CareOf { get; set; } + [JsonPropertyName("care_of")] + public string? CareOf { get; set; } - [JsonProperty(PropertyName = "country")] - public string Country { get; set; } + [JsonPropertyName("country")] + public string? Country { get; set; } - [JsonProperty(PropertyName = "locality")] - public string Locality { get; set; } + [JsonPropertyName("locality")] + public string? Locality { get; set; } - [JsonProperty(PropertyName = "po_box")] - public string PoBox { get; set; } + [JsonPropertyName("po_box")] + public string? PoBox { get; set; } - [JsonProperty(PropertyName = "postal_code")] - public string PostalCode { get; set; } + [JsonPropertyName("postal_code")] + public string? PostalCode { get; set; } - [JsonProperty(PropertyName = "premises")] - public string Premises { get; set; } + [JsonPropertyName("premises")] + public string? Premises { get; set; } - [JsonProperty(PropertyName = "region")] - public string Region { get; set; } + [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..d1a9f9e 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")] - public int[] AddressSnippet { get; set; } + [JsonPropertyName("address_snippet")] + public int[]? AddressSnippet { get; set; } - [JsonProperty(PropertyName = "snippet")] - public int[] Snippet { get; set; } + [JsonPropertyName("snippet")] + public int[]? Snippet { get; set; } - [JsonProperty(PropertyName = "title")] - public int[] Title { get; set; } + [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..2146075 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/Officer.cs @@ -1,39 +1,49 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.OfficerSearch { public class Officer : SearchItem { - [JsonProperty(PropertyName = "address")] - public Address Address { get; set; } + [JsonPropertyName("address")] + public Address Address { get; set; } = new(); - [JsonProperty(PropertyName = "address_snippet")] - public string AddressSnippet { get; set; } + [JsonPropertyName("address_snippet")] + public string AddressSnippet { get; set; } = string.Empty; - [JsonProperty(PropertyName = "appointment_count")] + [JsonPropertyName("appointment_count")] public int AppointmentCount { get; set; } - [JsonProperty(PropertyName = "date_of_birth")] - public DateOfBirth DateOfBirth { get; set; } + [JsonPropertyName("date_of_birth")] + public DateOfBirth? DateOfBirth { get; set; } - [JsonProperty(PropertyName = "description")] - public string Description { get; set; } + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; - [JsonProperty(PropertyName = "description_identifiers")] - public string[] DescriptionIdentifiers { get; set; } + [JsonPropertyName("description_identifiers")] + public string[]? DescriptionIdentifiers { get; set; } - [JsonProperty(PropertyName = "matches")] - public Match Matches { get; set; } + [JsonPropertyName("matches")] + public Match? Matches { get; set; } - [JsonProperty(PropertyName = "snippet")] - public string Snippet { get; set; } + [JsonPropertyName("snippet")] + public string? Snippet { get; set; } - [JsonProperty(PropertyName = "title")] - public string Title { get; set; } + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; - 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; + } } } -} \ 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..010e045 100644 --- a/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs +++ b/src/CompaniesHouse/Response/Search/OfficerSearch/OfficerSearch.cs @@ -1,23 +1,26 @@ -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace CompaniesHouse.Response.Search.OfficerSearch { public class OfficerSearch { - [JsonProperty(PropertyName = "items")] - public Officer[] Officers { get; set; } + [JsonPropertyName("items")] + public Officer[]? Officers { get; set; } - [JsonProperty(PropertyName = "items_per_page")] + [JsonPropertyName("items_per_page")] public int ItemsPerPage { get; set; } - [JsonProperty(PropertyName = "kind")] - public string Kind { get; set; } + [JsonPropertyName("kind")] + public string Kind { get; set; } = string.Empty; - [JsonProperty(PropertyName = "start_index")] + [JsonPropertyName("page_number")] + public int? PageNumber { get; set; } + + [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..7165184 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")] - public string Kind { get; set; } + [JsonPropertyName("kind")] + public string? Kind { get; set; } - [JsonProperty(PropertyName = "links")] - public Links Links { get; set; } + [JsonPropertyName("links")] + public Links? Links { get; set; } } } diff --git a/src/CompaniesHouse/Response/SecuredDetailType.cs b/src/CompaniesHouse/Response/SecuredDetailType.cs deleted file mode 100644 index c1d138b..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 - } -} \ No newline at end of file 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/SearchUriBuilderFactory.cs b/src/CompaniesHouse/SearchUriBuilderFactory.cs index 32d3e7c..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); @@ -13,26 +13,29 @@ public ISearchUriBuilder Create() where TSearch : Sea { return (ISearchUriBuilder)new SearchCompanyUriBuilder("search/companies"); } - - if (type == typeof(SearchOfficerRequest)) + else if (type == typeof(SearchOfficerRequest)) { - return (ISearchUriBuilder)new QuerySearchUriBuilder("search/officers"); + return (ISearchUriBuilder)new SearchUriBuilder("search/officers"); } - - if (type == typeof(SearchDisqualifiedOfficerRequest)) + else if (type == typeof(SearchDisqualifiedOfficerRequest)) { - return (ISearchUriBuilder)new QuerySearchUriBuilder( - "search/disqualified-officers"); + return (ISearchUriBuilder)new SearchUriBuilder("search/disqualified-officers"); } - - if (type == typeof(SearchAllRequest)) + else if (type == typeof(SearchAllRequest)) { - return (ISearchUriBuilder)new QuerySearchUriBuilder("search"); + return (ISearchUriBuilder)new SearchUriBuilder("search"); } - - if (type == typeof(AdvancedSearchCompanyRequest)) + else if (type == typeof(SearchCompaniesAlphabeticallyRequest)) + { + return (ISearchUriBuilder)new SearchCompaniesAlphabeticallyUriBuilder("alphabetical-search/companies"); + } + else if (type == typeof(SearchDissolvedCompaniesRequest)) + { + return (ISearchUriBuilder)new SearchDissolvedCompaniesUriBuilder("dissolved-search/companies"); + } + else if (type == typeof(AdvancedCompanySearchRequest)) { - return (ISearchUriBuilder)new AdvancedSearchCompanyUriBuilder("advanced-search/companies"); + 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/AdvancedSearchCompanyUriBuilder.cs b/src/CompaniesHouse/UriBuilders/AdvancedSearchCompanyUriBuilder.cs deleted file mode 100644 index cec6712..0000000 --- a/src/CompaniesHouse/UriBuilders/AdvancedSearchCompanyUriBuilder.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Text; -using CompaniesHouse.Request; - -namespace CompaniesHouse.UriBuilders; - -public class AdvancedSearchCompanyUriBuilder : SearchUriBuilder -{ - public AdvancedSearchCompanyUriBuilder(string path) : base(path) - { - } - - protected override string BuildQuery(AdvancedSearchCompanyRequest request) - { - var queryBuilder = new StringBuilder(base.BuildQuery(request)); - - AppendParameterIfValid(queryBuilder, "company_name_includes", request.CompanyNameIncludes, value => !string.IsNullOrWhiteSpace(value)); - AppendParameterIfValid(queryBuilder, "company_name_excludes", request.CompanyNameExcludes, value => !string.IsNullOrWhiteSpace(value)); - AppendParameterIfValid(queryBuilder, "company_status", request.CompanyStatus); - AppendParameterIfValid(queryBuilder, "company_subtype", request.CompanySubtype); - AppendParameterIfValid(queryBuilder, "company_type", request.CompanyType); - AppendParameterIfValid(queryBuilder, "dissolved_from", request.DissolvedFrom, value => value.HasValue); - AppendParameterIfValid(queryBuilder, "dissolved_to", request.DissolvedTo, value =>value.HasValue); - AppendParameterIfValid(queryBuilder, "incorporated_from", request.IncorporatedFrom, value =>value.HasValue); - AppendParameterIfValid(queryBuilder, "incorporated_to", request.IncorporatedTo, value => value.HasValue); - AppendParameterIfValid(queryBuilder, "location", request.Location, value => !string.IsNullOrWhiteSpace(value)); - AppendParameterIfValid(queryBuilder, "sic_codes", request.SicCodes); - - return queryBuilder.ToString(); - } -} \ No newline at end of file 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 73e8f95..32dcaf5 100644 --- a/src/CompaniesHouse/UriBuilders/SearchCompanyUriBuilder.cs +++ b/src/CompaniesHouse/UriBuilders/SearchCompanyUriBuilder.cs @@ -1,9 +1,8 @@ -using System.Text; -using CompaniesHouse.Request; +using CompaniesHouse.Request; namespace CompaniesHouse.UriBuilders; -public class SearchCompanyUriBuilder : QuerySearchUriBuilder +public class SearchCompanyUriBuilder : SearchUriBuilder { public SearchCompanyUriBuilder(string path) : base(path) { @@ -11,10 +10,13 @@ public SearchCompanyUriBuilder(string path) : base(path) protected override string BuildQuery(SearchCompanyRequest request) { - var queryBuilder = new StringBuilder(base.BuildQuery(request)); + var query = base.BuildQuery(request); - AppendParameterIfValid(queryBuilder, "restrictions", request.Restrictions, value => !string.IsNullOrWhiteSpace(value)); + if (!string.IsNullOrWhiteSpace(request.Restrictions)) + { + query += "&restrictions=" + Uri.EscapeDataString(request.Restrictions); + } - return queryBuilder.ToString(); + return query; } } \ No newline at end of file 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/UriBuilders/SearchUriBuilder.cs b/src/CompaniesHouse/UriBuilders/SearchUriBuilder.cs index 6776827..edcf6cb 100644 --- a/src/CompaniesHouse/UriBuilders/SearchUriBuilder.cs +++ b/src/CompaniesHouse/UriBuilders/SearchUriBuilder.cs @@ -1,26 +1,8 @@ -using System.Text; +using System; using CompaniesHouse.Request; -using Newtonsoft.Json; namespace CompaniesHouse.UriBuilders { - public class QuerySearchUriBuilder : SearchUriBuilder - where TSearch : IQuerySearchRequest - { - public QuerySearchUriBuilder(string path) : base(path) - { - } - - protected override string BuildQuery(TSearch request) - { - var queryBuilder = new StringBuilder(base.BuildQuery(request)); - - AppendParameterIfValid(queryBuilder, "q", request.Query, value => !string.IsNullOrWhiteSpace(value)); - - return queryBuilder.ToString(); - } - } - public class SearchUriBuilder : ISearchUriBuilder where TSearch : ISearchRequest { private readonly string _path; @@ -41,52 +23,19 @@ public Uri Build(TSearch request) protected virtual string BuildQuery(TSearch request) { - var queryBuilder = new StringBuilder("?"); - - AppendParameterIfValid(queryBuilder, "items_per_page", request.ItemsPerPage, value => value.HasValue); - AppendParameterIfValid(queryBuilder, "start_index", request.StartIndex, value => value.HasValue); + var query = $"?q={Uri.EscapeDataString(request.Query)}"; - return queryBuilder.ToString(); - } - - protected void AppendParameterIfValid( - StringBuilder builder, - string parameterName, - IReadOnlyCollection parameterValues) - { - foreach (var parameterValue in parameterValues) + if (request.ItemsPerPage.HasValue) { - AppendParameterIfValid(builder, parameterName, parameterValue, _ => true); + query += "&items_per_page=" + request.ItemsPerPage.Value; } - } - - protected void AppendParameterIfValid(StringBuilder builder, string parameterName, T parameterValue, - Func isValid) - { - if (!isValid(parameterValue)) return; - var value = parameterValue switch - { - string s => Uri.EscapeDataString(s), - Enum @enum => GetEnumValue(@enum), - DateTime dateTime => dateTime.ToString("O"), - _ => parameterValue.ToString() - }; - if (builder[builder.Length - 1] != '?') + if (request.StartIndex.HasValue) { - builder.Append("&"); + query += "&start_index=" + request.StartIndex.Value; } - builder.Append($"{parameterName}={value}"); - } - - private static string GetEnumValue(Enum @enum) - { - using var stringWriter = new StringWriter(); - using var textWriter = new JsonTextWriter(stringWriter); - HttpContentExtensions.Serializer.Serialize(textWriter, @enum, @enum.GetType()); - var s = stringWriter.ToString(); - return s.Substring(1, s.Length - 2); // Remove quotes + return query; } } } \ 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/enum-map.txt b/src/CompaniesHouse/enum-map.txt new file mode 100644 index 0000000..99d26d0 --- /dev/null +++ b/src/CompaniesHouse/enum-map.txt @@ -0,0 +1,32 @@ +# 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 +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 +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/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.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests.csproj index a156726..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 @@ -1,14 +1,16 @@ - net9.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..cc29238 100644 --- a/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs +++ b/tests/CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests/ServiceCollectionExtensionsTests.cs @@ -1,46 +1,135 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; +using Microsoft.Extensions.Options; +using Shouldly; +using Xunit; namespace CompaniesHouse.Extensions.Microsoft.DependencyInjection.Tests { public class ServiceCollectionExtensionsTests { - [Test] + [Fact] public void CanResolveCompaniesHouseClients() { var serviceCollection = new ServiceCollection(); serviceCollection.AddCompaniesHouseClient("ApiKey"); - + 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(); + scope.ServiceProvider.GetService().ShouldNotBeNull(); + 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/")); } - [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("first").ShouldNotBeNull(); + scope.ServiceProvider.GetRequiredKeyedService("second").ShouldNotBeNull(); + scope.ServiceProvider.GetRequiredKeyedService("second").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(); - 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(); + } + + [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 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/CompaniesHouse.IntegrationTests.csproj b/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj index f0bd530..e3de9d2 100644 --- a/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj +++ b/tests/CompaniesHouse.IntegrationTests/CompaniesHouse.IntegrationTests.csproj @@ -1,22 +1,18 @@  - net9.0 + net10.0 false - - - - - - - - + + + + \ No newline at end of file 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 497210a..efa047f 100644 --- a/tests/CompaniesHouse.IntegrationTests/Keys.cs +++ b/tests/CompaniesHouse.IntegrationTests/Keys.cs @@ -4,17 +4,13 @@ namespace CompaniesHouse.IntegrationTests { public static class Keys { - public static string ApiKey - { - get - { - var key = Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY"); - if (string.IsNullOrEmpty(key)) - { - throw new InvalidOperationException("COMPANIES_HOUSE_API_KEY environment variable is not set."); - } - return key; - } - } + 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/AppointmentsTests/AppointmentsTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/AppointmentsTestBase.cs index e4b6389..fdb9154 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 CompaniesHouseResponse 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..857a374 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/AppointmentsTests/OfficersTestsValid.cs @@ -1,30 +1,38 @@ -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] + [IntegrationFact] public void ThenTheDataItemsAreNotEmpty() { - Assert.That(Result.Data.Items, Is.Not.Empty); + Result.Data.Items.ShouldNotBeEmpty(); + } + + [IntegrationFact] + 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).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..16d7df8 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); + [IntegrationFact] + public void ThenChargesListIsNull() => Result.ShouldBeOfType.NotFound>(); } } \ 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..c79de55 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargeByIdTestsValid.cs @@ -1,18 +1,27 @@ -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"; - 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); - [Test] - public void ThenChargesListIsNull() => Assert.IsNotNull(Result.Data); + [IntegrationFact] + public void ThenChargesListIsNull() => Result.Data.ShouldNotBeNull(); + + [IntegrationFact] + 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/ChargesListTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsInValid.cs index 0a11896..5dced7a 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); + [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 40fe978..9ef8ae5 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesListTestsValid.cs @@ -1,29 +1,41 @@ 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); + public ChargesListTestsValid() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(CompaniesHouseUris.Default, Keys.ApiKey)); + } + + [IntegrationTheory] + [InlineData("03977902")] + [InlineData("00445790")] + [InlineData("00002065")] + [InlineData("03487070")] + public async Task ThenChargesListIsNotEmpty(string companyNumber) + { + var result = await _client.GetChargesListAsync(companyNumber); - [Test] - public void ThenChargesListIsNotEmpty() => Assert.IsNotEmpty(Result.Data.Items); + result.Data.Items.ShouldNotBeEmpty(); + } - public static string[] TestCases() + [IntegrationFact] + public async Task ThenKnownChargeListIncludesObservedGeneratedValues() { - return new[] - { - "03977902", // Google - "00445790", // Tesco - "00002065", // Lloyds Bank PLCo - "03487070" - }; - } + var result = await _client.GetChargesListAsync("03977902"); + var items = result.Data.Items ?? []; + + result.Data.UnfilteredCount.ShouldNotBeNull(); + result.Data.UnfilteredCount.Value.ShouldBeGreaterThan(0); + 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/ChargesTests/ChargesTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/ChargesTests/ChargesTestBase.cs index a4a8aed..081d2e2 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 CompaniesHouseResponse 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 c4cc4e7..e7e40eb 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsInvalid.cs @@ -1,37 +1,34 @@ -using System.Threading.Tasks; -using CompaniesHouse.Response; +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 CompaniesHouseResponse _result = null!; protected override async Task When() { await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() - .ConfigureAwait(false); + ; } - [Test] - public void ThenTheDataHasSomeEmptyPropertiesAndStatusOfInvalidFormat() + [IntegrationFact] + public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data.Items, Is.Empty); - Assert.That(_result.Data.HistoryStatus, Is.EqualTo(FilingHistoryStatus.InvalidFormat)); - Assert.That(_result.Data.StartIndex, Is.EqualTo(0)); - Assert.That(_result.Data.TotalCount, Is.EqualTo(0)); - + _result.Data.ShouldNotBeNull(); + _result.Data.Items.ShouldBeEmpty(); } 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..4116d6b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/CompanyFilingHistoryTestsValid.cs @@ -2,55 +2,55 @@ 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() + [IntegrationTheory] + [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; + CompaniesHouseResponse result; do { - result = await _client.GetCompanyFilingHistoryAsync(_companyNumber, page++ * size, size) - .ConfigureAwait(false); - _results.AddRange(result.Data.Items); - } while (result.Data.Items.Any()); + result = await _client.GetCompanyFilingHistoryAsync(companyNumber, page++ * size, size); + var items = result.Data.Items ?? []; + results.AddRange(items); + } while ((result.Data.Items ?? []).Any()); + + results.ShouldNotBeEmpty(); } - [Test] - public void ThenTheDataItemsAreNotEmpty() + [IntegrationFact] + public async Task ThenKnownFilingHistoryIncludesObservedPaginationFields() { - Assert.That(_results, Is.Not.Empty); + 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/FilingHistoryByTransactionIdTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsInvalid.cs index 5451971..3781b20 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 CompaniesHouseResponse _result = null!; protected override async Task When() { await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() - .ConfigureAwait(false); + ; } - [Test] + [IntegrationFact] public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data, Is.Null); + _result.ShouldBeOfType.NotFound>(); } 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..3b373cf 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyFilingHistoryTests/FilingHistoryByTransactionIdTestsValid.cs @@ -1,33 +1,41 @@ -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 const string ValidCompanyNumber = "00445790"; + private const string ValidTransactionId = "MzUyNDY1MTExNmFkaXF6a2N4"; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result = null!; protected override async Task When() { await WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() - .ConfigureAwait(false); + ; } - [Test] + [IntegrationFact] public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data, Is.Not.Null); + _result.Data.ShouldNotBeNull(); + } + + [IntegrationFact] + public void ThenObservedFieldsAreReturned() + { + _result.Data.Links?.DocumentMetaData.ShouldNotBeNullOrWhiteSpace(); + _result.Data.Category.Value.ShouldNotBeNullOrWhiteSpace(); } private async Task WhenRetrievingAnCompanyFilingHistoryForAnInvalidCompany() { - _result = await _client.GetFilingHistoryByTransactionAsync(InvalidCompanyNumber, InvalidTransactionId) - .ConfigureAwait(false); + _result = await _client.GetFilingHistoryByTransactionAsync(ValidCompanyNumber, ValidTransactionId) + ; } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestBase.cs new file mode 100644 index 0000000..889149a --- /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 CompaniesHouseResponse 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 ca73cda..0000000 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Threading.Tasks; -using CompaniesHouse.Response.Insolvency; -using NUnit.Framework; - -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); - } - - [SetUp] - public async Task WhenSearching() - { - _result = await _client.GetCompanyInsolvencyInformationAsync("08749409") - .ConfigureAwait(false); - } - - [Test] - public void TheItemsAreReturned() - { - Assert.That(_result.Data, Is.Not.Null); - } - } -} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs new file mode 100644 index 0000000..a59652e --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsInvalid.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using CompaniesHouse.Response.Insolvency; +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); + + [IntegrationFact] + public void ThenTheItemsAreNull() => Result.ShouldBeOfType.NotFound>(); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs new file mode 100644 index 0000000..685330f --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyInsolvencyInformationTests/CompanyInsolvencyInformationTestsValid.cs @@ -0,0 +1,26 @@ +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); + + [IntegrationFact] + public void ThenTheItemsAreReturned() => Result.Data.ShouldNotBeNull(); + + [IntegrationFact] + 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/CompanyProfileTests/CompanyProfileTestsBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsBase.cs index 6669c38..71f4dd3 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 CompaniesHouseResponse _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..205dabf 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsInvalid.cs @@ -1,30 +1,33 @@ -using System.Threading.Tasks; -using NUnit.Framework; +using System.Threading.Tasks; +using CompaniesHouse.Response.CompanyProfile; +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] + [IntegrationFact] public void ThenTheProfileIsNotReturned() { - Assert.That(_result.Data, Is.Null); + _result.ShouldBeOfType.NotFound>(); + _result.StatusCode.ShouldBe(404); } 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..ed9fb8b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValid.cs @@ -1,31 +1,75 @@ -using System.Threading.Tasks; -using NUnit.Framework; +using System.Threading.Tasks; +using CompaniesHouse.Response; +using CompaniesHouse.Response.CompanyProfile; +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] + [IntegrationFact] public void ThenTheProfileIsReturned() { - Assert.That(_result.Data.CompanyName, Is.Not.Empty); + _result.Data.CompanyName.ShouldNotBeEmpty(); + } + + [IntegrationFact] + 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); + } + + [IntegrationFact] + 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(); + } + + [IntegrationFact] + 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) - .ConfigureAwait(false); + ; } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValidForeignCompany.cs b/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValidForeignCompany.cs deleted file mode 100644 index ff884ad..0000000 --- a/tests/CompaniesHouse.IntegrationTests/Tests/CompanyProfileTests/CompanyProfileTestsValidForeignCompany.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Threading.Tasks; -using CompaniesHouse.Response.CompanyProfile; -using NUnit.Framework; - -namespace CompaniesHouse.IntegrationTests.Tests.CompanyProfileTests -{ - [TestFixture] - public class CompanyProfileTestsValidForeignCompany : CompanyProfileTestsBase - { - // Apple DISTRIBUTION company number, unlikely to go away soon - private const string ValidCompanyNumber = "FC031666"; - - [SetUp] - protected override async Task When() - { - await WhenRetrievingAValidCompanyProfile() - .ConfigureAwait(false); - } - - [Test] - public void ThenTheProfileIsReturned() - { - Assert.That(_result.Data.CompanyName, Is.Not.Empty); - } - - [Test] - public void ThenTheProfileForeignCompanyDetailsIsReturned() - { - Assert.That(_result.Data.ForeignCompanyDetails, Is.Not.Null); - Assert.That(_result.Data.ForeignCompanyDetails.AccountingRequirement.ForeignAccountType, - Is.EqualTo(ForeignAccountType.AccountingRequirementsOfOriginatingCountryApply)); - Assert.That(_result.Data.ForeignCompanyDetails.AccountingRequirement.TermsOfAccountPublication, - Is.EqualTo(TermsOfAccountPublication.AccountingPublicationDateDoesNotNeedToBeSuppliedByCompany)); - } - - - private async Task WhenRetrievingAValidCompanyProfile() - { - _result = await _client.GetCompanyProfileAsync(ValidCompanyNumber) - .ConfigureAwait(false); - } - } -} 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.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs index 0b7eeda..f20e5a4 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentDownloadTests.cs @@ -1,44 +1,47 @@ -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 CompaniesHouseResponse _result = null!; - [SetUp] + protected override async Task When() => await DownloadingDocument(); private async Task DownloadingDocument() => _result = await Client.DownloadDocumentAsync(DocumentId); - [Test] + [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); - 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 CompaniesHouseResponse _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); + [IntegrationFact] + 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 d9841d4..dc3a8c9 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); + [IntegrationFact] + public void ThenDocumentMetadataIsNull() => Result.ShouldBeOfType.NotFound>(); } } \ 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..6d0e2bf 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentMetadataTestsValid.cs @@ -1,24 +1,33 @@ -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"; + private const string DocumentId = "IHFGB_pcm7rSIRefsfuXK1MDkLFxrSoHbKKAgY7OTxk"; + - [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] + [IntegrationFact] 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(); + } + + [IntegrationFact] + public void ThenObservedFilenameAndDocumentLinkAreReturned() + { + Result.Data.Filename.ShouldNotBeNullOrWhiteSpace(); + Result.Data.Links?.Document.ShouldNotBeNullOrWhiteSpace(); } } } diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/DocumentTests/DocumentTestBase.cs index 6aaca7c..f7bdf09 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 CompaniesHouseResponse 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/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.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentSchemaTests.cs new file mode 100644 index 0000000..ed1bf86 --- /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 + { + [IntegrationFact] + 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/OfficerByAppointmentTestsInvalid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsInvalid.cs new file mode 100644 index 0000000..b73b826 --- /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); + + [IntegrationFact] + public void ThenTheDataIsNull() => Result.ShouldBeOfType.NotFound>(); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficerByAppointmentTestsValid.cs index 0af2a98..dc314dd 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] + [IntegrationFact] 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/OfficersSchemaTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs new file mode 100644 index 0000000..f9f9434 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersSchemaTests.cs @@ -0,0 +1,58 @@ +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 + { + [IntegrationFact] + 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 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(); + melissaBethell.IdentityVerificationDetails.AppointmentVerificationStartOn.ShouldBe(new DateTime(2026, 07, 01)); + melissaBethell.IdentityVerificationDetails.AppointmentVerificationEndOn.ShouldBe(new DateTime(9999, 12, 31)); + } + + [IntegrationFact] + 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 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 = informaItems.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.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestBase.cs index 640763c..5e88744 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 CompaniesHouseResponse 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 087b54d..1ace931 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/OfficerTests/OfficersTestsInvalid.cs @@ -1,33 +1,35 @@ -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] - public void ThenTheDataIsFullWithEmptyProperties() + [IntegrationFact] + public void ThenTheDataItemsAreEmpty() { - Assert.That(Result.Data.Items, Is.Empty); - Assert.That(Result.Data.ActiveCount, Is.EqualTo(0)); - Assert.That(Result.Data.ResignedCount,Is.EqualTo(0)); - Assert.That(Result.Data.StartIndex, Is.EqualTo(0)); - Assert.That(Result.Data.TotalResults, Is.EqualTo(0));; + // 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() { - 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..adb2653 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] + [IntegrationFact] 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/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.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestBase.cs index b85d617..643c06b 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 CompaniesHouseResponse _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 84a3837..701c5dc 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsInValid.cs @@ -1,32 +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 PersonsWithSignificantControlTestsInValid : PersonsWithSignificantControlTestBase { private const string InvalidCompanyNumber = "ABC00000"; - [SetUp] + protected override async Task When() { - await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany().ConfigureAwait(false); + await WhenRetrievingAnCompanyPersonsWithSignificantControlForAnInvalidCompany(); } - - [Test] - public void ThenTheDataIsFullWithEmptyProperties() + [IntegrationFact] + public void ThenTheDataItemsAreNull() { - Assert.That(_result.Data.Items, Is.Empty); - Assert.That(_result.Data.ActiveCount, Is.EqualTo(0)); - Assert.That(_result.Data.CeasedCount, Is.EqualTo(0)); + _result.Data.ShouldNotBeNull(); + _result.Data.Items.ShouldBeEmpty(); } 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..b3f9c32 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/PersonsWithSignificantControlTests/PersonsWithSignificantControlTestsValid.cs @@ -1,30 +1,41 @@ -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] + [IntegrationFact] public void ThenTheDataItemsAreNotEmpty() { - Assert.That(_result.Data.Items, Is.Not.Empty); + _result.Data.Items.ShouldNotBeEmpty(); + } + + [IntegrationFact] + public void ThenObservedCountsAndKindsAreReturned() + { + var items = _result.Data.Items ?? []; + + _result.Data.TotalResults.ShouldNotBeNull(); + _result.Data.TotalResults.Value.ShouldBeGreaterThan(0); + items.ShouldNotBeEmpty(); + items[0].Kind.Value.ShouldNotBeNullOrWhiteSpace(); } 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..4c9d176 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 CompaniesHouseResponse 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/RegisteredOfficeAddressesTestsInValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs new file mode 100644 index 0000000..04dbaca --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsInValid.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using CompaniesHouse.Response.RegisteredOfficeAddress; +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); + + [IntegrationFact] + public void ThenRegisteredOfficeAddressIsNull() => Result.ShouldBeOfType.NotFound>(); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs index b35e94e..929dc2a 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/RegisteredOfficeAddress/RegisteredOfficeAddressesTestsValid.cs @@ -1,16 +1,24 @@ 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); + + [IntegrationFact] + public void ThenRegisteredOfficeAddressIsNotNull() => Result.Data.ShouldNotBeNull(); + + [IntegrationFact] + 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/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.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs new file mode 100644 index 0000000..d35ee0a --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AdvancedCompanySearchTests.cs @@ -0,0 +1,62 @@ +using System.Threading.Tasks; +using CompaniesHouse.Request; +using CompaniesHouse.Response; +using System.Linq; +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)); + } + + [IntegrationFact] + 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(); + } + + [IntegrationFact] + 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); + } + + [IntegrationFact] + 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 61b8eda..c42b44b 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/AllSearchTests.cs @@ -1,42 +1,52 @@ -using System.Threading.Tasks; +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 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() + [IntegrationTheory] + [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); + (result.Data.Items ?? []).ShouldNotBeEmpty(); } - [SetUp] - public async Task WhenSearching() + [IntegrationFact] + public async Task ThenPagingAndMixedItemTypesAreReturned() { - _result = await _client.SearchAllAsync(new SearchAllRequest() { Query = _query }) - .ConfigureAwait(false); + 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); } - [Test] - public void ThenItemsAreReturned() + [IntegrationFact] + public async Task ThenCompanySpecificFieldsRoundTripFromSearchAll() { - Assert.That(_result.Data.Items, Is.Not.Empty); + 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 new file mode 100644 index 0000000..1d5ecc6 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompaniesAlphabeticalSearchTests.cs @@ -0,0 +1,53 @@ +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)); + } + + [IntegrationTheory] + [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(); + } + + [IntegrationFact] + 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/CompanySearchAdvancedTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchAdvancedTests.cs deleted file mode 100644 index fa77ba2..0000000 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchAdvancedTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Threading.Tasks; -using CompaniesHouse.Request; -using CompaniesHouse.Response.Search.AdvancedCompanySearch; -using CompaniesHouse.Response.Search.CompanySearch; -using NUnit.Framework; - -namespace CompaniesHouse.IntegrationTests.Tests.SearchingTests -{ - [TestFixture("brighouse")] - [TestFixture("British Gas")] - [TestFixture("Bay Horse")] - public class CompanySearchAdvancedTests - { - private readonly string _query; - private CompaniesHouseClient _client; - private CompaniesHouseClientResponse _result; - - public CompanySearchAdvancedTests(string query) - { - _query = query; - } - - [OneTimeSetUp] - public void GivenACompaniesHouseClient() - { - var settings = new CompaniesHouseSettings(Keys.ApiKey); - - _client = new CompaniesHouseClient(settings); - } - - [SetUp] - public async Task WhenSearchingForACompany() - { - _result = await _client.SearchCompanyAdvancedAsync( - new AdvancedSearchCompanyRequest - { - CompanyNameIncludes = _query, StartIndex = 0, ItemsPerPage = 100 - }) - .ConfigureAwait(false); - } - - [Test] - public void ThenCompaniesAreReturned() - { - Assert.That(_result.Data.Companies, Is.Not.Empty); - } - } -} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs index a0d39ca..282f117 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/CompanySearchTests.cs @@ -1,44 +1,61 @@ -using System; using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.CompanySearch; -using NUnit.Framework; +using System.Linq; +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() + [IntegrationTheory] + [InlineData("brighouse computers")] + [InlineData("British Gas")] + [InlineData("Bay Horse")] + public async Task ThenCompaniesAreReturned(string query) { - var settings = new CompaniesHouseSettings(Keys.ApiKey); + var result = await _client.SearchCompanyAsync(new SearchCompanyRequest { Query = query, StartIndex = 0, ItemsPerPage = 100 }); - _client = new CompaniesHouseClient(settings); + (result.Data.Companies ?? []).ShouldNotBeEmpty(); } - [SetUp] - public async Task WhenSearchingForACompany() + [IntegrationFact] + public async Task ThenForeignCompanyFieldsAreReturned() { - _result = await _client.SearchCompanyAsync(new SearchCompanyRequest() { Query = _query, StartIndex = 0, ItemsPerPage = 100 }) - .ConfigureAwait(false); + 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"]); } - [Test] - public void ThenCompaniesAreReturned() + [IntegrationFact] + public async Task ThenRestrictionsCanBeSentToTheLiveApi() { - Assert.That(_result.Data.Companies, Is.Not.Empty); + 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 2481ffb..a66e798 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DisqualifiedOfficersSearchTests.cs @@ -1,35 +1,37 @@ 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); + private readonly CompaniesHouseClient _client; - _client = new CompaniesHouseClient(settings); + public DisqualifiedOfficersSearchTests() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [SetUp] - public async Task WhenSearchingForADisqualifiedOfficers() + [IntegrationFact] + public async Task ThenDisqualifiedOfficersAreReturned() { - _result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest() { Query = "Kevin" }) - .ConfigureAwait(false); + var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "Kevin" }); + + (result.Data.DisqualifiedOfficers ?? []).ShouldNotBeEmpty(); } - [Test] - public void ThenDisqualifiedOfficersAreReturned() + [IntegrationFact] + public async Task ThenPagingMetadataAndDateOfBirthAreReturned() { - Assert.That(_result.Data.DisqualifiedOfficers, Is.Not.Empty); + var result = await _client.SearchDisqualifiedOfficerAsync(new SearchDisqualifiedOfficerRequest { Query = "john", ItemsPerPage = 20 }); + + result.Data.PageNumber.ShouldBe(1); + 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 new file mode 100644 index 0000000..194c2b0 --- /dev/null +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/DissolvedCompaniesSearchTests.cs @@ -0,0 +1,72 @@ +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)); + } + + [IntegrationTheory] + [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(); + } + + [IntegrationFact] + public async Task ThenPreviousNameSearchReturnsMatchedPreviousCompanyName() + { + var result = await SearchPreviousNamesAsync(); + + // Retry once on 5xx server error + if (result is CompaniesHouseResponse.ServerError) + { + result = await SearchPreviousNamesAsync(); + } + + result.Data.Kind.ShouldBe("search#previous-name-dissolved"); + result.Data.TopHit?.MatchedPreviousCompanyName.ShouldNotBeNull(); + result.Data.TopHit?.MatchedPreviousCompanyName?.Name.ShouldNotBeNull(); + result.Data.TopHit?.MatchedPreviousCompanyName?.Name!.ShouldContain("RADIO RENTALS"); + } + + [IntegrationFact] + 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)); + } + + private Task> SearchPreviousNamesAsync() => + _client.SearchDissolvedCompaniesAsync(new SearchDissolvedCompaniesRequest + { + Query = "radio rentals", + SearchType = "previous-name-dissolved", + Size = 10, + }); + } +} diff --git a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs index 68d7a8c..5575fce 100644 --- a/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs +++ b/tests/CompaniesHouse.IntegrationTests/Tests/SearchingTests/OfficersSearchTests.cs @@ -1,36 +1,38 @@ -using System; using System.Threading.Tasks; using CompaniesHouse.Request; using CompaniesHouse.Response.Search.OfficerSearch; -using NUnit.Framework; +using System.Linq; +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); + private readonly CompaniesHouseClient _client; - _client = new CompaniesHouseClient(settings); + public OfficersSearchTests() + { + _client = new CompaniesHouseClient(new CompaniesHouseSettings(Keys.ApiKey)); } - [SetUp] - public async Task WhenSearchingForAOfficer() + [IntegrationFact] + public async Task ThenOfficersAreReturned() { - _result = await _client.SearchOfficerAsync(new SearchOfficerRequest() { Query = "Kevin" }) - .ConfigureAwait(false); + var result = await _client.SearchOfficerAsync(new SearchOfficerRequest { Query = "Kevin" }); + + (result.Data.Officers ?? []).ShouldNotBeEmpty(); } - [Test] - public void ThenOfficersAreReturned() + [IntegrationFact] + public async Task ThenLiveOfficerBirthMonthAndPagingMetadataAreReturned() { - Assert.That(_result.Data.Officers, Is.Not.Empty); + 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.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.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.ScenarioTests/AppointmentsAndPscScenarios.cs b/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs new file mode 100644 index 0000000..2a27519 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/AppointmentsAndPscScenarios.cs @@ -0,0 +1,85 @@ +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); + var items = value?.Items ?? []; + + value.ShouldNotBeNull(); + value.IsCorporateOfficer.ShouldBeTrue(); + items.ShouldNotBeEmpty(); + 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); + var items = value?.Items ?? []; + + value.ShouldNotBeNull(); + value.TotalResults.ShouldBe(1); + 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/CompaniesHouse.ScenarioTests.csproj b/tests/CompaniesHouse.ScenarioTests/CompaniesHouse.ScenarioTests.csproj index 4323627..4f85ee5 100644 --- a/tests/CompaniesHouse.ScenarioTests/CompaniesHouse.ScenarioTests.csproj +++ b/tests/CompaniesHouse.ScenarioTests/CompaniesHouse.ScenarioTests.csproj @@ -1,17 +1,18 @@ - net9.0 + net10.0 false latest - - - - - + + + + + + diff --git a/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/CompanyProfileDeserializationScenarioTests.cs new file mode 100644 index 0000000..435769f --- /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.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.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.ScenarioTests/FilingAndChargesScenarios.cs b/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs new file mode 100644 index 0000000..9e846d8 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/FilingAndChargesScenarios.cs @@ -0,0 +1,76 @@ +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); + var items = value?.Items ?? []; + + value.ShouldNotBeNull(); + value.UnfilteredCount.ShouldBe(1); + 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 new file mode 100644 index 0000000..ed800aa --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/InsolvencyScenarios.cs @@ -0,0 +1,43 @@ +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); + var cases = value?.Cases ?? []; + + value.ShouldNotBeNull(); + value.Status.ShouldBe([new InsolvencyStatus("liquidation")]); + 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/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/OfficersDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs new file mode 100644 index 0000000..aae1b5f --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/OfficersDeserializationScenarioTests.cs @@ -0,0 +1,156 @@ +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); + 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] + 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(); + 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 = """ + { + "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.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.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs b/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs new file mode 100644 index 0000000..1ba4bc1 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/RegisteredOfficeAndDocumentsScenarios.cs @@ -0,0 +1,64 @@ +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.ShouldNotBeNull(); + value.Resources["application/pdf"].ContentLength.ShouldBe(82803); + } + } +} 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.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests.cs index a6bc44d..ee3ee71 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,33 @@ 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 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); + foundOfficer.OfficerId.ShouldNotBeNullOrWhiteSpace(); - var officerAppointments = await _client.GetAppointmentsAsync(foundOfficer.OfficerId) - .ConfigureAwait(false); + 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) - .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/SearchResponseDeserializationScenarioTests.cs b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs new file mode 100644 index 0000000..db2db15 --- /dev/null +++ b/tests/CompaniesHouse.ScenarioTests/SearchResponseDeserializationScenarioTests.cs @@ -0,0 +1,379 @@ +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); + var allItems = payload.Items ?? []; + allItems.Length.ShouldBe(3); + allItems[0].ShouldBeOfType(); + allItems[2].ShouldBeOfType(); + } + + [Fact] + public void CompanySearchPayload_DeserializesAddressSnippetAndExternalRegistrationNumber() + { + var payload = JsonSerializer.Deserialize(CompanySearchJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + payload.PageNumber.ShouldBe(1); + 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] + public void OfficerSearchPayload_DeserializesPageNumberAndOptionalDateOfBirth() + { + var payload = JsonSerializer.Deserialize(OfficerSearchJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + payload.PageNumber.ShouldBe(1); + 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] + public void AdvancedCompanySearchPayload_DeserializesOptionalSubtypeAndSicCodes() + { + var payload = JsonSerializer.Deserialize(AdvancedCompanySearchJson, CompaniesHouseJsonSerializerOptions.Default); + + payload.ShouldNotBeNull(); + 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] + 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.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.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.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..cec1970 --- /dev/null +++ b/tests/CompaniesHouse.SourceGenerator.Tests/EnumValueTypeGeneratorTests.cs @@ -0,0 +1,178 @@ +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.Value] = \"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.Value] = \"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"); + } + + [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(); + 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"); + } + } +} diff --git a/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs b/tests/CompaniesHouse.SourceGenerator.Tests/ValueTypeEmitterSnapshotTests.cs new file mode 100644 index 0000000..f2f6535 --- /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.Value, + Off.Value, + }; + + private static readonly IReadOnlyDictionary Descriptions = new Dictionary(StringComparer.Ordinal) + { + [On.Value] = "On", + [Off.Value] = "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()); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj b/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj index c62f1a1..411e2db 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj +++ b/tests/CompaniesHouse.Tests/CompaniesHouse.Tests.csproj @@ -1,31 +1,20 @@  - net9.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/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/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 b718c57..7c4c5a4 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseChargesClientTests/CompaniesHouseChargesClientTests.cs @@ -2,18 +2,19 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using CompaniesHouse.Response; 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 +27,16 @@ 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", "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)); + } } - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task GivenACompaniesHouseChargesClient_WhenGettingCompanyChargeById(CompaniesHouseChargesClientTestCase testCase) { var charge = CompanyChargesBuilder.CreateOne(testCase); @@ -42,10 +49,12 @@ 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 { @@ -82,7 +91,7 @@ private static CompaniesHouseChargesClientTestCase[] TestCases() ClassificationChargeType = x, Status = EnumerationMappings.PossibleChargeStatuses.Keys.First() }); - + var allChargeStatuses = EnumerationMappings.PossibleChargeStatuses.Keys.Select(x => new CompaniesHouseChargesClientTestCase { AssetsCeasedReleased = EnumerationMappings.PossibleAssetsCeasedReleased.Keys.First(), @@ -97,7 +106,52 @@ private static CompaniesHouseChargesClientTestCase[] TestCases() .Concat(allSecuredDetailTypes) .Concat(allClassificationChargeTypes) .Concat(allChargeStatuses) - .ToArray(); + .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/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/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs index eb0b133..da62270 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyFilingHistoryClientTests/CompaniesHouseCompanyFilingHistoryClientTests.cs @@ -1,19 +1,22 @@ using System; +using System.Collections.Generic; 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 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 +35,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 +56,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 +101,41 @@ public static CompaniesHouseCompanyFilingHistoryClientTestCase[] TestCases() .Concat(allFilingSubcategories) .Concat(allFilingHistoryStatus) .Concat(allFilingResolutionCategories) - .ToArray(); + .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..c3bc06a --- /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/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 40a1503..822ed63 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompaniesHouseCompanyProfileClientTests.cs @@ -1,21 +1,32 @@ -using CompaniesHouse.Tests.ResourceBuilders; +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 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; + private CompaniesHouseCompanyProfileClient _client = null!; - private CompaniesHouseClientResponse _result; - private ResourceBuilders.CompanyProfile _companyProfile; + private CompaniesHouseResponse _result = null!; + private ResourceBuilders.CompanyProfile _companyProfile = null!; - [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) @@ -31,13 +42,54 @@ 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); } + [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.ShouldBeOfType.NotFound>(); + _result.StatusCode.ShouldBe(404); + } - public static CompaniesHouseCompanyProfileClientTestCase[] TestCases() + + public static IEnumerable TestCases() { var allLastAccountsTypes = EnumerationMappings.PossibleLastAccountsTypes.Keys .Select(x => new CompaniesHouseCompanyProfileClientTestCase @@ -93,8 +145,33 @@ public static CompaniesHouseCompanyProfileClientTestCase[] TestCases() .Concat(allCompanyStatusDetails) .Concat(allJurisdictions) .Concat(allCompanyTypes) - .ToArray(); + .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/CompaniesHouseCompanyProfileClientTests/CompanyProfileBuilder.cs b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompanyProfileBuilder.cs index f810d30..20c7716 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompanyProfileBuilder.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseCompanyProfileClientTests/CompanyProfileBuilder.cs @@ -1,4 +1,13 @@ +using System.Linq; +using CompaniesHouse.Response.CompanyProfile; using AutoFixture; +using Accounts = CompaniesHouse.Tests.ResourceBuilders.Accounts; +using AnnualReturn = CompaniesHouse.Tests.ResourceBuilders.AnnualReturn; +using CompanyProfile = CompaniesHouse.Tests.ResourceBuilders.CompanyProfile; +using ConfirmationStatement = CompaniesHouse.Tests.ResourceBuilders.ConfirmationStatement; +using LastAccounts = CompaniesHouse.Tests.ResourceBuilders.LastAccounts; +using Officer = CompaniesHouse.Tests.ResourceBuilders.Officer; +using OfficerSummary = CompaniesHouse.Tests.ResourceBuilders.OfficerSummary; namespace CompaniesHouse.Tests.CompaniesHouseCompanyProfileClientTests { @@ -54,15 +63,6 @@ public ResourceBuilders.CompanyProfile Build(CompaniesHouseCompanyProfileClientT var officerSummary = fixture.Build().With(x => x.Officers, officers).Create(); - var accountingRequirement = fixture.Build() - .With(x => x.ForeignAccountType, "") - .With(x => x.TermsOfAccountPublication, "") - .Create(); - - var foreignCompanyDetails = fixture.Build() - .With(x => x.AccountingRequirement, accountingRequirement) - .Create(); - var companyProfile = fixture.Build() .With(x => x.Accounts, accounts) .With(x => x.CompanyStatus, testCase.CompanyStatus) @@ -71,7 +71,6 @@ public ResourceBuilders.CompanyProfile Build(CompaniesHouseCompanyProfileClientT .With(x => x.Type, testCase.Type) .With(x => x.OfficerSummary, officerSummary) .With(x => x.PreviousCompanyNames, previousCompanyNames) - .With(x => x.ForeignCompanyDetails, foreignCompanyDetails) .Create(); return companyProfile; 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/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs index 1546c58..462af15 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentClientTests/CompaniesHouseDocumentClientTests.cs @@ -1,25 +1,23 @@ -using System; +using System; using System.IO; using System.Net.Http; 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 CompaniesHouseResponse _result = null!; 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,17 @@ 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); + _result.Data.Content.ShouldNotBeNull(); + 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..de2c3a6 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/CompaniesHouseDocumentMetadataClientTests.cs @@ -1,23 +1,21 @@ -using System; +using System; using System.Collections.Generic; 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; + private CompaniesHouseResponse _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,47 @@ 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); + } + + [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.ShouldNotBeNull(); + 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..842a6d3 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseDocumentMetadataClientTests/DocumentMetadataTestCase.cs @@ -1,30 +1,30 @@ -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 { - public int ContentLength { get; set; } + public long ContentLength { get; set; } } 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/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/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/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs index 2104d5e..8e276ee 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersAppointmentClientTests/CompaniesHouseOfficersAppointmentClientTests.cs @@ -1,44 +1,95 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using CompaniesHouse.Response.Officers; 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); 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"); - - 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 }); + + [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 f9718bd..73b7d3e 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseOfficersTests/CompaniesHouseCompanyOfficersClientTests.cs @@ -1,24 +1,25 @@ -using System; +using System; using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Response.Officers; 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; + private CompaniesHouseOfficersClient _client = null!; - private CompaniesHouseClientResponse _result; - private ResourceBuilders.Officers _officers; + private CompaniesHouseResponse _result = null!; + private ResourceBuilders.Officers _officers = null!; - [Test] - public void GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile() + [Fact] + public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile() { _officers = new OfficersBuilder().Build(); var resource = new OfficersResourceBuilder(_officers).Create(); @@ -28,14 +29,151 @@ public void GivenACompaniesHouseCompanyProfileClient_WhenGettingACompanyProfile( 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); - _result = _client.GetOfficersAsync("abc", 0, 25).Result; + _result = await _client.GetOfficersAsync("abc", 0, 25); - _result.Data.ShouldBeEquivalentTo(_officers); + 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); + var items = result.Data.Items ?? []; + items.Length.ShouldBe(2); + + var officer = 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(); + 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 = """ + { + "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/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/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs index daae6c7..bf36fd1 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHousePersonsWithSignificantControlTests/CompaniesHousePersonsWithSignificantControlTests.cs @@ -1,24 +1,25 @@ -using System; +using System; using System.Net.Http; +using System.Threading.Tasks; +using CompaniesHouse.Response.PersonsWithSignificantControl; 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; + private CompaniesHousePersonsWithSignificantControlClient _client = null!; - private CompaniesHouseClientResponse _result; - private ResourceBuilders.PersonsWithSignificantControl _personsWithSignificantControl; + private CompaniesHouseResponse _result = null!; + private ResourceBuilders.PersonsWithSignificantControl _personsWithSignificantControl = null!; - [Test] - public void GivenACompaniesHouseCompanyProfileClient_WhenGettingPersonsWithSignificantControl() + [Fact] + public async Task GivenACompaniesHouseCompanyProfileClient_WhenGettingPersonsWithSignificantControl() { _personsWithSignificantControl = new PersonsWithSignificantControlBuilder().Build(); var resource = new PersonsWithSignificantControlResourceBuilder(_personsWithSignificantControl).Create(); @@ -33,9 +34,52 @@ 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); + } + + [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/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/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseRegisteredOfficeAddressTests/CompaniesHouseRegisteredOfficeAddressTests.cs index 99ef9ae..0bb08f9 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.Response.RegisteredOfficeAddress; 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,29 +32,48 @@ public async Task GivenACompaniesHouseRegistereOfficeAddressClient_WhenGettingAR var result = await client.GetRegisteredOfficeAddress("abc"); - result.Data.ShouldBeEquivalentTo(registeredOfficeAddress, opt => opt.Excluding(x => x.Country)); - - result.Data.Country.GetEnumMemberValue().Should().Be(registeredOfficeAddress.Country); + EquivalencyAssertionExtensions.ShouldBeEquivalentTo((object)result.Data, registeredOfficeAddress); } - 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 - { - 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/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/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs new file mode 100644 index 0000000..1e876f8 --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForAdvancedCompanySearch.cs @@ -0,0 +1,88 @@ +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 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.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..4ab92aa --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompaniesAlphabeticallySearch.cs @@ -0,0 +1,64 @@ +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"); + 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.OrderedAlphaKeyWithId.ShouldBe("ABC LIMITED:01234567"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs index 5c456a1..51ae6ec 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearch.cs @@ -1,24 +1,23 @@ -using AutoFixture; +using AutoFixture; using CompaniesHouse.Request; 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; - private CompaniesHouseClientResponse _result; + private CompaniesHouseResponse _result; private ResourceDetails _resourceDetails; 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, () => null!) .With(x => x.CompanyType, "private-unlimited").With(x => x.Kind, "searchresults#company").Create(), }; @@ -68,90 +67,97 @@ 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 companies = _result.Data.Companies ?? []; 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)); + 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.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); } - [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() { + var companies = _result.Data.Companies ?? []; 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)); + 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.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); } } 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/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs index c5e3ecf..5e36ec5 100644 --- a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForCompanySearchWithTooManyRequests.cs @@ -1,22 +1,20 @@ -using System; +using System; using System.Net.Http; using System.Threading.Tasks; 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; + private CompaniesHouseResponse? _response; - [OneTimeSetUp] - public async Task GivenACompanyHouseSearchCompanyClient_WhenSearchingForACompanyAndApiReturnsTooManyRequests() + public async Task InitializeAsync() { var uri = new Uri("https://wibble.com/search/companies"); @@ -27,23 +25,17 @@ public async Task GivenACompanyHouseSearchCompanyClient_WhenSearchingForACompany 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()); } - [Test] - public void ThenExceptionIsThrown() - { - _caughtException.Should().BeOfType(); + public Task DisposeAsync() => Task.CompletedTask; - _caughtException.As().Message.Should().StartWith("Response status code does not indicate success: 429"); + [Fact] + public void ThenUnsuccessfulResponseIsReturned() + { + _response.ShouldNotBeNull(); + _response.ShouldBeOfType.RateLimited>(); + _response.StatusCode.ShouldBe(429); } - } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs new file mode 100644 index 0000000..6bdc7ac --- /dev/null +++ b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForDissolvedCompaniesSearch.cs @@ -0,0 +1,107 @@ +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 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"); + } + } +} diff --git a/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs b/tests/CompaniesHouse.Tests/CompaniesHouseSearchClientTests/CompaniesHouseSearchClientTestsForOfficerSearch.cs index f96a8ef..c4c8e2b 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; + private CompaniesHouseSearchClient _client = null!; + private CompaniesHouseResponse _result = null!; + private ResourceDetails _resourceDetails = null!; - [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/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/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..9c6f605 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; + private CompaniesHouseAuthorizationHandler _handler = null!; + private string _apiKey = null!; + private HttpRequestMessage _actual = null!; - [OneTimeSetUp] - public void GivenACompaniesHouseAuthorizationHandler() + public CompaniesHouseAuthorizationHandlerTests() { var innerHandler = new Mock(MockBehavior.Strict); innerHandler.Protected() @@ -29,20 +28,16 @@ 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.ShouldNotBeNull(); + _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 f2b777c..6c3f618 100644 --- a/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs +++ b/tests/CompaniesHouse.Tests/DescriptionTests/DescriptionProviderTests.cs @@ -1,102 +1,58 @@ using CompaniesHouse.Description; -using FluentAssertions; -using Newtonsoft.Json.Linq; -using NUnit.Framework; +using System.Text.Json; +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 values = JsonDocument.Parse(@"{ ""variable"": ""Value"" }").RootElement; 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 values = JsonDocument.Parse(@"{ ""variable1"": ""Value1"", ""variable2"": ""Value2"" }").RootElement; 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 values = JsonDocument.Parse(@"{ ""otherVariable"": ""Value"" }").RootElement; 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 values = JsonDocument.Parse(@"{ ""parent"": { ""variable"": ""Value"" }}").RootElement; 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}"); - } - - [Test] - public void GivenFormatAndMatchingDateVariableNoDateFormat() - { - var format = "some value: {variable}"; - var values = JObject.Parse(@"{ ""variable"": ""2024-11-28"" }"); - var result = DescriptionProvider.GetDescription(format, values); - - result.Should().Be(@"some value: 2024-11-28"); - } - - [Test] - public void GivenFormatAndMatchingDateVariableWithDateFormat() - { - var format = "some value: {variable}"; - var values = JObject.Parse(@"{ ""variable"": ""2024-11-28"" }"); - var dateFormat = "dd-MMM-yyyy"; - var result = DescriptionProvider.GetDescription(format, values, dateFormat); - - result.Should().Be(@"some value: 28-Nov-2024"); - } - - [Test] - public void GivenFormatAndInvalidDateVariableWithDateFormat() - { - var format = "some value: {variable}"; - var values = JObject.Parse(@"{ ""variable"": ""2024-20-28"" }"); - var dateFormat = "dd-MMM-yyyy"; - var result = DescriptionProvider.GetDescription(format, values, dateFormat); - - result.Should().Be(@"some value: 2024-20-28"); - } - - [Test] - public void GivenFormatAndIsNotDateVariableWithDateFormat() - { - var format = "some value: {variable}"; - var values = JObject.Parse(@"{ ""variable"": ""Value"" }"); - var dateFormat = "dd-MMM-yyyy"; - var result = DescriptionProvider.GetDescription(format, values, dateFormat); - - result.Should().Be(@"some value: Value"); + result.ShouldBe(@"some value: {nullvariable}"); } } } diff --git a/tests/CompaniesHouse.Tests/EnumerationMappings.cs b/tests/CompaniesHouse.Tests/EnumerationMappings.cs index 1b9c0a5..0dd1200 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 @@ -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}, @@ -117,8 +119,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,152 +136,142 @@ 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}, }; 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}, - {"investment-company", FilingSubcategory.InvestmentCompany}, + {"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 PossibleTermsOfAccountPublication = new Dictionary - { - {"", TermsOfAccountPublication.None}, - {"accounts-publication-date-supplied-by-company", TermsOfAccountPublication.AccountsPublicationDateSuppliedByCompany}, - {"accounting-publication-date-does-not-need-to-be-supplied-by-company", TermsOfAccountPublication.AccountingPublicationDateDoesNotNeedToBeSuppliedByCompany}, - {"accounting-reference-date-allocated-by-companies-house", TermsOfAccountPublication.AccountingReferenceDateAllocatedByCompaniesHouse}, - - }; - public static readonly IReadOnlyDictionary PossibleForeignAccountTypes = new Dictionary - { - {"", ForeignAccountType.None}, - {"accounting-requirements-of-originating-country-apply", ForeignAccountType.AccountingRequirementsOfOriginatingCountryApply}, - {"accounting-requirements-of-originating-country-do-not-apply", ForeignAccountType.AccountingRequirementsOfOriginatingCountryDoNotApply}, - }; 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/EquivalencyAssertionExtensions.cs b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs new file mode 100644 index 0000000..8e0a55d --- /dev/null +++ b/tests/CompaniesHouse.Tests/EquivalencyAssertionExtensions.cs @@ -0,0 +1,199 @@ +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/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. + /// + 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/string-backed-value-type <-> 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; + } + + 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) + { + 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; + } + + if (TryGetStringBackedValue(actual, out var actualRawValueFromValueType) && expected is string expectedRawValue) + { + return actualRawValueFromValueType == expectedRawValue; + } + + 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() : 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/Extensions/HttpResponseMessageExtensionsTests.cs b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs index 547af01..4b7bcc0 100644 --- a/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs +++ b/tests/CompaniesHouse.Tests/Extensions/HttpResponseMessageExtensionsTests.cs @@ -1,65 +1,140 @@ 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 NUnit.Framework; + using Shouldly; + using Xunit; - [TestFixture] public class HttpResponseMessageExtensionsTests { - [Test] - public void GivenAnHttpResponse_WhenTheStatusCodeIsSuccess_ThenEnsureSuccessStatusCode2ReturnsTheHttpResponse() + [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)200); - var responseMessage = sut.EnsureSuccessStatusCode2(); - Assert.AreEqual(responseMessage, sut); - } + 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); } - [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)] - public void GivenAnHttpResponse_WhenTheStatusCodeIsNotSuccess_ThenEnsureSuccessStatusCode2ThrowsHttpRequestExceptionWithData( - int statusCode, - string reasonPhrase, - int retryAfterSeconds, - string retryAfterDate) + [Fact] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs404_ThenReturnsNotFound() { - 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.NotFound) { ReasonPhrase = "Not Found" }; - if (retryAfterSeconds >= 0 || !string.IsNullOrWhiteSpace(retryAfterDate)) - { - sut.Headers.RetryAfter = string.IsNullOrWhiteSpace(retryAfterDate) - ? new RetryConditionHeaderValue(TimeSpan.FromSeconds(retryAfterSeconds)) - : new RetryConditionHeaderValue(retryAfterDateTimeOffset); - } + var response = await sut.ToCompaniesHouseResponseAsync(); - var exception = Assert.Throws(() => sut.EnsureSuccessStatusCode2()); - Assert.AreEqual(statusCode, exception.Data["StatusCode"]); - Assert.AreEqual(reasonPhrase, exception.Data["ReasonPhrase"]); + var notFound = response.ShouldBeOfType.NotFound>(); + notFound.StatusCode.ShouldBe(404); + notFound.ReasonPhrase.ShouldBe("Not Found"); + } - if (retryAfterSeconds >= 0 || !string.IsNullOrWhiteSpace(retryAfterDate)) - { - Assert.AreEqual(string.IsNullOrWhiteSpace(retryAfterDate) - ? retryAfterSeconds.ToString() - : retryAfterDateTimeOffset.ToString("R"), - exception.Data["RetryAfter"]); - } - else - { - Assert.AreEqual(null, exception.Data["RetryAfter"]); - } + [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(401)] + [InlineData(403)] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs401Or403_ThenReturnsUnauthorized(int statusCode) + { + var sut = new HttpResponseMessage((HttpStatusCode)statusCode); + + var response = await sut.ToCompaniesHouseResponseAsync(); + + var unauthorized = response.ShouldBeOfType.Unauthorized>(); + unauthorized.StatusCode.ShouldBe(statusCode); + } + + [Theory] + [InlineData(500)] + [InlineData(503)] + public async Task GivenAnHttpResponse_WhenTheStatusCodeIs5xx_ThenReturnsServerError(int statusCode) + { + var sut = new HttpResponseMessage((HttpStatusCode)statusCode) { ReasonPhrase = "Server Error" }; + + 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 + { + public string? Value { get; set; } } } } + diff --git a/tests/CompaniesHouse.Tests/Initializer.cs b/tests/CompaniesHouse.Tests/Initializer.cs deleted file mode 100644 index 9a0d915..0000000 --- a/tests/CompaniesHouse.Tests/Initializer.cs +++ /dev/null @@ -1,38 +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>(); - 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..8230b28 100644 --- a/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs +++ b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsBase.cs @@ -1,30 +1,17 @@ -using System.IO; -using CompaniesHouse.JsonConverters; using CompaniesHouse.Response; -using Newtonsoft.Json; -using NUnit.Framework; +using System.Text.Json; namespace CompaniesHouse.Tests.JsonConverters.FilingSubcategoryConverterTests { - [TestFixture] public abstract class StringArrayOrFieldEnumConverterTestsBase { - private StringArrayOrFieldEnumConverter _convertor; - protected object Result; + protected FilingSubcategory[] 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(); - 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/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs b/tests/CompaniesHouse.Tests/JsonConverters/FilingSubcategoryConverterTests/StringArrayOrFieldEnumConverterTestsForMultipleValues.cs index 6336502..c864619 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[] { 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 e0626d6..59b6766 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[] { new FilingSubcategory("change") }); } } diff --git a/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs b/tests/CompaniesHouse.Tests/JsonConverters/OptionalDateJsonConverterTests/OptionalDateJsonConverterTestsForUnknownValue.cs index 27160a5..d3c72d1 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 System; +using System.Text; +using System.Text.Json; +using Shouldly; +using Xunit; namespace CompaniesHouse.Tests.JsonConverters.OptionalDateJsonConverterTests { - [TestFixture] public class OptionalDateJsonConverterTestsForUnknownValue { - private OptionalDateJsonConverter _convertor; - private object _result; + private readonly DateTime? _result; - [OneTimeSetUp] - public void GivenADateOfCessationJsonConverter() + public OptionalDateJsonConverterTestsForUnknownValue() { - _convertor = new OptionalDateJsonConverter(); + var converter = new OptionalDateJsonConverter(); + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(@"""Unknown""")); + reader.Read(); + _result = converter.Read(ref reader, typeof(DateTime?), CompaniesHouseJsonSerializerOptions.Default); } - [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/MapProviders/SecureDetailTypeMapProvider.cs b/tests/CompaniesHouse.Tests/MapProviders/SecureDetailTypeMapProvider.cs index 8694512..1dea77f 100644 --- a/tests/CompaniesHouse.Tests/MapProviders/SecureDetailTypeMapProvider.cs +++ b/tests/CompaniesHouse.Tests/MapProviders/SecureDetailTypeMapProvider.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using CompaniesHouse.Response; -using CompaniesHouse.Response.CompanyProfile; namespace CompaniesHouse.Tests.MapProviders { @@ -8,14 +7,4 @@ public class SecureDetailTypeMapProvider : IEnumDataMapProvider Map => EnumerationMappings.PossibleSecuredDetailTypes; } - - public class ForeignAccountTypeMapProvider : IEnumDataMapProvider - { - public IReadOnlyDictionary Map => EnumerationMappings.PossibleForeignAccountTypes; - } - - public class TermsOfAccountPublicationMapProvider : IEnumDataMapProvider - { - public IReadOnlyDictionary Map => EnumerationMappings.PossibleTermsOfAccountPublication; - } } \ No newline at end of file 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/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/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 aeb8712..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,82 +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 ForeignCompanyDetails ForeignCompanyDetails { get; set; } - } - - public class ForeignCompanyDetails - { - public AccountingRequirement AccountingRequirement { get; set; } - - public ForeignCompanyAccounts Accounts { get; set; } - - public string BusinessActivity { get; set; } - - public string CompanyType { get; set; } - - public string GovernedBy { get; set; } - - public bool? IsACreditFinanceInstitution { get; set; } - - public OriginatingRegistry OriginatingRegistry { get; set; } - - public string RegistrationNumber { get; set; } - } - - public class OriginatingRegistry - { - public string Country { get; set; } - - public string Name { get; set; } - } - - public class ForeignCompanyAccounts - { - public ForeignCompanyPeriodFrom AccountPeriodFrom { get; set; } - - public ForeignCompanyPeriodTo AccountPeriodTo { get; set; } - - public MustFileWithin MustFileWithin { get; set; } - - } - - public class MustFileWithin - { - public int? Months { get; set; } - } - - public class ForeignCompanyPeriodTo - { - public int? Day { get; set; } - - public int? Month { get; set; } - } - - public class ForeignCompanyPeriodFrom - { - public int? Day { get; set; } - - public int? Month { get; set; } - } - - public class AccountingRequirement - { - public string ForeignAccountType { get; set; } - - public string TermsOfAccountPublication { 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 9d2b016..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")}"", @@ -105,35 +105,7 @@ public string Create() {string.Join(",", _companyProfile.SicCodes.Select(x => $@"""{x}"""))} ], ""type"" : ""{_companyProfile.Type}"", - ""undeliverable_registered_office_address"" : {_companyProfile.UndeliverableRegisteredOfficeAddress.ToString().ToLower()}, - ""foreign_company_details"" : {{ - ""accounting_requirement"": {{ - ""foreign_account_type"": ""{_companyProfile.ForeignCompanyDetails.AccountingRequirement.ForeignAccountType}"", - ""terms_of_account_publication"": ""{_companyProfile.ForeignCompanyDetails.AccountingRequirement.TermsOfAccountPublication}"" - }}, - ""accounts"": {{ - ""account_period_from:"": {{ - ""day"": ""{_companyProfile.ForeignCompanyDetails.Accounts.AccountPeriodFrom.Day}"", - ""month"": ""{_companyProfile.ForeignCompanyDetails.Accounts.AccountPeriodFrom.Month}"" - }}, - ""account_period_to"": {{ - ""day"": ""{_companyProfile.ForeignCompanyDetails.Accounts.AccountPeriodTo.Day}"", - ""month"": ""{_companyProfile.ForeignCompanyDetails.Accounts.AccountPeriodTo.Month}"" - }}, - ""must_file_within"": {{ - ""months"": ""{_companyProfile.ForeignCompanyDetails.Accounts.MustFileWithin.Months}"", - }} - }}, - ""business_activity"": ""{_companyProfile.ForeignCompanyDetails.BusinessActivity}"", - ""company_type"": ""{_companyProfile.ForeignCompanyDetails.CompanyType}"", - ""governed_by"": ""{_companyProfile.ForeignCompanyDetails.GovernedBy}"", - ""is_a_credit_finance_institution"": {_companyProfile.ForeignCompanyDetails.IsACreditFinanceInstitution.ToString().ToLower()}, - ""originating_registry"": {{ - ""country"": ""{_companyProfile.ForeignCompanyDetails.OriginatingRegistry.Country}"", - ""name"": ""{_companyProfile.ForeignCompanyDetails.OriginatingRegistry.Name}"" - }}, - ""registration_number"": ""{_companyProfile.ForeignCompanyDetails.RegistrationNumber}"" - }} + ""undeliverable_registered_office_address"" : {_companyProfile.UndeliverableRegisteredOfficeAddress.ToString().ToLower()} }}"; } diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs index cba888c..7a4b5e9 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/CompanySearchResource/CompanyDetails.cs @@ -4,42 +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; } = 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; } = null!; + + public string Kind { get; set; } = null!; - public string Kind { get; set; } + public string LinksSelf { get; set; } = null!; - public string LinksSelf { get; set; } + public string Snippet { get; set; } = null!; - public string Snippet { get; set; } + public string Title { get; set; } = null!; - public string Title { 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/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)} ] 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/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/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 509b314..3febeb4 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/Officer.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/Officer.cs @@ -10,26 +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 string PersonNumber { 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 22532dd..76165fb 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,8 +41,9 @@ public static string CreateSingle(Officer officer) ""year"" : {officer.DateOfBirth.Year} }}, ""links"" : {{ + {selfProperty} ""officer"" : {{ - ""appointments"" : ""{officer.Links.Officer.AppointmentsResource}"" + ""appointments"" : ""{officer.Links?.Officer?.AppointmentsResource}"" }} }}, ""name"" : ""{officer.Name}"", @@ -66,8 +71,7 @@ public static string CreateSingle(Officer officer) ""legal_form"": ""{officer.Identification.LegalForm}"", ""place_registered"": ""{officer.Identification.PlaceRegistered}"", ""registration_number"": ""{officer.Identification.RegistrationNumber}"" - }}, - ""person_number"" : ""{officer.PersonNumber}"" + }} }}"; } 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 321de48..ad8559e 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControl.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControl.cs @@ -1,17 +1,11 @@ -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; } - - public int ItemsPerPage { get; set; } - - public int StartIndex { get; set; } - - public int TotalResults { get; set; } } } \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControlResourceBuilder.cs b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControlResourceBuilder.cs index 54dd3ba..8b1e0b1 100644 --- a/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControlResourceBuilder.cs +++ b/tests/CompaniesHouse.Tests/ResourceBuilders/PersonsWithSignificantControlResourceBuilder.cs @@ -20,10 +20,7 @@ public string Create() ""items"" : [ {string.Join(",", _personsWithSignificantControl.Items.Select(GetPersonWithSignificantControlJsonBlock).ToArray())} ], - ""ceased_count"" : {_personsWithSignificantControl.CeasedCount}, - ""items_per_page"" : {_personsWithSignificantControl.ItemsPerPage}, - ""start_index"" : {_personsWithSignificantControl.StartIndex}, - ""total_results"" : {_personsWithSignificantControl.TotalResults} + ""ceased_count"" : {_personsWithSignificantControl.CeasedCount} }}"; } 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/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/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/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/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); + } + } +} 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/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/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/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/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.)"); + } + } +} 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/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/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 { diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsBase+Thens.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsBase+Thens.cs deleted file mode 100644 index 38216d8..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsBase+Thens.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - public abstract partial class AdvancedSearchCompanyUriBuilderTestsBase - { - public class Thens - { - private readonly AdvancedSearchCompanyUriBuilderTestsBase _testBase; - - public Thens(AdvancedSearchCompanyUriBuilderTestsBase testBase) - { - _testBase = testBase; - } - - public void TheUriQueryStringDoesNotContainsTheItemsPerPage() - { - var query = GetQuery(); - Assert.That(query.Contains("items_per_page"), Is.False); - } - - public void TheUriQueryStringContainsTheItemsPerPage() - { - var query = GetQuery(); - Assert.That(query["items_per_page"].Single(), Is.EqualTo(_testBase.ItemsPerPage.ToString())); - } - - public void TheUriQueryStringContainsTheStartIndex() - { - var query = GetQuery(); - Assert.That(query["start_index"].Single(), Is.EqualTo(_testBase.StartIndex.ToString())); - } - - public void TheUriQueryStringDoesNotContainsTheStartIndex() - { - var query = GetQuery(); - Assert.That(query.Contains("start_index"), Is.False); - } - - public void TheUriQueryStringContainsTheCompanyNameIncludes() - { - var query = GetQuery(); - Assert.That(query["company_name_includes"].Single(), Is.EqualTo(_testBase.CompanyNameIncludes)); - } - - public void TheUriQueryStringDoesNotContainsTheCompanyNameIncludes() - { - var query = GetQuery(); - Assert.That(query.Contains("company_name_includes"), Is.False); - } - - public void TheUriQueryStringContainsTheCompanyNameExcludes() - { - var query = GetQuery(); - Assert.That(query["company_name_excludes"].Single(), Is.EqualTo(_testBase.CompanyNameExcludes)); - } - - public void TheUriQueryStringDoesNotContainsTheCompanyNameExcludes() - { - var query = GetQuery(); - Assert.That(query.Contains("company_name_excludes"), Is.False); - } - - public void TheUriQueryStringContainsTheCompanyStatus() - { - var query = GetQuery(); - var expected = _testBase.CompanyStatus.Select(x => x.ToString().ToLowerInvariant()).ToArray(); - Assert.That(query["company_status"], Is.EquivalentTo(expected)); - } - - public void TheUriQueryStringDoesNotContainsTheCompanyStatus() - { - var query = GetQuery(); - Assert.That(query.Contains("company_status"), Is.False); - } - - public void TheUriQueryStringContainsTheCompanySubtype() - { - var query = GetQuery(); - Assert.That(query.Contains("company_subtype"), Is.True); - } - - public void TheUriQueryStringDoesNotContainsTheCompanySubtype() - { - var query = GetQuery(); - Assert.That(query.Contains("company_subtype"), Is.False); - } - - public void TheUriQueryStringContainsTheCompanyType() - { - var query = GetQuery(); - Assert.That(query.Contains("company_type"), Is.True); - } - - public void TheUriQueryStringDoesNotContainsTheCompanyType() - { - var query = GetQuery(); - Assert.That(query.Contains("company_type"), Is.False); - } - - public void TheUriQueryStringContainsTheDissolvedFrom() - { - var query = GetQuery(); - Assert.That(query.Contains("dissolved_from"), Is.True); - } - - public void TheUriQueryStringDoesNotContainsTheDissolvedFrom() - { - var query = GetQuery(); - Assert.That(query.Contains("dissolved_from"), Is.False); - } - - public void TheUriQueryStringContainsTheDissolvedTo() - { - var query = GetQuery(); - Assert.That(query.Contains("dissolved_to"), Is.True); - } - - public void TheUriQueryStringDoesNotContainsTheDissolvedTo() - { - var query = GetQuery(); - Assert.That(query.Contains("dissolved_to"), Is.False); - } - - public void TheUriQueryStringContainsTheIncorporatedFrom() - { - var query = GetQuery(); - Assert.That(query.Contains("incorporated_from"), Is.True); - } - - public void TheUriQueryStringDoesNotContainsTheIncorporatedFrom() - { - var query = GetQuery(); - Assert.That(query.Contains("incorporated_from"), Is.False); - } - - public void TheUriQueryStringContainsTheIncorporatedTo() - { - var query = GetQuery(); - Assert.That(query.Contains("incorporated_to"), Is.True); - } - - public void TheUriQueryStringDoesNotContainsTheIncorporatedTo() - { - var query = GetQuery(); - Assert.That(query.Contains("incorporated_to"), Is.False); - } - - public void TheUriQueryStringContainsTheLocation() - { - var query = GetQuery(); - Assert.That(query["location"].Single(), Is.EqualTo(_testBase.Location)); - } - - public void TheUriQueryStringDoesNotContainsTheLocation() - { - var query = GetQuery(); - Assert.That(query.Contains("location"), Is.False); - } - - public void TheUriQueryStringContainsTheSicCodes() - { - var query = GetQuery(); - Assert.That(query.Contains("sic_codes"), Is.True); - } - - public void TheUriQueryStringDoesNotContainsTheSicCodes() - { - var query = GetQuery(); - Assert.That(query.Contains("sic_codes"), Is.False); - } - - protected ILookup GetQuery() - { - return new Uri(_testBase._baseUri, _testBase._actualUri) - .ToString() - .Split('?') - .LastOrDefault("") - .Split('&', StringSplitOptions.RemoveEmptyEntries) - .ToLookup(GetKey, GetValue); - - string GetKey(string keyValue) => keyValue.Split('=')[0]; - string GetValue(string keyValue) => Uri.UnescapeDataString(keyValue.Split('=')[1]); - } - } - } -} \ No newline at end of file diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsBase.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsBase.cs deleted file mode 100644 index 5c6b552..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsBase.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using CompaniesHouse.Request; -using CompaniesHouse.Response; -using CompaniesHouse.UriBuilders; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - public abstract partial class AdvancedSearchCompanyUriBuilderTestsBase - { - private AdvancedSearchCompanyUriBuilder _uriBuilder; - private Uri _actualUri; - private readonly Uri _baseUri = new Uri("http://testing123.co.uk/bla1/bla2/"); - - protected virtual int? ItemsPerPage { get; } = null; - protected virtual int? StartIndex { get; } = null; - protected virtual string CompanyNameIncludes { get; } = null; - protected virtual string CompanyNameExcludes { get; } = null; - protected virtual IReadOnlyCollection CompanyStatus { get; } = Array.Empty(); - protected virtual IReadOnlyCollection CompanySubtype { get; } = Array.Empty(); - protected virtual IReadOnlyCollection CompanyType { get; } = Array.Empty(); - protected virtual DateTime? DissolvedFrom { get; } = null; - protected virtual DateTime? DissolvedTo { get; } = null; - protected virtual DateTime? IncorporatedFrom { get; } = null; - protected virtual DateTime? IncorporatedTo { get; } = null; - protected virtual string Location { get; } = null; - protected virtual IReadOnlyCollection SicCodes { get; } = Array.Empty(); - - private string _path; - - [OneTimeSetUp] - public void GivenAnAdvancedSearchCompanyUriBuilder() - { - _path = "wat/wat/1"; - _uriBuilder = new AdvancedSearchCompanyUriBuilder(_path); - } - - [SetUp] - public void WhenBuildingUriWithAdvancedSearchCompanyRequest() - { - var request = new AdvancedSearchCompanyRequest - { - ItemsPerPage = ItemsPerPage, - StartIndex = StartIndex, - CompanyNameIncludes = CompanyNameIncludes, - CompanyNameExcludes = CompanyNameExcludes, - CompanyStatus = CompanyStatus, - CompanySubtype = CompanySubtype, - CompanyType = CompanyType, - DissolvedFrom = DissolvedFrom, - DissolvedTo = DissolvedTo, - IncorporatedFrom = IncorporatedFrom, - IncorporatedTo = IncorporatedTo, - Location = Location, - SicCodes = SicCodes - }; - - _actualUri = _uriBuilder.Build(request); - } - - [Test] - public void ThenTheUriIsNotAbsolute() - { - Assert.That(_actualUri.IsAbsoluteUri, Is.False); - } - - [Test] - public void ThenTheUriPathIsCorrect() - { - var uri = new Uri(_baseUri, _actualUri); - Assert.That(uri.AbsolutePath, Is.EqualTo($"/bla1/bla2/{_path}")); - } - - public Thens Then => new Thens(this); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyNameExcludes.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyNameExcludes.cs deleted file mode 100644 index 9c89cb5..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyNameExcludes.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForCompanyNameExcludes : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override string CompanyNameExcludes { get; } = Guid.NewGuid().ToString(); - - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyNameIncludes() => Then.TheUriQueryStringDoesNotContainsTheCompanyNameIncludes(); - - [Test] - public void ThenTheUriQueryStringContainsTheCompanyNameExcludes() => Then.TheUriQueryStringContainsTheCompanyNameExcludes(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyNameIncludes.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyNameIncludes.cs deleted file mode 100644 index b636101..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyNameIncludes.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForCompanyNameIncludes : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override string CompanyNameIncludes { get; } = Guid.NewGuid().ToString(); - - - [Test] - public void ThenTheUriQueryStringContainsTheCompanyNameIncludes() => Then.TheUriQueryStringContainsTheCompanyNameIncludes(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyNameExcludes() => Then.TheUriQueryStringDoesNotContainsTheCompanyNameExcludes(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyStatus.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyStatus.cs deleted file mode 100644 index 4006c38..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyStatus.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using CompaniesHouse.Response; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForCompanyStatus : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override IReadOnlyCollection CompanyStatus { get; } = new[] { Response.CompanyStatus.Active, Response.CompanyStatus.Dissolved }; - - [Test] - public void ThenTheUriQueryStringContainsTheCompanyStatus() => Then.TheUriQueryStringContainsTheCompanyStatus(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanySubtype() => Then.TheUriQueryStringDoesNotContainsTheCompanySubtype(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyType() => Then.TheUriQueryStringDoesNotContainsTheCompanyType(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanySubtype.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanySubtype.cs deleted file mode 100644 index 59fcd44..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanySubtype.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using CompaniesHouse.Response; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForCompanySubtype : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override IReadOnlyCollection CompanySubtype { get; } = new[] { Response.CompanySubType.CommunityInterestCompany }; - - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyStatus() => Then.TheUriQueryStringDoesNotContainsTheCompanyStatus(); - - [Test] - public void ThenTheUriQueryStringContainsTheCompanySubtype() => Then.TheUriQueryStringContainsTheCompanySubtype(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyType() => Then.TheUriQueryStringDoesNotContainsTheCompanyType(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyType.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyType.cs deleted file mode 100644 index 0a24e55..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForCompanyType.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using CompaniesHouse.Response; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForCompanyType : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override IReadOnlyCollection CompanyType { get; } = new[] { Response.CompanyType.Ltd }; - - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyStatus() => Then.TheUriQueryStringDoesNotContainsTheCompanyStatus(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanySubtype() => Then.TheUriQueryStringDoesNotContainsTheCompanySubtype(); - - [Test] - public void ThenTheUriQueryStringContainsTheCompanyType() => Then.TheUriQueryStringContainsTheCompanyType(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForDissolvedFrom.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForDissolvedFrom.cs deleted file mode 100644 index b5d098f..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForDissolvedFrom.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForDissolvedFrom : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override DateTime? DissolvedFrom { get; } = new DateTime(2020, 1, 1); - - - [Test] - public void ThenTheUriQueryStringContainsTheDissolvedFrom() => Then.TheUriQueryStringContainsTheDissolvedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedTo() => Then.TheUriQueryStringDoesNotContainsTheDissolvedTo(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedFrom() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedTo() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedTo(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForDissolvedTo.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForDissolvedTo.cs deleted file mode 100644 index 536144d..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForDissolvedTo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForDissolvedTo : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override DateTime? DissolvedTo { get; } = new DateTime(2023, 12, 31); - - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedFrom() => Then.TheUriQueryStringDoesNotContainsTheDissolvedFrom(); - - [Test] - public void ThenTheUriQueryStringContainsTheDissolvedTo() => Then.TheUriQueryStringContainsTheDissolvedTo(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedFrom() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedTo() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedTo(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForIncorporatedFrom.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForIncorporatedFrom.cs deleted file mode 100644 index 030e724..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForIncorporatedFrom.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForIncorporatedFrom : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override DateTime? IncorporatedFrom { get; } = new DateTime(2015, 1, 1); - - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedFrom() => Then.TheUriQueryStringDoesNotContainsTheDissolvedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedTo() => Then.TheUriQueryStringDoesNotContainsTheDissolvedTo(); - - [Test] - public void ThenTheUriQueryStringContainsTheIncorporatedFrom() => Then.TheUriQueryStringContainsTheIncorporatedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedTo() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedTo(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForIncorporatedTo.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForIncorporatedTo.cs deleted file mode 100644 index 9d98544..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForIncorporatedTo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForIncorporatedTo : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override DateTime? IncorporatedTo { get; } = new DateTime(2022, 12, 31); - - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedFrom() => Then.TheUriQueryStringDoesNotContainsTheDissolvedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedTo() => Then.TheUriQueryStringDoesNotContainsTheDissolvedTo(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedFrom() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedFrom(); - - [Test] - public void ThenTheUriQueryStringContainsTheIncorporatedTo() => Then.TheUriQueryStringContainsTheIncorporatedTo(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForItemsPerPage.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForItemsPerPage.cs deleted file mode 100644 index dc4386d..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForItemsPerPage.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForItemsPerPage : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override int? ItemsPerPage { get; } = new Random().Next(); - - - [Test] - public void ThenTheUriQueryStringContainsTheItemsPerPage() => Then.TheUriQueryStringContainsTheItemsPerPage(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheStartIndex() => Then.TheUriQueryStringDoesNotContainsTheStartIndex(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForLocation.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForLocation.cs deleted file mode 100644 index 5931ee6..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForLocation.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForLocation : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override string Location { get; } = "London"; - - - [Test] - public void ThenTheUriQueryStringContainsTheLocation() => Then.TheUriQueryStringContainsTheLocation(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheSicCodes() => Then.TheUriQueryStringDoesNotContainsTheSicCodes(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForQuery.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForQuery.cs deleted file mode 100644 index 472af8f..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForQuery.cs +++ /dev/null @@ -1,48 +0,0 @@ -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForQuery : AdvancedSearchCompanyUriBuilderTestsBase - { - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheItemsPerPage() => Then.TheUriQueryStringDoesNotContainsTheItemsPerPage(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheStartIndex() => Then.TheUriQueryStringDoesNotContainsTheStartIndex(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyNameIncludes() => Then.TheUriQueryStringDoesNotContainsTheCompanyNameIncludes(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyNameExcludes() => Then.TheUriQueryStringDoesNotContainsTheCompanyNameExcludes(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyStatus() => Then.TheUriQueryStringDoesNotContainsTheCompanyStatus(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanySubtype() => Then.TheUriQueryStringDoesNotContainsTheCompanySubtype(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheCompanyType() => Then.TheUriQueryStringDoesNotContainsTheCompanyType(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedFrom() => Then.TheUriQueryStringDoesNotContainsTheDissolvedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheDissolvedTo() => Then.TheUriQueryStringDoesNotContainsTheDissolvedTo(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedFrom() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedFrom(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheIncorporatedTo() => Then.TheUriQueryStringDoesNotContainsTheIncorporatedTo(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheLocation() => Then.TheUriQueryStringDoesNotContainsTheLocation(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheSicCodes() => Then.TheUriQueryStringDoesNotContainsTheSicCodes(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForSicCodes.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForSicCodes.cs deleted file mode 100644 index ec26db2..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForSicCodes.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForSicCodes : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override IReadOnlyCollection SicCodes { get; } = new[] { "62012", "62020", "70221" }; - - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheLocation() => Then.TheUriQueryStringDoesNotContainsTheLocation(); - - [Test] - public void ThenTheUriQueryStringContainsTheSicCodes() => Then.TheUriQueryStringContainsTheSicCodes(); - } -} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForStartIndex.cs b/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForStartIndex.cs deleted file mode 100644 index 72878e0..0000000 --- a/tests/CompaniesHouse.Tests/UriBuilders/AdvancedSearchCompanyUriBuilderTests/AdvancedSearchCompanyUriBuilderTestsForStartIndex.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using NUnit.Framework; - -namespace CompaniesHouse.Tests.UriBuilders.AdvancedSearchCompanyUriBuilderTests -{ - [TestFixture] - public class AdvancedSearchCompanyUriBuilderTestsForStartIndex : AdvancedSearchCompanyUriBuilderTestsBase - { - protected override int? StartIndex { get; } = new Random().Next(); - - [Test] - public void ThenTheUriQueryStringDoesNotContainsTheItemsPerPage() => Then.TheUriQueryStringDoesNotContainsTheItemsPerPage(); - - [Test] - public void ThenTheUriQueryStringContainsTheStartIndex() => Then.TheUriQueryStringContainsTheStartIndex(); - } -} 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/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)); + } + } +} 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/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/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/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)); + } + } +} 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)); + } + } +} 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)); + } + } +} 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)); + } + } +} 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..3c9b33e 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,43 @@ 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); + _actualUri = _uriBuilder.Build(_companyNumber, _startIndex, _pageSize, null, null, null); } - [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); + } + + [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"); } } } 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/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)); + } + } +} 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/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/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)); + } + } +} diff --git a/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBase.cs b/tests/CompaniesHouse.Tests/UriBuilders/SearchUriBuilderTests/SearchUriBuilderTestsBase.cs index 02f715a..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 QuerySearchUriBuilder _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 QuerySearchUriBuilder(_path); + _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(); } 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 @@ - - - - - - - - - - -