Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
ARG CONFIGURATION="Release"
ARG NUGET_PACKAGE_VERSION="1.0.0"
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore
FROM mcr.microsoft.com/dotnet/sdk:10.0.401 AS restore

ARG CONFIGURATION

Expand Down Expand Up @@ -33,6 +33,7 @@ COPY ./tests/ ./tests/
COPY ./samples/ ./samples/
COPY ./external/ ./external/
COPY ./enumerations/ ./enumerations/
COPY ./.git/ ./.git/
RUN dotnet build --configuration $CONFIGURATION --no-restore

FROM build AS test
Expand Down
3 changes: 3 additions & 0 deletions enumerations/extra/psc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
person_with_significant_control_notification_kind:
'personal-notification': ''

person_with_significant_control_kind:
'corporate-entity-person-with-significant-control': ''
'corporate-entity-beneficial-owner': ''
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"version": "10.0.401",
"rollForward": "latestFeature",
"allowPrerelease": false
}
Expand Down
5 changes: 5 additions & 0 deletions src/CompaniesHouse/CompaniesHouseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ public Task<CompaniesHouseResponse<PersonWithSignificantControl>> GetLegalPerson
return _companiesHousePersonsWithSignificantControlDetailsClient.GetLegalPersonBeneficialOwnerAsync(companyNumber, notificationId, cancellationToken);
}

public Task<CompaniesHouseResponse<PersonWithSignificantControlNotifications>> GetNotificationsAsync(string companyNumber, string pscId, string? filter = null, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default)
{
return _companiesHousePersonsWithSignificantControlDetailsClient.GetNotificationsAsync(companyNumber, pscId, filter, startIndex, pageSize, cancellationToken);
}

public Task<CompaniesHouseResponse<PersonsWithSignificantControlStatements>> GetPersonsWithSignificantControlStatementsAsync(string companyNumber, int startIndex = 0, int pageSize = 25, bool? registerView = null, CancellationToken cancellationToken = default)
{
return _companiesHousePersonsWithSignificantControlDetailsClient.GetPersonsWithSignificantControlStatementsAsync(companyNumber, startIndex, pageSize, registerView, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public async Task<CompaniesHouseResponse<PersonWithSignificantControl>> GetLegal
return await GetAsync<PersonWithSignificantControl>(_uriBuilder.BuildLegalPersonBeneficialOwner(companyNumber, notificationId), cancellationToken).ConfigureAwait(false);
}

public async Task<CompaniesHouseResponse<PersonWithSignificantControlNotifications>> GetNotificationsAsync(string companyNumber, string pscId, string? filter = null, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default)
{
return await GetAsync<PersonWithSignificantControlNotifications>(_uriBuilder.BuildNotifications(companyNumber, pscId, filter, startIndex, pageSize), cancellationToken).ConfigureAwait(false);
}

public async Task<CompaniesHouseResponse<PersonsWithSignificantControlStatements>> GetPersonsWithSignificantControlStatementsAsync(string companyNumber, int startIndex = 0, int pageSize = 25, bool? registerView = null, CancellationToken cancellationToken = default)
{
return await GetAsync<PersonsWithSignificantControlStatements>(_uriBuilder.BuildStatementsList(companyNumber, startIndex, pageSize, registerView), cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public interface ICompaniesHousePersonsWithSignificantControlDetailsClient

Task<CompaniesHouseResponse<PersonWithSignificantControl>> GetLegalPersonBeneficialOwnerAsync(string companyNumber, string notificationId, CancellationToken cancellationToken = default);

Task<CompaniesHouseResponse<PersonWithSignificantControlNotifications>> GetNotificationsAsync(string companyNumber, string pscId, string? filter = null, int startIndex = 0, int pageSize = 25, CancellationToken cancellationToken = default);

Task<CompaniesHouseResponse<PersonsWithSignificantControlStatements>> GetPersonsWithSignificantControlStatementsAsync(string companyNumber, int startIndex = 0, int pageSize = 25, bool? registerView = null, CancellationToken cancellationToken = default);

Task<CompaniesHouseResponse<PersonWithSignificantControlStatement>> GetPersonsWithSignificantControlStatementAsync(string companyNumber, string statementId, CancellationToken cancellationToken = default);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Text.Json.Serialization;
using CompaniesHouse.Response.Appointments;
using CompaniesHouse.Response.Officers;

namespace CompaniesHouse.Response.PersonsWithSignificantControl
{
public class PersonWithSignificantControlNotification
{
[JsonPropertyName("address")]
public Address? Address { get; set; }

[JsonPropertyName("ceased_on")]
public DateTime? CeasedOn { get; set; }

[JsonPropertyName("country_of_residence")]
public string? CountryOfResidence { get; set; }

[JsonPropertyName("etag")]
public string? ETag { get; set; }

[JsonPropertyName("identification")]
public PersonWithSignificantControlIdentification? Identification { get; set; }

[JsonPropertyName("identity_verification_details")]
public IdentityVerificationDetails? IdentityVerificationDetails { get; set; }

[JsonPropertyName("is_sanctioned")]
public bool? IsSanctioned { get; set; }

[JsonPropertyName("kind")]
public PersonWithSignificantControlKind Kind { get; set; }

[JsonPropertyName("links")]
public PersonWithSignificantControlNotificationLinks? Links { get; set; }

[JsonPropertyName("name")]
public string? Name { get; set; }

[JsonPropertyName("name_elements")]
public NameElements? NameElements { get; set; }

[JsonPropertyName("nationality")]
public string? Nationality { get; set; }

[JsonPropertyName("natures_of_control")]
public PersonWithSignificantControlNatureOfControl[]? NaturesOfControl { get; set; }

[JsonPropertyName("notified_on")]
public DateTime? NotifiedOn { get; set; }

[JsonPropertyName("notified_to")]
public PersonWithSignificantControlNotifiedTo? NotifiedTo { get; set; }

[JsonPropertyName("principal_office_address")]
public Address? PrincipalOfficeAddress { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;

namespace CompaniesHouse.Response.PersonsWithSignificantControl
{
public class PersonWithSignificantControlNotificationLinks
{
[JsonPropertyName("company")]
public string? Company { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Text.Json.Serialization;

namespace CompaniesHouse.Response.PersonsWithSignificantControl
{
public class PersonWithSignificantControlNotifications
{
[JsonPropertyName("active_count")]
public int? ActiveCount { get; set; }

[JsonPropertyName("ceased_count")]
public int? CeasedCount { get; set; }

[JsonPropertyName("inactive_count")]
public int? InactiveCount { get; set; }

[JsonPropertyName("items")]
public PersonWithSignificantControlNotification[]? Items { get; set; }

[JsonPropertyName("items_per_page")]
public int? ItemsPerPage { get; set; }

[JsonPropertyName("date_of_birth")]
public DateOfBirth? DateOfBirth { get; set; }

[JsonPropertyName("kind")]
public PersonWithSignificantControlNotificationKind Kind { get; set; }

[JsonPropertyName("links")]
public PersonWithSignificantControlNotificationsLinks? Links { get; set; }

[JsonPropertyName("name")]
public string? Name { get; set; }

[JsonPropertyName("start_index")]
public int? StartIndex { get; set; }

[JsonPropertyName("total_results")]
public int? TotalResults { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;

namespace CompaniesHouse.Response.PersonsWithSignificantControl
{
public class PersonWithSignificantControlNotificationsLinks
{
[JsonPropertyName("self")]
public string? Self { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;

namespace CompaniesHouse.Response.PersonsWithSignificantControl
{
public class PersonWithSignificantControlNotifiedTo
{
[JsonPropertyName("company_name")]
public string? CompanyName { get; set; }

[JsonPropertyName("company_number")]
public string? CompanyNumber { get; set; }

[JsonPropertyName("company_status")]
public CompanyStatus CompanyStatus { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface IPersonsWithSignificantControlDetailsUriBuilder

Uri BuildLegalPersonBeneficialOwner(string companyNumber, string notificationId);

Uri BuildNotifications(string companyNumber, string pscId, string? filter, int startIndex, int pageSize);

Uri BuildStatementsList(string companyNumber, int startIndex, int pageSize, bool? registerView);

Uri BuildStatement(string companyNumber, string statementId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ public Uri BuildLegalPersonBeneficialOwner(string companyNumber, string notifica
return Build(companyNumber, $"persons-with-significant-control/legal-person-beneficial-owner/{Uri.EscapeDataString(notificationId)}");
}

public Uri BuildNotifications(string companyNumber, string pscId, string? filter, int startIndex, int pageSize)
{
var queryParts = new List<string>();

if (!string.IsNullOrWhiteSpace(filter))
{
queryParts.Add("filter=" + Uri.EscapeDataString(filter));
}

queryParts.Add("items_per_page=" + pageSize.ToString(CultureInfo.InvariantCulture));
queryParts.Add("start_index=" + startIndex.ToString(CultureInfo.InvariantCulture));

return Build(companyNumber, $"persons-with-significant-control/{Uri.EscapeDataString(pscId)}/notifications?{string.Join("&", queryParts)}");
}

public Uri BuildStatementsList(string companyNumber, int startIndex, int pageSize, bool? registerView)
{
var queryParts = new List<string>
Expand Down
1 change: 1 addition & 0 deletions src/CompaniesHouse/enum-map.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ 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_notification_kind|CompaniesHouse.Response.PersonsWithSignificantControl|PersonWithSignificantControlNotificationKind|false
person_with_significant_control_nature_of_control|CompaniesHouse.Response.PersonsWithSignificantControl|PersonWithSignificantControlNatureOfControl|false
16 changes: 16 additions & 0 deletions tests/CompaniesHouse.ScenarioTests/ScenarioFactAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using Xunit;

namespace CompaniesHouse.ScenarioTests
{
public sealed class ScenarioFactAttribute : FactAttribute
{
public ScenarioFactAttribute()
{
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("COMPANIES_HOUSE_API_KEY")))
{
Skip = "COMPANIES_HOUSE_API_KEY environment variable is not set - skipping scenario test that calls the real API.";
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public SearchForAnOfficerAndFetchCorrespondingCompanyScenarioTests()
_client = new CompaniesHouseClient(settings);
}

[Fact]
[ScenarioFact]
public async Task RunScenario()
{
var officersSearch = await _client.SearchOfficerAsync(new SearchOfficerRequest() { Query = "Richard Branson" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using CompaniesHouse.Response.Search.CompanySearch;
using CompaniesHouse.Response.Search.DissolvedCompaniesSearch;
using CompaniesHouse.Response.Search.OfficerSearch;
using CompaniesHouse.Response.Search;
using Shouldly;
using Xunit;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace CompaniesHouse.ScenarioTests
{
public class SearchingCompaniesUsingMicrosoftServiceContainerTests
{
[Fact]
[ScenarioFact]
public async Task CanResolveCompaniesHouseClients()
{
var serviceCollection = new ServiceCollection();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using CompaniesHouse.Response;
using CompaniesHouse.Response.PersonsWithSignificantControl;
using CompaniesHouse.UriBuilders;
using Moq;
Expand Down Expand Up @@ -43,6 +44,27 @@ public async Task GivenCapturedStatementList_WhenGettingStatementsList_ThenEnvel
result.Data.Items[0].Links.Self.ShouldBe("/company/05124262/persons-with-significant-control-statements/8xxEeFpu5Xmpf1ce1FmwM-sK8J8");
}

[Fact]
public async Task GivenCapturedNotifications_WhenGettingNotifications_ThenEnvelopeAndItemsDeserialize()
{
var uri = new Uri("https://wibble.com/company/05124262/persons-with-significant-control/abc/notifications");
HttpMessageHandler handler = new StubHttpMessageHandler(uri, NotificationsJson);
var uriBuilder = CreateUriBuilder(uri);

var client = new CompaniesHousePersonsWithSignificantControlDetailsClient(new HttpClient(handler), uriBuilder.Object);
var result = await client.GetNotificationsAsync("05124262", "abc", "active", 0, 25);

result.Data.TotalResults.ShouldBe(1);
var items = result.Data.Items.ShouldNotBeNull();
items.Length.ShouldBe(1);
items[0].Kind.ShouldBe(new PersonWithSignificantControlKind("individual-person-with-significant-control"));
items[0].NotifiedTo?.CompanyNumber.ShouldBe("05124262");
items[0].NotifiedTo?.CompanyStatus.ShouldBe(new CompanyStatus("active"));
result.Data.Kind.ShouldBe(new PersonWithSignificantControlNotificationKind("personal-notification"));
result.Data.DateOfBirth?.Month.ShouldBe(7);
result.Data.DateOfBirth?.Year.ShouldBe(1979);
}

[Fact]
public async Task GivenCapturedSuperSecure_WhenGettingSuperSecurePsc_ThenObservedFieldsDeserialize()
{
Expand All @@ -69,6 +91,7 @@ private static Mock<IPersonsWithSignificantControlDetailsUriBuilder> CreateUriBu
uriBuilder.Setup(x => x.BuildCorporateEntityBeneficialOwner(It.IsAny<string>(), It.IsAny<string>())).Returns(uri);
uriBuilder.Setup(x => x.BuildLegalPerson(It.IsAny<string>(), It.IsAny<string>())).Returns(uri);
uriBuilder.Setup(x => x.BuildLegalPersonBeneficialOwner(It.IsAny<string>(), It.IsAny<string>())).Returns(uri);
uriBuilder.Setup(x => x.BuildNotifications(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string?>(), It.IsAny<int>(), It.IsAny<int>())).Returns(uri);
uriBuilder.Setup(x => x.BuildStatementsList(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<bool?>())).Returns(uri);
uriBuilder.Setup(x => x.BuildStatement(It.IsAny<string>(), It.IsAny<string>())).Returns(uri);
uriBuilder.Setup(x => x.BuildSuperSecure(It.IsAny<string>(), It.IsAny<string>())).Returns(uri);
Expand All @@ -95,6 +118,7 @@ private static Mock<IPersonsWithSignificantControlDetailsUriBuilder> CreateUriBu
private const string StatementListJson = """
{
"items_per_page":25,
"date_of_birth":{"month":7,"year":1979},
"items":[
{
"etag":"95ca7497819e5fbc1144b6a3ef09f477228f3f5f",
Expand Down Expand Up @@ -127,5 +151,30 @@ private static Mock<IPersonsWithSignificantControlDetailsUriBuilder> CreateUriBu
"links":{"self":"/company/1/persons-with-significant-control/super-secure/2"}
}
""";

private const string NotificationsJson = """
{
"active_count":1,
"ceased_count":0,
"inactive_count":0,
"items_per_page":25,
"items":[
{
"etag":"abc",
"kind":"individual-person-with-significant-control",
"name":"Chris Brown",
"notified_on":"2019-01-16",
"natures_of_control":["ownership-of-shares-25-to-50-percent"],
"notified_to":{"company_number":"05124262","company_name":"Example Ltd","company_status":"active"},
"links":{"company":"/company/05124262"}
}
],
"kind":"personal-notification",
"name":"Chris Brown",
"start_index":0,
"total_results":1,
"links":{"self":"/company/05124262/persons-with-significant-control/abc/notifications"}
}
""";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ public void BuildLegalPersonBeneficialOwner_EncodesIds()
.ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/legal-person-beneficial-owner/a%2Fb", UriKind.Relative));
}

[Fact]
public void BuildNotifications_IncludesFilterAndPaging()
{
_builder.BuildNotifications("00/123", "a/b", "active", 25, 10)
.ShouldBe(new Uri("company/00%2F123/persons-with-significant-control/a%2Fb/notifications?filter=active&items_per_page=10&start_index=25", UriKind.Relative));
}

[Fact]
public void BuildStatementsList_IncludesPagingAndOptionalRegisterView()
{
Expand Down
Loading