Skip to content

Commit c62adb4

Browse files
committed
test(*): add integration tests
1 parent c6ed084 commit c62adb4

File tree

17 files changed

+672
-40
lines changed

17 files changed

+672
-40
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,19 @@ identifier):
166166
?filter[attribute]=gt:value
167167
?filter[attribute]=le:value
168168
?filter[attribute]=ge:value
169-
```
169+
```
170+
171+
# Tests
172+
173+
## Running
174+
175+
I am using DotNetCoreDocs to generate sample requests and documentation.
176+
177+
1. To run the tests, start a postgres server and verify the connection properties define in `/test/JsonApiDotNetCoreExampleTests/appsettings.json`
178+
2. `cd ./test/JsonApiDotNetCoreExampleTests`
179+
3. `dotnet test`
180+
4. `cd ./src/JsonApiDotNetCoreExample`
181+
5. `dotnet run`
182+
6. `open http://localhost:5000/docs`
183+
184+

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections;
32
using System.Collections.Generic;
43
using JsonApiDotNetCore.Extensions;

src/JsonApiDotNetCore/Controllers/JsonApiController.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
43
using System.Threading.Tasks;
@@ -111,7 +110,10 @@ public virtual async Task<IActionResult> GetRelationshipAsync(TId id, string rel
111110
.GetRelationshipName<T>(relationshipName.ToProperCase());
112111

113112
if (relationshipName == null)
113+
{
114+
_logger?.LogInformation($"Relationship name not specified returning 422");
114115
return UnprocessableEntity();
116+
}
115117

116118
var entity = await _entities.GetAndIncludeAsync(id, relationshipName);
117119

@@ -131,7 +133,10 @@ public virtual async Task<IActionResult> GetRelationshipAsync(TId id, string rel
131133
public virtual async Task<IActionResult> PostAsync([FromBody] T entity)
132134
{
133135
if (entity == null)
136+
{
137+
_logger?.LogInformation($"Entity cannot be null returning 422");
134138
return UnprocessableEntity();
139+
}
135140

136141
await _entities.CreateAsync(entity);
137142

@@ -142,7 +147,10 @@ public virtual async Task<IActionResult> PostAsync([FromBody] T entity)
142147
public virtual async Task<IActionResult> PatchAsync(TId id, [FromBody] T entity)
143148
{
144149
if (entity == null)
150+
{
151+
_logger?.LogInformation($"Entity cannot be null returning 422");
145152
return UnprocessableEntity();
153+
}
146154

147155
var updatedEntity = await _entities.UpdateAsync(id, entity);
148156

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
43
using System.Threading.Tasks;

src/JsonApiDotNetCore/Formatters/JsonApiInputFormatter.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using JsonApiDotNetCore.Services;
66
using Microsoft.AspNetCore.Mvc.Formatters;
77
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.Extensions.Logging;
89
using Newtonsoft.Json;
910

1011
namespace JsonApiDotNetCore.Formatters
@@ -33,16 +34,23 @@ public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
3334
return InputFormatterResult.SuccessAsync(null);
3435
}
3536

37+
var loggerFactory = GetService<ILoggerFactory>(context);
38+
var logger = loggerFactory?.CreateLogger<JsonApiInputFormatter>();
39+
3640
try
3741
{
3842
var body = GetRequestBody(context.HttpContext.Request.Body);
39-
var jsonApiContext = context.HttpContext.RequestServices.GetService<IJsonApiContext>();
43+
var jsonApiContext = GetService<IJsonApiContext>(context);
4044
var model = JsonApiDeSerializer.Deserialize(body, jsonApiContext);
4145

46+
if(model == null)
47+
logger?.LogError("An error occurred while de-serializing the payload");
48+
4249
return InputFormatterResult.SuccessAsync(model);
4350
}
44-
catch (JsonSerializationException)
51+
catch (JsonSerializationException ex)
4552
{
53+
logger?.LogError(new EventId(), ex, "An error occurred while de-serializing the payload");
4654
context.HttpContext.Response.StatusCode = 422;
4755
return InputFormatterResult.FailureAsync();
4856
}
@@ -55,5 +63,10 @@ private string GetRequestBody(Stream body)
5563
return reader.ReadToEnd();
5664
}
5765
}
66+
67+
private TService GetService<TService>(InputFormatterContext context)
68+
{
69+
return context.HttpContext.RequestServices.GetService<TService>();
70+
}
5871
}
5972
}

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using JsonApiDotNetCore.Models;
88
using JsonApiDotNetCore.Services;
99
using Newtonsoft.Json;
10-
using Newtonsoft.Json.Linq;
1110

1211
namespace JsonApiDotNetCore.Serialization
1312
{
@@ -17,24 +16,50 @@ public static object Deserialize(string requestBody, IJsonApiContext context)
1716
{
1817
var document = JsonConvert.DeserializeObject<Document>(requestBody);
1918

20-
var entityTypeName = document.Data.Type.ToProperCase();
19+
var entity = DataToObject(document.Data, context);
20+
21+
return entity;
22+
}
23+
24+
public static List<TEntity> DeserializeList<TEntity>(string requestBody, IJsonApiContext context)
25+
{
26+
var documents = JsonConvert.DeserializeObject<Documents>(requestBody);
27+
28+
var deserializedList = new List<TEntity>();
29+
foreach (var data in documents.Data)
30+
{
31+
var entity = DataToObject(data, context);
32+
deserializedList.Add((TEntity)entity);
33+
}
34+
35+
return deserializedList;
36+
}
37+
38+
private static object DataToObject(DocumentData data, IJsonApiContext context)
39+
{
40+
var entityTypeName = data.Type.ToProperCase();
2141

2242
var contextEntity = context.ContextGraph.GetContextEntity(entityTypeName);
2343
context.RequestEntity = contextEntity;
24-
44+
2545
var entity = Activator.CreateInstance(contextEntity.EntityType);
26-
27-
entity = _setEntityAttributes(entity, contextEntity, document.Data.Attributes);
28-
entity = _setRelationships(entity, contextEntity, document.Data.Relationships);
2946

30-
return entity;
47+
entity = _setEntityAttributes(entity, contextEntity, data.Attributes);
48+
entity = _setRelationships(entity, contextEntity, data.Relationships);
49+
50+
var identifiableEntity = (IIdentifiable)entity;
51+
52+
if(data.Id != null)
53+
identifiableEntity.Id = Convert.ChangeType(data.Id, identifiableEntity.Id.GetType());
54+
55+
return identifiableEntity;
3156
}
3257

3358
private static object _setEntityAttributes(
3459
object entity, ContextEntity contextEntity, Dictionary<string, object> attributeValues)
3560
{
3661
var entityProperties = entity.GetType().GetProperties();
37-
62+
3863
foreach (var attr in contextEntity.Attributes)
3964
{
4065
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalAttributeName);
@@ -56,22 +81,26 @@ private static object _setEntityAttributes(
5681
private static object _setRelationships(
5782
object entity, ContextEntity contextEntity, Dictionary<string, RelationshipData> relationships)
5883
{
59-
if(relationships == null)
84+
if (relationships == null || relationships.Count == 0)
6085
return entity;
6186

6287
var entityProperties = entity.GetType().GetProperties();
63-
88+
6489
foreach (var attr in contextEntity.Relationships)
6590
{
6691
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.RelationshipName}Id");
6792

6893
if (entityProperty == null)
69-
throw new ArgumentException($"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.RelationshipName}", nameof(entity));
70-
94+
throw new JsonApiException("400", $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.RelationshipName}");
95+
96+
var relationshipName = attr.RelationshipName.Dasherize();
7197
RelationshipData relationshipData;
72-
if (relationships.TryGetValue(attr.RelationshipName.Dasherize(), out relationshipData))
98+
if (relationships.TryGetValue(relationshipName, out relationshipData))
7399
{
74-
var data = (Dictionary<string,string>)relationshipData.ExposedData;
100+
var data = (Dictionary<string, string>)relationshipData.ExposedData;
101+
102+
if(data == null) continue;
103+
75104
var newValue = data["id"];
76105
var convertedValue = Convert.ChangeType(newValue, entityProperty.PropertyType);
77106
entityProperty.SetValue(entity, convertedValue);

src/JsonApiDotNetCoreExample/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
_data/
2+
13
## Ignore Visual Studio temporary files, build results, and
24
## files generated by popular Visual Studio add-ons.
35

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## Running the Example
2+
3+
```
4+
> dotnet run
5+
```
6+
7+
## Generate Documentation
8+
9+
```
10+
> cd ./test/JsonApiDotNetCoreExampleTests
11+
> dotnet test
12+
> cd ./src/JsonApiDotNetCoreExample
13+
> dotnet run
14+
> open http://localhost:5000/docs
15+
```

src/JsonApiDotNetCoreExample/Startup.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
using JsonApiDotNetCoreExample.Data;
88
using Microsoft.EntityFrameworkCore;
99
using JsonApiDotNetCore.Extensions;
10+
using DotNetCoreDocs.Configuration;
11+
using DotNetCoreDocs.Middleware;
12+
using System;
1013

1114
namespace JsonApiDotNetCoreExample
1215
{
@@ -25,28 +28,46 @@ public Startup(IHostingEnvironment env)
2528
_config = builder.Build();
2629
}
2730

28-
public void ConfigureServices(IServiceCollection services)
31+
public IServiceProvider ConfigureServices(IServiceCollection services)
2932
{
30-
services.AddDbContext<AppDbContext>(options => {
33+
var loggerFactory = new LoggerFactory();
34+
loggerFactory
35+
.AddConsole(LogLevel.Trace);
36+
services.AddSingleton<ILoggerFactory>(loggerFactory);
37+
38+
services.AddDbContext<AppDbContext>(options =>
39+
{
3140
options.UseNpgsql(_getDbConnectionString());
3241
}, ServiceLifetime.Transient);
33-
34-
services.AddJsonApi<AppDbContext>(opt => {
42+
43+
services.AddJsonApi<AppDbContext>(opt =>
44+
{
3545
opt.Namespace = "api/v1";
36-
opt.DefaultPageSize = 1;
46+
opt.DefaultPageSize = 5;
3747
});
48+
49+
services.AddDocumentationConfiguration(_config);
50+
51+
var provider = services.BuildServiceProvider();
52+
var appContext = provider.GetRequiredService<AppDbContext>();
53+
if(appContext == null)
54+
throw new ArgumentException();
55+
return provider;
3856
}
3957

4058
public void Configure(
41-
IApplicationBuilder app,
42-
IHostingEnvironment env,
59+
IApplicationBuilder app,
60+
IHostingEnvironment env,
4361
ILoggerFactory loggerFactory,
4462
AppDbContext context)
4563
{
4664
context.Database.Migrate();
4765

4866
loggerFactory.AddConsole(_config.GetSection("Logging"));
4967
loggerFactory.AddDebug();
68+
69+
app.UseDocs();
70+
5071
app.UseJsonApi();
5172
}
5273

src/JsonApiDotNetCoreExample/appsettings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@
99
"System": "Trace",
1010
"Microsoft": "Trace"
1111
}
12+
},
13+
"DocsConfiguration": {
14+
"BaseAddress": "http://localhost:5000",
15+
"RequestsDirectory": "../../src/JsonApiDotNetCoreExample/_data",
16+
"DocumentationRoute": "/docs"
1217
}
1318
}

src/JsonApiDotNetCoreExample/project.json

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
{
22
"dependencies": {
3-
"JsonApiDotNetCore": {
4-
"target": "project"
5-
},
63
"Microsoft.NETCore.App": {
74
"version": "1.1.0",
85
"type": "platform"
96
},
7+
"JsonApiDotNetCore": {
8+
"target": "project"
9+
},
1010
"Microsoft.AspNetCore.Mvc": "1.1.0",
1111
"Microsoft.AspNetCore.Routing": "1.1.0",
12-
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
13-
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
14-
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
15-
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
16-
"Microsoft.Extensions.Configuration.Json": "1.0.0",
17-
"Microsoft.Extensions.Configuration.CommandLine": "1.0.0",
12+
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
13+
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
14+
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
15+
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
16+
"Microsoft.Extensions.Configuration.Json": "1.1.0",
17+
"Microsoft.Extensions.Configuration.CommandLine": "1.1.0",
1818
"Microsoft.Extensions.Logging": "1.1.0",
1919
"Microsoft.Extensions.Logging.Console": "1.1.0",
2020
"Microsoft.Extensions.Logging.Debug": "1.1.0",
21-
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
21+
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
2222
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
23-
"Npgsql.EntityFrameworkCore.PostgreSQL": "1.1.0"
23+
"Npgsql.EntityFrameworkCore.PostgreSQL": "1.1.0",
24+
"DotNetCoreDocs": "0.4.0"
2425
},
2526

2627
"tools": {
@@ -67,10 +68,10 @@
6768
},
6869

6970
"scripts": {
70-
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
71+
"postpublish": ["dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"]
7172
},
7273

7374
"tooling": {
7475
"defaultNamespace": "JsonApiDotNetCoreExample"
7576
}
76-
}
77+
}

0 commit comments

Comments
 (0)