diff --git a/src/OpenFga.Sdk.Test/Api/OpenFgaApiTests.cs b/src/OpenFga.Sdk.Test/Api/OpenFgaApiTests.cs index 561928d..bc3820a 100644 --- a/src/OpenFga.Sdk.Test/Api/OpenFgaApiTests.cs +++ b/src/OpenFga.Sdk.Test/Api/OpenFgaApiTests.cs @@ -1373,6 +1373,50 @@ public async Task ReadTest() { Assert.Equal(response, expectedResponse); } + /// + /// Test Read With Empty Parameters + /// + [Fact] + public async Task ReadEmptyTest() { + var mockHandler = new Mock(MockBehavior.Strict); + var expectedResponse = new ReadResponse() { + Tuples = new List() { + new(new TupleKey("document:roadmap", "viewer", "user:81684243-9356-4421-8fbf-a4f8d36aa31b"), DateTime.Now) + } + }; + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.Is(req => + req.RequestUri == new Uri($"{_config.BasePath}/stores/{_config.StoreId}/read") && + req.Method == HttpMethod.Post), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage() { + StatusCode = HttpStatusCode.OK, + Content = Utils.CreateJsonStringContent(expectedResponse), + }); + + var httpClient = new HttpClient(mockHandler.Object); + var openFgaApi = new OpenFgaApi(_config, httpClient); + + var body = new ReadRequest { }; + var response = await openFgaApi.Read(body); + + mockHandler.Protected().Verify( + "SendAsync", + Times.Exactly(1), + ItExpr.Is(req => + req.RequestUri == new Uri($"{_config.BasePath}/stores/{_config.StoreId}/read") && + req.Method == HttpMethod.Post), + ItExpr.IsAny() + ); + + Assert.IsType(response); + Assert.Single(response.Tuples); + Assert.Equal(response, expectedResponse); + } + /// /// Test ReadChanges /// diff --git a/src/OpenFga.Sdk.Test/Client/OpenFgaClientTests.cs b/src/OpenFga.Sdk.Test/Client/OpenFgaClientTests.cs index 5505e2d..9a416ee 100644 --- a/src/OpenFga.Sdk.Test/Client/OpenFgaClientTests.cs +++ b/src/OpenFga.Sdk.Test/Client/OpenFgaClientTests.cs @@ -608,7 +608,8 @@ public async Task ReadTest() { "SendAsync", ItExpr.Is(req => req.RequestUri == new Uri($"{_config.BasePath}/stores/{_config.StoreId}/read") && - req.Method == HttpMethod.Post), + req.Method == HttpMethod.Post && + req.Content.ReadAsStringAsync().Result.Contains("tuple")), ItExpr.IsAny() ) .ReturnsAsync(new HttpResponseMessage() { @@ -632,7 +633,56 @@ public async Task ReadTest() { Times.Exactly(1), ItExpr.Is(req => req.RequestUri == new Uri($"{_config.BasePath}/stores/{_config.StoreId}/read") && - req.Method == HttpMethod.Post), + req.Method == HttpMethod.Post && + req.Content.ReadAsStringAsync().Result.Contains("tuple")), + ItExpr.IsAny() + ); + + Assert.IsType(response); + Assert.Single(response.Tuples); + Assert.Equal(response, expectedResponse); + } + + /// + /// Test Read with Empty Body + /// + [Fact] + public async Task ReadEmptyTest() { + var mockHandler = new Mock(MockBehavior.Strict); + var expectedResponse = new ReadResponse() { + Tuples = new List() { + new(new TupleKey("document:roadmap", "viewer", "user:81684243-9356-4421-8fbf-a4f8d36aa31b"), + DateTime.Now) + } + }; + mockHandler.Protected() + .Setup>( + "SendAsync", + ItExpr.Is(req => + req.RequestUri == new Uri($"{_config.BasePath}/stores/{_config.StoreId}/read") && + req.Method == HttpMethod.Post && + !req.Content.ReadAsStringAsync().Result.Contains("tuple")), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage() { + StatusCode = HttpStatusCode.OK, + Content = Utils.CreateJsonStringContent(expectedResponse), + }); + + var httpClient = new HttpClient(mockHandler.Object); + var fgaClient = new OpenFgaClient(_config, httpClient); + + var body = new ClientReadRequest() { }; + var options = new ClientReadOptions { }; + var response = await fgaClient.Read(body, options); + + mockHandler.Protected().Verify( + "SendAsync", + Times.Exactly(1), + ItExpr.Is(req => + req.RequestUri == new Uri($"{_config.BasePath}/stores/{_config.StoreId}/read") && + req.Method == HttpMethod.Post && + !req.Content.ReadAsStringAsync().Result.Contains("tuple")), ItExpr.IsAny() ); diff --git a/src/OpenFga.Sdk/Client/Client.cs b/src/OpenFga.Sdk/Client/Client.cs index 686d84c..7b6cf88 100644 --- a/src/OpenFga.Sdk/Client/Client.cs +++ b/src/OpenFga.Sdk/Client/Client.cs @@ -148,13 +148,18 @@ public async Task ReadChanges(ClientReadChangesRequest body * Read - Read tuples previously written to the store (does not evaluate) */ public async Task Read(ClientReadRequest body, IClientReadOptions? options = default, - CancellationToken cancellationToken = default) => - await api.Read( + CancellationToken cancellationToken = default) { + TupleKey tupleKey = null; + if (body != null && (body.User != null || body.Relation != null || body.Object != null)) { + tupleKey = body; + } + return await api.Read( new ReadRequest { - TupleKey = body, + TupleKey = tupleKey, PageSize = options?.PageSize, ContinuationToken = options?.ContinuationToken }, cancellationToken); + } /** * Write - Create or delete relationship tuples