Skip to content

Make unit testing easier for CerbosClient #143

Closed

Description

Hi there, I'd like to add a suggestion that the CerbosClient use an interface, maybe something like ICerbosClient, with the public methods that it uses, like CheckResources/CheckResourcesAsync , PlanResources/PlanResourcesAsync so that it can more easily be Mocked in a unit test.

i.e.

 var cerbosClient = new Mock<ICerbosClient>();
            cerbosClientMock.Setup(x => x.CheckResourcesAsync(It.IsAny<CheckResourcesRequest>()))
                .ReturnsAsync(checkResourcesResponse);

However, this is possible today if developers write their own C# interface and class that uses the CerbosClient internally.

Similarly, to help unit testing, it would be nice to have public access to the ResourceEntries property of the class CheckResourcesRequest (Cerbos.Sdk.Builder) type, to validate the request has the correct form and attributes when the request is made. Currently these are all private.

https://github.com/cerbos/cerbos-sdk-net/blob/main/src/Sdk/Cerbos/Sdk/Builder/CheckResourcesRequest.cs#L15

For example,

// Application code creating request to send to Cerbos

var request = CheckResourcesRequest.NewInstance()
                .WithIncludeMeta(true)
                .WithPrincipal(
                    Principal.NewInstance("1", "APPLICATION")
                        .WithPolicyVersion("default"))
                .WithResourceEntries(
                    ResourceEntry.NewInstance("todoItem", "123")
                        .WithPolicyVersion("default")
                        .WithAttribute("isTodoItemComplete", AttributeValue.BoolValue(todoItemComplete))
                        .WithAttribute("isTodoItemOwnedByUser", AttributeValue.BoolValue(todoItemOwnedByUser))
                        .WithActions("delete"));

Test code snippet testing application code above:

    
// Arrange
 
// Create resultEntry
var resultEntry = new CheckResourcesResponse.Types.ResultEntry(new Cerbos.Api.V1.Response.CheckResourcesResponse.Types.ResultEntry()
{
     Resource = new Cerbos.Api.V1.Response.CheckResourcesResponse.Types.ResultEntry.Types.Resource()
     {
          Id = "1"
      },
      Actions = { { "delete", Cerbos.Api.V1.Effect.Effect.Allow } }
});

// Create CheckResourcesResponse with the resultEntry
var checkResourcesResponse = new CheckResourcesResponse(new Cerbos.Api.V1.Response.CheckResourcesResponse()
{
    Results = { resultEntry.Raw }
});
 
var capturedRequest = new List<CheckResourcesRequest>();

// ICerbosClient Interface doesn't exist yet, but when it does this should be possible
var cerbosClientMock = new Mock<ICerbosClient>();
cerbosClientMock.Setup(x => x.CheckResourcesRequest(It.IsAny<CheckResourcesRequest>()))
    .Callback<CheckResourcesRequest>(req => capturedRequest.Add(req))
    .ReturnsAsync(checkResourcesResponse);

// Act
// Call the method that triggers CheckResourcesRequest

// Assert
Assert.Single(capturedRequest); // Ensure a request was captured
var request = capturedRequest.First();

// Inspect the captured request to verify it contains the expected attributes
// Code below is not possible since ResourceEntries is private.
Assert.True(request.ResourceEntries.Any(re => 
    re.Attributes.Any(attr => attr.Key == "isTodoItemComplete" && attr.Value.BoolValue) &&
    re.Attributes.Any(attr => attr.Key == "isTodoItemOwnedByUser" && attr.Value.BoolValue)));
    

I hope that helps! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions