Skip to content
Draft
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
11 changes: 11 additions & 0 deletions db/migrations/20260818100000_add-environment-to-aws-account.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Add Environment column to AwsAccount table
ALTER TABLE "AwsAccount" ADD COLUMN "Environment" varchar(255);

-- Backfill existing rows with 'prod' as default environment
UPDATE "AwsAccount" SET "Environment" = 'prod' WHERE "Environment" IS NULL;

-- Make Environment NOT NULL
ALTER TABLE "AwsAccount" ALTER COLUMN "Environment" SET NOT NULL;

-- Add unique index on (CapabilityId, Environment)
CREATE UNIQUE INDEX "IX_AwsAccount_CapabilityId_Environment" ON "AwsAccount" ("CapabilityId", "Environment");
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE "KubernetesAccess" (
"Id" uuid PRIMARY KEY,
"CapabilityId" varchar(255) NOT NULL,
"Environment" varchar(255) NOT NULL,
"AwsAccountId" uuid NULL,
"RequestedAt" timestamp NOT NULL,
"RequestedBy" varchar(255) NOT NULL,
"Namespace" varchar(255) NULL,
"GrantedAt" timestamp NULL
);
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,25 @@ public class TestComplianceApplicationService

private const string EmptyMetadata = "{}";

private static IAwsAccountRepository AwsAccountRepoWithK8sLinkFor(params CapabilityId[] capabilityIds)
private static IKubernetesAccessRepository KubernetesAccessRepoWithActiveFor(params CapabilityId[] capabilityIds)
{
var mock = new Mock<IAwsAccountRepository>();
mock.Setup(r => r.FindBy(It.IsAny<CapabilityId>())).ReturnsAsync((AwsAccount?)null);
var linkedAccounts = new List<AwsAccount>();
foreach (var capId in capabilityIds)
{
var account = AwsAccount.RequestNew(capId, DateTime.UtcNow, "test@dfds.com");
account.LinkKubernetesNamespace($"ns-{capId}", DateTime.UtcNow);
linkedAccounts.Add(account);
mock.Setup(r => r.FindBy(capId)).ReturnsAsync(account);
}
mock.Setup(r => r.GetByCapabilityIds(It.IsAny<IEnumerable<CapabilityId>>()))
var mock = new Mock<IKubernetesAccessRepository>();
var activeAccesses = capabilityIds
.Select(capId =>
{
var access = KubernetesAccess.Request(capId, "prod", null, null, DateTime.UtcNow, "test@dfds.com");
access.GrantAccess($"ns-{capId}", DateTime.UtcNow);
return access;
})
.ToList();
mock.Setup(r => r.GetAllBy(It.IsAny<CapabilityId>()))
.ReturnsAsync((CapabilityId id) => activeAccesses.Where(a => a.CapabilityId == id).ToList());
mock.Setup(r => r.GetAllBy(It.IsAny<IEnumerable<CapabilityId>>()))
.ReturnsAsync(
(IEnumerable<CapabilityId> ids) =>
{
var idSet = ids.Select(i => i.ToString()).ToHashSet();
return linkedAccounts.Where(a => idSet.Contains(a.CapabilityId.ToString())).ToList();
return activeAccesses.Where(a => idSet.Contains(a.CapabilityId.ToString())).ToList();
}
);
return mock.Object;
Expand Down Expand Up @@ -134,7 +135,7 @@ public async Task GetCapabilityCompliance_Stub_ExternalSecretsIsUnknown()

var service = A
.ComplianceApplicationService.WithCapabilityRepository(repo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(capabilityId))
.WithKubernetesAccessRepository(KubernetesAccessRepoWithActiveFor(capabilityId))
.Build();

var result = await service.GetCapabilityCompliance(capabilityId);
Expand Down Expand Up @@ -187,7 +188,7 @@ public async Task GetCapabilityCompliance_HasFiveCategoriesTotal_WhenKubernetesL

var service = A
.ComplianceApplicationService.WithCapabilityRepository(repo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(capabilityId))
.WithKubernetesAccessRepository(KubernetesAccessRepoWithActiveFor(capabilityId))
.Build();

var result = await service.GetCapabilityCompliance(capabilityId);
Expand Down Expand Up @@ -219,23 +220,16 @@ public async Task GetCapabilityCompliance_NoAwsAccount_OnlyTagsCategoryReturned(
}

[Fact]
public async Task GetCapabilityCompliance_AwsAccountWithoutKubernetesLink_OnlyTagsCategoryReturned()
public async Task GetCapabilityCompliance_NoKubernetesAccess_OnlyTagsCategoryReturned()
{
var capabilityId = CapabilityId.CreateFrom("test-cap");
var capability = A.Capability.WithId(capabilityId).WithJsonMetadata(AllTagsPresent).Build();
var unlinkedAccount = AwsAccount.RequestNew(capabilityId, DateTime.UtcNow, "test@dfds.com");
// No call to LinkKubernetesNamespace — KubernetesLink stays Unlinked.

var capabilityRepo = new Mock<ICapabilityRepository>();
capabilityRepo.Setup(r => r.FindBy(capabilityId)).ReturnsAsync(capability);

var awsRepo = new Mock<IAwsAccountRepository>();
awsRepo.Setup(r => r.FindBy(capabilityId)).ReturnsAsync(unlinkedAccount);

var service = A
.ComplianceApplicationService.WithCapabilityRepository(capabilityRepo.Object)
.WithAwsAccountRepository(awsRepo.Object)
.Build();
// Default builder has no active KubernetesAccess records
var service = A.ComplianceApplicationService.WithCapabilityRepository(capabilityRepo.Object).Build();

var result = await service.GetCapabilityCompliance(capabilityId);

Expand Down Expand Up @@ -343,7 +337,7 @@ public async Task GetCostCentreCompliance_K8sCategoryCountsOnlyReflectK8sCapabil

var service = A
.ComplianceApplicationService.WithCapabilityRepository(capabilityRepo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(k8sCapId))
.WithKubernetesAccessRepository(KubernetesAccessRepoWithActiveFor(k8sCapId))
.Build();

var result = await service.GetCostCentreCompliance("ti-platform");
Expand Down Expand Up @@ -379,7 +373,7 @@ public async Task GetCapabilityCompliance_Stub_IrsaMutualTrustIsUnknown()

var service = A
.ComplianceApplicationService.WithCapabilityRepository(repo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(capabilityId))
.WithKubernetesAccessRepository(KubernetesAccessRepoWithActiveFor(capabilityId))
.Build();

var result = await service.GetCapabilityCompliance(capabilityId);
Expand All @@ -399,7 +393,7 @@ public async Task GetCapabilityCompliance_Stub_WorkloadProbesIsUnknown()

var service = A
.ComplianceApplicationService.WithCapabilityRepository(repo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(capabilityId))
.WithKubernetesAccessRepository(KubernetesAccessRepoWithActiveFor(capabilityId))
.Build();

var result = await service.GetCapabilityCompliance(capabilityId);
Expand All @@ -419,7 +413,7 @@ public async Task GetCapabilityCompliance_Stub_EcrPullIsUnknown()

var service = A
.ComplianceApplicationService.WithCapabilityRepository(repo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(capabilityId))
.WithKubernetesAccessRepository(KubernetesAccessRepoWithActiveFor(capabilityId))
.Build();

var result = await service.GetCapabilityCompliance(capabilityId);
Expand Down Expand Up @@ -525,7 +519,7 @@ public async Task GetRequirementsCompliance_ReturnsKnownRequirementsAndCounts()

var service = A
.ComplianceApplicationService.WithCapabilityRepository(capabilityRepo.Object)
.WithAwsAccountRepository(AwsAccountRepoWithK8sLinkFor(k8sCapId))
.WithKubernetesAccessRepository(KubernetesAccessRepoWithActiveFor(k8sCapId))
.Build();

var result = await service.GetRequirementsCompliance();
Expand Down
10 changes: 9 additions & 1 deletion src/SelfService.Tests/Builders/AwsAccountBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ public class AwsAccountBuilder
{
private AwsAccountId _id;
private CapabilityId _capabilityId;
private string _environment;
private DateTime _createdAt;
private string _createdBy;

public AwsAccountBuilder()
{
_id = AwsAccountId.New();
_capabilityId = CapabilityId.Parse("foo");
_environment = "prod";
_createdAt = new DateTime(2000, 1, 1);
_createdBy = nameof(AwsAccountBuilder);
}
Expand All @@ -25,7 +27,13 @@ public AwsAccountBuilder WithCapabilityId(CapabilityId capabilityId)

public AwsAccount Build()
{
return new AwsAccount(id: _id, capabilityId: _capabilityId, requestedAt: _createdAt, requestedBy: _createdBy);
return new AwsAccount(
id: _id,
capabilityId: _capabilityId,
environment: _environment,
requestedAt: _createdAt,
requestedBy: _createdBy
);
}

public static implicit operator AwsAccount(AwsAccountBuilder builder) => builder.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ public class ComplianceApplicationServiceBuilder
{
private ICapabilityRepository _capabilityRepository;
private IAwsAccountRepository _awsAccountRepository;
private IKubernetesAccessRepository _kubernetesAccessRepository;
private RequirementsDbContext? _requirementsDbContext;

public ComplianceApplicationServiceBuilder()
{
_capabilityRepository = Dummy.Of<ICapabilityRepository>();
_awsAccountRepository = DefaultAwsAccountRepository();
_kubernetesAccessRepository = DefaultKubernetesAccessRepository();
}

public ComplianceApplicationServiceBuilder WithCapabilityRepository(ICapabilityRepository capabilityRepository)
Expand All @@ -30,6 +32,14 @@ public ComplianceApplicationServiceBuilder WithAwsAccountRepository(IAwsAccountR
return this;
}

public ComplianceApplicationServiceBuilder WithKubernetesAccessRepository(
IKubernetesAccessRepository kubernetesAccessRepository
)
{
_kubernetesAccessRepository = kubernetesAccessRepository;
return this;
}

public ComplianceApplicationServiceBuilder WithRequirementsDbContext(RequirementsDbContext requirementsDbContext)
{
_requirementsDbContext = requirementsDbContext;
Expand All @@ -43,11 +53,16 @@ public IComplianceApplicationService Build()
return new ComplianceApplicationService(
_capabilityRepository,
_awsAccountRepository,
_kubernetesAccessRepository,
_requirementsDbContext
);
}

return new StubComplianceApplicationService(_capabilityRepository, _awsAccountRepository);
return new StubComplianceApplicationService(
_capabilityRepository,
_awsAccountRepository,
_kubernetesAccessRepository
);
}

private static IAwsAccountRepository DefaultAwsAccountRepository()
Expand All @@ -58,4 +73,12 @@ private static IAwsAccountRepository DefaultAwsAccountRepository()
.ReturnsAsync(new List<AwsAccount>());
return mock.Object;
}

private static IKubernetesAccessRepository DefaultKubernetesAccessRepository()
{
var mock = new Mock<IKubernetesAccessRepository>();
mock.Setup(r => r.GetAllBy(It.IsAny<CapabilityId>())).ReturnsAsync(new List<KubernetesAccess>());
mock.Setup(r => r.GetAllBy(It.IsAny<IEnumerable<CapabilityId>>())).ReturnsAsync(new List<KubernetesAccess>());
return mock.Object;
}
}
38 changes: 5 additions & 33 deletions src/SelfService.Tests/Domain/Models/TestAwsAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,40 @@ namespace SelfService.Tests.Domain.Models;
public class TestAwsAccount
{
[Fact]
public void requested_account_is_not_registered_nor_linked_to_kubernetes()
public void requested_account_is_not_registered()
{
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), DateTime.Today, "bar");
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), "prod", DateTime.Today, "bar");

Assert.Equal(AwsAccountRegistration.Incomplete, account.Registration);
Assert.Equal(KubernetesLink.Unlinked, account.KubernetesLink);
}

[Fact]
public void registered_account_as_expected()
{
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), DateTime.Today, "bar");
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), "prod", DateTime.Today, "bar");

account.RegisterRealAwsAccount(RealAwsAccountId.Empty, "foo@foo.com", DateTime.Today);

Assert.Equal(
new AwsAccountRegistration(RealAwsAccountId.Empty, "foo@foo.com", DateTime.Today),
account.Registration
);
Assert.Equal(KubernetesLink.Unlinked, account.KubernetesLink);
}

[Fact]
public void linked_to_kubernetes_as_expected()
{
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), DateTime.Today, "bar");

account.RegisterRealAwsAccount(RealAwsAccountId.Empty, "foo@foo.com", DateTime.Today);
account.LinkKubernetesNamespace("dummy-namespace", DateTime.Today);

Assert.Equal(
new AwsAccountRegistration(RealAwsAccountId.Empty, "foo@foo.com", DateTime.Today),
account.Registration
);
Assert.Equal(new KubernetesLink("dummy-namespace", DateTime.Today), account.KubernetesLink);
}

[Fact]
public void new_account_has_expected_status()
{
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), DateTime.Today, "bar");
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), "prod", DateTime.Today, "bar");

Assert.Equal(AwsAccountStatus.Requested, account.Status);
}

[Fact]
public void registered_account_has_expected_status()
{
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), DateTime.Today, "bar");

account.RegisterRealAwsAccount(RealAwsAccountId.Empty, "foo@foo.com", DateTime.Today);

Assert.Equal(AwsAccountStatus.Pending, account.Status);
}

[Fact]
public void linked_account_has_expected_status()
{
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), DateTime.Today, "bar");
var account = AwsAccount.RequestNew(CapabilityId.Parse("foo"), "prod", DateTime.Today, "bar");

account.RegisterRealAwsAccount(RealAwsAccountId.Empty, "foo@foo.com", DateTime.Today);
account.LinkKubernetesNamespace("dummy-namespace", DateTime.Today);

Assert.Equal(AwsAccountStatus.Completed, account.Status);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public async Task get_capability_by_id_returns_expected_allow_on_aws_account_lin
.Select(x => x.GetString() ?? "")
.ToArray();

Assert.Equal(new[] { "GET" }, allowValues);
Assert.Equal(new[] { "GET", "POST" }, allowValues);
}

[Fact]
Expand Down Expand Up @@ -167,7 +167,7 @@ public async Task get_capability_by_id_returns_expected_allow_on_aws_account_lin
.Select(x => x.GetString() ?? "")
.ToArray();

Assert.Equal(new[] { "POST" }, allowValues);
Assert.Equal(new[] { "GET", "POST" }, allowValues);
}

[Fact]
Expand Down Expand Up @@ -257,7 +257,7 @@ public async Task get_capability_by_id_returns_expected_allow_on_aws_account_lin
.Select(x => x.GetString() ?? "")
.ToArray();

Assert.Equal(new[] { "GET" }, allowValues);
Assert.Equal(new[] { "GET", "POST" }, allowValues);
}

[Fact]
Expand All @@ -270,6 +270,7 @@ public async Task pending_deletion_capability_doesnt_have_POST_endpoint_on_aws_a
.WithAwsAccountRepository(new StubAwsAccountRepository(stubAwsAccount))
.WithCapabilityRepository(new StubCapabilityRepository(stubCapability))
.WithMembershipQuery(new StubMembershipQuery(hasActiveMembership: true))
.WithCapabilityDeletionStatusQuery(new StubCapabilityDeletionStatusQuery(isPendingDeletion: true))
.Build();
/*
application.ReplaceService<IRbacPermissionGrantRepository>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ public async Task awsaccount()
"foo@foo.com",
new DateTime(2000, 1, 1)
);
stub.LinkKubernetesNamespace("the-namespace", new DateTime(2000, 1, 1));

// write
await dbContext.AwsAccounts.AddAsync(stub);
Expand Down
Loading
Loading