Skip to content

Commit 63e1d30

Browse files
committed
Add end-to-end tests for JSON:API endpoints
1 parent 46a2892 commit 63e1d30

File tree

132 files changed

+17906
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

132 files changed

+17906
-5
lines changed

test/OpenApiKiotaEndToEndTests/OpenApiKiotaEndToEndTests.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
Command="dotnet kiota generate --language CSharp --class-name QueryStringsClient --namespace-name OpenApiKiotaEndToEndTests.QueryStrings.GeneratedCode --output ./QueryStrings/GeneratedCode --backing-store --exclude-backward-compatible --clean-output --clear-cache --log-level Error --openapi ../OpenApiTests/QueryStrings/GeneratedSwagger/swagger.g.json" />
3333
<Exec
3434
Command="dotnet kiota generate --language CSharp --class-name ClientIdGenerationModesClient --namespace-name OpenApiKiotaEndToEndTests.ClientIdGenerationModes.GeneratedCode --output ./ClientIdGenerationModes/GeneratedCode --backing-store --exclude-backward-compatible --clean-output --clear-cache --log-level Error --openapi ../OpenApiTests/ClientIdGenerationModes/GeneratedSwagger/swagger.g.json" />
35+
<Exec
36+
Command="dotnet kiota generate --language CSharp --class-name RestrictedControllersClient --namespace-name OpenApiKiotaEndToEndTests.RestrictedControllers.GeneratedCode --output ./RestrictedControllers/GeneratedCode --backing-store --exclude-backward-compatible --clean-output --clear-cache --log-level Error --openapi ../OpenApiTests/RestrictedControllers/GeneratedSwagger/swagger.g.json" />
3537

3638
<ItemGroup>
3739
<!-- This isn't entirely reliable: may require a second build after the source swagger.json has changed, to get rid of compile errors. -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
using System.Net;
2+
using FluentAssertions;
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.Kiota.Http.HttpClientLibrary;
5+
using OpenApiKiotaEndToEndTests.RestrictedControllers.GeneratedCode;
6+
using OpenApiKiotaEndToEndTests.RestrictedControllers.GeneratedCode.Models;
7+
using OpenApiTests;
8+
using OpenApiTests.RestrictedControllers;
9+
using TestBuildingBlocks;
10+
using Xunit;
11+
using Xunit.Abstractions;
12+
13+
namespace OpenApiKiotaEndToEndTests.RestrictedControllers;
14+
15+
public sealed class CreateResourceTests : IClassFixture<IntegrationTestContext<OpenApiStartup<RestrictionDbContext>, RestrictionDbContext>>
16+
{
17+
private readonly IntegrationTestContext<OpenApiStartup<RestrictionDbContext>, RestrictionDbContext> _testContext;
18+
private readonly TestableHttpClientRequestAdapterFactory _requestAdapterFactory;
19+
private readonly RestrictionFakers _fakers = new();
20+
21+
public CreateResourceTests(IntegrationTestContext<OpenApiStartup<RestrictionDbContext>, RestrictionDbContext> testContext,
22+
ITestOutputHelper testOutputHelper)
23+
{
24+
_testContext = testContext;
25+
_requestAdapterFactory = new TestableHttpClientRequestAdapterFactory(testOutputHelper);
26+
27+
testContext.UseController<WriteOnlyChannelsController>();
28+
}
29+
30+
[Fact]
31+
public async Task Can_create_resource_with_includes_and_fieldsets()
32+
{
33+
// Arrange
34+
DataStream existingVideoStream = _fakers.DataStream.Generate();
35+
DataStream existingAudioStream = _fakers.DataStream.Generate();
36+
WriteOnlyChannel newChannel = _fakers.WriteOnlyChannel.Generate();
37+
38+
await _testContext.RunOnDatabaseAsync(async dbContext =>
39+
{
40+
dbContext.DataStreams.AddRange(existingVideoStream, existingAudioStream);
41+
await dbContext.SaveChangesAsync();
42+
});
43+
44+
using HttpClientRequestAdapter requestAdapter = _requestAdapterFactory.CreateAdapter(_testContext.Factory);
45+
var apiClient = new RestrictedControllersClient(requestAdapter);
46+
47+
var requestBody = new WriteOnlyChannelPostRequestDocument
48+
{
49+
Data = new WriteOnlyChannelDataInPostRequest
50+
{
51+
Type = WriteOnlyChannelResourceType.WriteOnlyChannels,
52+
Attributes = new WriteOnlyChannelAttributesInPostRequest
53+
{
54+
Name = newChannel.Name,
55+
IsAdultOnly = newChannel.IsAdultOnly
56+
},
57+
Relationships = new WriteOnlyChannelRelationshipsInPostRequest
58+
{
59+
VideoStream = new ToOneDataStreamInRequest
60+
{
61+
Data = new DataStreamIdentifier
62+
{
63+
Type = DataStreamResourceType.DataStreams,
64+
Id = existingVideoStream.StringId!
65+
}
66+
},
67+
AudioStreams = new ToManyDataStreamInRequest
68+
{
69+
Data =
70+
[
71+
new DataStreamIdentifier
72+
{
73+
Type = DataStreamResourceType.DataStreams,
74+
Id = existingAudioStream.StringId!
75+
}
76+
]
77+
}
78+
}
79+
}
80+
};
81+
82+
var queryString = new Dictionary<string, string?>
83+
{
84+
["include"] = "videoStream,audioStreams",
85+
["fields[writeOnlyChannels]"] = "name,isCommercial,videoStream,audioStreams",
86+
["fields[dataStreams]"] = "bytesTransmitted"
87+
};
88+
89+
using (_requestAdapterFactory.WithQueryString(queryString))
90+
{
91+
// Act
92+
WriteOnlyChannelPrimaryResponseDocument? response = await apiClient.WriteOnlyChannels.PostAsync(requestBody);
93+
94+
response.ShouldNotBeNull();
95+
96+
response.Data.ShouldNotBeNull();
97+
response.Data.Attributes.ShouldNotBeNull();
98+
response.Data.Attributes.Name.Should().Be(newChannel.Name);
99+
response.Data.Attributes.IsCommercial.Should().BeNull();
100+
response.Data.Attributes.IsAdultOnly.Should().BeNull();
101+
response.Data.Relationships.ShouldNotBeNull();
102+
response.Data.Relationships.VideoStream.ShouldNotBeNull();
103+
response.Data.Relationships.VideoStream.Data.ShouldNotBeNull();
104+
response.Data.Relationships.VideoStream.Data.Id.Should().Be(existingVideoStream.StringId);
105+
response.Data.Relationships.UltraHighDefinitionVideoStream.Should().BeNull();
106+
response.Data.Relationships.AudioStreams.ShouldNotBeNull();
107+
response.Data.Relationships.AudioStreams.Data.ShouldHaveCount(1);
108+
response.Data.Relationships.AudioStreams.Data.ElementAt(0).Id.Should().Be(existingAudioStream.StringId);
109+
110+
response.Included.ShouldHaveCount(2);
111+
DataStreamDataInResponse[] dataStreamIncludes = response.Included.OfType<DataStreamDataInResponse>().ToArray();
112+
113+
DataStreamDataInResponse videoStream = dataStreamIncludes.Single(include => include.Id == existingVideoStream.StringId);
114+
videoStream.Attributes.ShouldNotBeNull();
115+
videoStream.Attributes.BytesTransmitted.Should().Be((long?)existingVideoStream.BytesTransmitted);
116+
117+
DataStreamDataInResponse audioStream = dataStreamIncludes.Single(include => include.Id == existingAudioStream.StringId);
118+
audioStream.Attributes.ShouldNotBeNull();
119+
audioStream.Attributes.BytesTransmitted.Should().Be((long?)existingAudioStream.BytesTransmitted);
120+
121+
long newChannelId = int.Parse(response.Data.Id.ShouldNotBeNull());
122+
123+
await _testContext.RunOnDatabaseAsync(async dbContext =>
124+
{
125+
// @formatter:wrap_chained_method_calls chop_always
126+
// @formatter:keep_existing_linebreaks true
127+
128+
WriteOnlyChannel channelInDatabase = await dbContext.WriteOnlyChannels
129+
.Include(channel => channel.VideoStream)
130+
.Include(channel => channel.AudioStreams)
131+
.FirstWithIdAsync(newChannelId);
132+
133+
// @formatter:keep_existing_linebreaks restore
134+
// @formatter:wrap_chained_method_calls restore
135+
136+
channelInDatabase.Name.Should().Be(newChannel.Name);
137+
channelInDatabase.IsCommercial.Should().BeNull();
138+
channelInDatabase.IsAdultOnly.Should().Be(newChannel.IsAdultOnly);
139+
140+
channelInDatabase.VideoStream.ShouldNotBeNull();
141+
channelInDatabase.VideoStream.Id.Should().Be(existingVideoStream.Id);
142+
143+
channelInDatabase.AudioStreams.ShouldHaveCount(1);
144+
channelInDatabase.AudioStreams.ElementAt(0).Id.Should().Be(existingAudioStream.Id);
145+
});
146+
}
147+
}
148+
149+
[Fact]
150+
public async Task Cannot_create_resource_for_missing_request_body()
151+
{
152+
// Arrange
153+
using HttpClientRequestAdapter requestAdapter = _requestAdapterFactory.CreateAdapter(_testContext.Factory);
154+
var apiClient = new RestrictedControllersClient(requestAdapter);
155+
156+
WriteOnlyChannelPostRequestDocument requestBody = null!;
157+
158+
// Act
159+
Func<Task> action = async () => _ = await apiClient.WriteOnlyChannels.PostAsync(requestBody);
160+
161+
// Assert
162+
await action.Should().ThrowExactlyAsync<ArgumentNullException>().WithParameterName("body");
163+
}
164+
165+
[Fact]
166+
public async Task Cannot_create_resource_with_unknown_relationship_ID()
167+
{
168+
// Arrange
169+
WriteOnlyChannel newChannel = _fakers.WriteOnlyChannel.Generate();
170+
171+
string unknownVideoStreamId = Unknown.StringId.For<DataStream, long>();
172+
173+
using HttpClientRequestAdapter requestAdapter = _requestAdapterFactory.CreateAdapter(_testContext.Factory);
174+
var apiClient = new RestrictedControllersClient(requestAdapter);
175+
176+
var requestBody = new WriteOnlyChannelPostRequestDocument
177+
{
178+
Data = new WriteOnlyChannelDataInPostRequest
179+
{
180+
Type = WriteOnlyChannelResourceType.WriteOnlyChannels,
181+
Attributes = new WriteOnlyChannelAttributesInPostRequest
182+
{
183+
Name = newChannel.Name
184+
},
185+
Relationships = new WriteOnlyChannelRelationshipsInPostRequest
186+
{
187+
VideoStream = new ToOneDataStreamInRequest
188+
{
189+
Data = new DataStreamIdentifier
190+
{
191+
Type = DataStreamResourceType.DataStreams,
192+
Id = unknownVideoStreamId
193+
}
194+
}
195+
}
196+
}
197+
};
198+
199+
// Act
200+
Func<Task> action = async () => _ = await apiClient.WriteOnlyChannels.PostAsync(requestBody);
201+
202+
// Assert
203+
ErrorResponseDocument exception = (await action.Should().ThrowExactlyAsync<ErrorResponseDocument>()).Which;
204+
exception.ResponseStatusCode.Should().Be((int)HttpStatusCode.NotFound);
205+
exception.Message.Should().Be($"Exception of type '{typeof(ErrorResponseDocument).FullName}' was thrown.");
206+
exception.Errors.ShouldHaveCount(1);
207+
208+
ErrorObject error = exception.Errors.ElementAt(0);
209+
error.Status.Should().Be("404");
210+
error.Title.Should().Be("A related resource does not exist.");
211+
error.Detail.Should().Be($"Related resource of type 'dataStreams' with ID '{unknownVideoStreamId}' in relationship 'videoStream' does not exist.");
212+
}
213+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System.Net;
2+
using FluentAssertions;
3+
using Microsoft.Kiota.Http.HttpClientLibrary;
4+
using OpenApiKiotaEndToEndTests.RestrictedControllers.GeneratedCode;
5+
using OpenApiKiotaEndToEndTests.RestrictedControllers.GeneratedCode.Models;
6+
using OpenApiTests;
7+
using OpenApiTests.RestrictedControllers;
8+
using TestBuildingBlocks;
9+
using Xunit;
10+
using Xunit.Abstractions;
11+
12+
namespace OpenApiKiotaEndToEndTests.RestrictedControllers;
13+
14+
public sealed class DeleteResourceTests : IClassFixture<IntegrationTestContext<OpenApiStartup<RestrictionDbContext>, RestrictionDbContext>>
15+
{
16+
private readonly IntegrationTestContext<OpenApiStartup<RestrictionDbContext>, RestrictionDbContext> _testContext;
17+
private readonly TestableHttpClientRequestAdapterFactory _requestAdapterFactory;
18+
private readonly RestrictionFakers _fakers = new();
19+
20+
public DeleteResourceTests(IntegrationTestContext<OpenApiStartup<RestrictionDbContext>, RestrictionDbContext> testContext,
21+
ITestOutputHelper testOutputHelper)
22+
{
23+
_testContext = testContext;
24+
_requestAdapterFactory = new TestableHttpClientRequestAdapterFactory(testOutputHelper);
25+
26+
testContext.UseController<WriteOnlyChannelsController>();
27+
}
28+
29+
[Fact]
30+
public async Task Can_delete_existing_resource()
31+
{
32+
// Arrange
33+
WriteOnlyChannel existingChannel = _fakers.WriteOnlyChannel.Generate();
34+
existingChannel.VideoStream = _fakers.DataStream.Generate();
35+
36+
await _testContext.RunOnDatabaseAsync(async dbContext =>
37+
{
38+
dbContext.WriteOnlyChannels.Add(existingChannel);
39+
await dbContext.SaveChangesAsync();
40+
});
41+
42+
using HttpClientRequestAdapter requestAdapter = _requestAdapterFactory.CreateAdapter(_testContext.Factory);
43+
var apiClient = new RestrictedControllersClient(requestAdapter);
44+
45+
// Act
46+
await apiClient.WriteOnlyChannels[existingChannel.StringId].DeleteAsync();
47+
48+
// Assert
49+
await _testContext.RunOnDatabaseAsync(async dbContext =>
50+
{
51+
WriteOnlyChannel? channelInDatabase = await dbContext.WriteOnlyChannels.FirstWithIdOrDefaultAsync(existingChannel.Id);
52+
53+
channelInDatabase.Should().BeNull();
54+
});
55+
}
56+
57+
[Fact]
58+
public async Task Cannot_delete_unknown_resource()
59+
{
60+
// Arrange
61+
string unknownChannelId = Unknown.StringId.For<WriteOnlyChannel, long>();
62+
63+
using HttpClientRequestAdapter requestAdapter = _requestAdapterFactory.CreateAdapter(_testContext.Factory);
64+
var apiClient = new RestrictedControllersClient(requestAdapter);
65+
66+
// Act
67+
Func<Task> action = async () => await apiClient.WriteOnlyChannels[unknownChannelId].DeleteAsync();
68+
69+
// Assert
70+
ErrorResponseDocument exception = (await action.Should().ThrowExactlyAsync<ErrorResponseDocument>()).Which;
71+
exception.ResponseStatusCode.Should().Be((int)HttpStatusCode.NotFound);
72+
exception.Message.Should().Be($"Exception of type '{typeof(ErrorResponseDocument).FullName}' was thrown.");
73+
exception.Errors.ShouldHaveCount(1);
74+
75+
ErrorObject error = exception.Errors.ElementAt(0);
76+
error.Status.Should().Be("404");
77+
error.Title.Should().Be("The requested resource does not exist.");
78+
error.Detail.Should().Be($"Resource of type 'writeOnlyChannels' with ID '{unknownChannelId}' does not exist.");
79+
}
80+
}

0 commit comments

Comments
 (0)