Skip to content

Commit 529a562

Browse files
committed
add DiscoveryTests
1 parent 66470a9 commit 529a562

File tree

12 files changed

+209
-8
lines changed

12 files changed

+209
-8
lines changed

Build.ps1

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ CheckLastExitCode
3838
dotnet test ./test/OperationsExampleTests/OperationsExampleTests.csproj
3939
CheckLastExitCode
4040

41+
dotnet test ./test/DiscoveryTests/DiscoveryTests.csproj
42+
CheckLastExitCode
43+
4144
dotnet build ./src/JsonApiDotNetCore/JsonApiDotNetCore.csproj -c Release
4245
CheckLastExitCode
4346

JsonApiDotnetCore.sln

+15
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResourceEntitySeparationExa
4747
EndProject
4848
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStarted", "src\Examples\GettingStarted\GettingStarted.csproj", "{9B2A5AD7-0BF4-472E-A1B4-8BB623FDEB71}"
4949
EndProject
50+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscoveryTests", "test\DiscoveryTests\DiscoveryTests.csproj", "{09C0C8D8-B721-4955-8889-55CB149C3B5C}"
51+
EndProject
5052
Global
5153
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5254
Debug|Any CPU = Debug|Any CPU
@@ -201,6 +203,18 @@ Global
201203
{9B2A5AD7-0BF4-472E-A1B4-8BB623FDEB71}.Release|x64.Build.0 = Release|Any CPU
202204
{9B2A5AD7-0BF4-472E-A1B4-8BB623FDEB71}.Release|x86.ActiveCfg = Release|Any CPU
203205
{9B2A5AD7-0BF4-472E-A1B4-8BB623FDEB71}.Release|x86.Build.0 = Release|Any CPU
206+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
207+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
208+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.ActiveCfg = Debug|Any CPU
209+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.Build.0 = Debug|Any CPU
210+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x86.ActiveCfg = Debug|Any CPU
211+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x86.Build.0 = Debug|Any CPU
212+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
213+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|Any CPU.Build.0 = Release|Any CPU
214+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.ActiveCfg = Release|Any CPU
215+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.Build.0 = Release|Any CPU
216+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.ActiveCfg = Release|Any CPU
217+
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.Build.0 = Release|Any CPU
204218
EndGlobalSection
205219
GlobalSection(SolutionProperties) = preSolution
206220
HideSolutionNode = FALSE
@@ -220,6 +234,7 @@ Global
220234
{F4097194-9415-418A-AB4E-315C5D5466AF} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
221235
{6DFA30D7-1679-4333-9779-6FB678E48EF5} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
222236
{9B2A5AD7-0BF4-472E-A1B4-8BB623FDEB71} = {026FBC6C-AF76-4568-9B87-EC73457899FD}
237+
{09C0C8D8-B721-4955-8889-55CB149C3B5C} = {24B15015-62E5-42E1-9BA0-ECE6BE7AA15F}
223238
EndGlobalSection
224239
GlobalSection(ExtensibilityGlobals) = postSolution
225240
SolutionGuid = {A2421882-8F0A-4905-928F-B550B192F9A4}

src/Examples/GettingStarted/Data/SampleDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using GettingStarted.Models;
2+
using GettingStarted.ResourceDefinitionExample;
23
using Microsoft.EntityFrameworkCore;
34

45
namespace GettingStarted
@@ -11,5 +12,6 @@ public SampleDbContext(DbContextOptions<SampleDbContext> options)
1112

1213
public DbSet<Article> Articles { get; set; }
1314
public DbSet<Person> People { get; set; }
15+
public DbSet<Model> Models { get; set; }
1416
}
1517
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using JsonApiDotNetCore.Models;
2+
3+
namespace GettingStarted.ResourceDefinitionExample
4+
{
5+
public class Model : Identifiable
6+
{
7+
[Attr]
8+
public string DontExpose { get; set; }
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Models;
3+
4+
namespace GettingStarted.ResourceDefinitionExample
5+
{
6+
public class ModelDefinition : ResourceDefinition<Model>
7+
{
8+
// this allows POST / PATCH requests to set the value of a
9+
// property, but we don't include this value in the response
10+
// this might be used if the incoming value gets hashed or
11+
// encrypted prior to being persisted and this value should
12+
// never be sent back to the client
13+
protected override List<AttrAttribute> OutputAttrs()
14+
=> Remove(model => model.DontExpose);
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using GettingStarted.Models;
2+
using JsonApiDotNetCore.Controllers;
3+
using JsonApiDotNetCore.Services;
4+
5+
namespace GettingStarted.ResourceDefinitionExample
6+
{
7+
public class ModelsController : JsonApiController<Model>
8+
{
9+
public ModelsController(
10+
IJsonApiContext jsonApiContext,
11+
IResourceService<Model> resourceService)
12+
: base(jsonApiContext, resourceService)
13+
{ }
14+
}
15+
}

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

+17-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ public class DefaultEntityRepository<TEntity>
1919
IEntityRepository<TEntity>
2020
where TEntity : class, IIdentifiable<int>
2121
{
22+
public DefaultEntityRepository(
23+
IJsonApiContext jsonApiContext,
24+
IDbContextResolver contextResolver)
25+
: base(jsonApiContext, contextResolver)
26+
{ }
27+
2228
public DefaultEntityRepository(
2329
ILoggerFactory loggerFactory,
2430
IJsonApiContext jsonApiContext,
@@ -42,6 +48,16 @@ public class DefaultEntityRepository<TEntity, TId>
4248
private readonly IJsonApiContext _jsonApiContext;
4349
private readonly IGenericProcessorFactory _genericProcessorFactory;
4450

51+
public DefaultEntityRepository(
52+
IJsonApiContext jsonApiContext,
53+
IDbContextResolver contextResolver)
54+
{
55+
_context = contextResolver.GetContext();
56+
_dbSet = contextResolver.GetDbSet<TEntity>();
57+
_jsonApiContext = jsonApiContext;
58+
_genericProcessorFactory = _jsonApiContext.GenericProcessorFactory;
59+
}
60+
4561
public DefaultEntityRepository(
4662
ILoggerFactory loggerFactory,
4763
IJsonApiContext jsonApiContext,
@@ -84,7 +100,7 @@ public virtual async Task<TEntity> GetAsync(TId id)
84100
/// <inheritdoc />
85101
public virtual async Task<TEntity> GetAndIncludeAsync(TId id, string relationshipName)
86102
{
87-
_logger.LogDebug($"[JADN] GetAndIncludeAsync({id}, {relationshipName})");
103+
_logger?.LogDebug($"[JADN] GetAndIncludeAsync({id}, {relationshipName})");
88104

89105
var includedSet = Include(Get(), relationshipName);
90106
var result = await includedSet.SingleOrDefaultAsync(e => e.Id.Equals(id));

src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,11 @@ private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescr
171171

172172
private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
173173
{
174-
var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, resourceDescriptor.ResourceType, resourceDescriptor.IdType);
174+
var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2
175+
? new [] { resourceDescriptor.ResourceType, resourceDescriptor.IdType }
176+
: new [] { resourceDescriptor.ResourceType };
177+
178+
var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments);
175179
if (service.implementation != null)
176180
_services.AddScoped(service.registrationInterface, service.implementation);
177181
}

src/JsonApiDotNetCore/Models/ResourceDefinition.cs

+14-2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected List<AttrAttribute> Remove(Expression<Func<T, dynamic>> filter, List<A
6464
var attributes = new List<AttrAttribute>();
6565
foreach (var attr in _contextEntity.Attributes)
6666
if (newExpression.Members.Any(m => m.Name == attr.InternalAttributeName) == false)
67-
attributes.Add(attr);
67+
attributes.Add(attr);
6868

6969
return attributes;
7070
}
@@ -76,12 +76,24 @@ protected List<AttrAttribute> Remove(Expression<Func<T, dynamic>> filter, List<A
7676
}
7777

7878
/// <summary>
79+
/// Allows POST / PATCH requests to set the value of an
80+
/// attribute, but exclude the attribute in the response
81+
/// this might be used if the incoming value gets hashed or
82+
/// encrypted prior to being persisted and this value should
83+
/// never be sent back to the client.
84+
///
7985
/// Called once per filtered resource in request.
8086
/// </summary>
8187
protected virtual List<AttrAttribute> OutputAttrs() => _contextEntity.Attributes;
8288

8389
/// <summary>
84-
/// Called for every instance of a resource
90+
/// Allows POST / PATCH requests to set the value of an
91+
/// attribute, but exclude the attribute in the response
92+
/// this might be used if the incoming value gets hashed or
93+
/// encrypted prior to being persisted and this value should
94+
/// never be sent back to the client.
95+
///
96+
/// Called for every instance of a resource.
8597
/// </summary>
8698
protected virtual List<AttrAttribute> OutputAttrs(T instance) => _contextEntity.Attributes;
8799

src/JsonApiDotNetCore/Services/EntityResourceService.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class EntityResourceService<TResource> : EntityResourceService<TResource,
1616
public EntityResourceService(
1717
IJsonApiContext jsonApiContext,
1818
IEntityRepository<TResource> entityRepository,
19-
ILoggerFactory loggerFactory) :
19+
ILoggerFactory loggerFactory = null) :
2020
base(jsonApiContext, entityRepository, loggerFactory)
2121
{ }
2222
}
@@ -28,7 +28,7 @@ public class EntityResourceService<TResource, TId> : EntityResourceService<TReso
2828
public EntityResourceService(
2929
IJsonApiContext jsonApiContext,
3030
IEntityRepository<TResource, TId> entityRepository,
31-
ILoggerFactory loggerFactory) :
31+
ILoggerFactory loggerFactory = null) :
3232
base(jsonApiContext, entityRepository, loggerFactory)
3333
{ }
3434
}
@@ -46,7 +46,7 @@ public class EntityResourceService<TResource, TEntity, TId> :
4646
public EntityResourceService(
4747
IJsonApiContext jsonApiContext,
4848
IEntityRepository<TEntity, TId> entityRepository,
49-
ILoggerFactory loggerFactory)
49+
ILoggerFactory loggerFactory = null)
5050
{
5151
// no mapper provided, TResource & TEntity must be the same type
5252
if (typeof(TResource) != typeof(TEntity))
@@ -56,7 +56,7 @@ public EntityResourceService(
5656

5757
_jsonApiContext = jsonApiContext;
5858
_entities = entityRepository;
59-
_logger = loggerFactory.CreateLogger<EntityResourceService<TResource, TEntity, TId>>();
59+
_logger = loggerFactory?.CreateLogger<EntityResourceService<TResource, TEntity, TId>>();
6060
}
6161

6262
public EntityResourceService(
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(NetCoreAppVersion)</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<ProjectReference Include="../../src/Examples/GettingStarted/GettingStarted.csproj" />
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
14+
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitVersion)" />
15+
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
16+
<PackageReference Include="Moq" Version="$(MoqVersion)" />
17+
</ItemGroup>
18+
19+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System;
2+
using GettingStarted.Models;
3+
using GettingStarted.ResourceDefinitionExample;
4+
using JsonApiDotNetCore.Builders;
5+
using JsonApiDotNetCore.Data;
6+
using JsonApiDotNetCore.Graph;
7+
using JsonApiDotNetCore.Models;
8+
using JsonApiDotNetCore.Services;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Logging;
11+
using Moq;
12+
using Xunit;
13+
14+
namespace DiscoveryTests
15+
{
16+
public class ServiceDiscoveryFacadeTests
17+
{
18+
private readonly IServiceCollection _services = new ServiceCollection();
19+
private readonly ContextGraphBuilder _graphBuilder = new ContextGraphBuilder();
20+
private ServiceDiscoveryFacade _facade => new ServiceDiscoveryFacade(_services, _graphBuilder);
21+
22+
[Fact]
23+
public void AddAssembly_Adds_All_Resources_To_Graph()
24+
{
25+
// arrange, act
26+
_facade.AddAssembly(typeof(Person).Assembly);
27+
28+
// assert
29+
var graph = _graphBuilder.Build();
30+
var personResource = graph.GetContextEntity(typeof(Person));
31+
var articleResource = graph.GetContextEntity(typeof(Article));
32+
var modelResource = graph.GetContextEntity(typeof(Model));
33+
34+
Assert.NotNull(personResource);
35+
Assert.NotNull(articleResource);
36+
Assert.NotNull(modelResource);
37+
}
38+
39+
[Fact]
40+
public void AddCurrentAssembly_Adds_Resources_To_Graph()
41+
{
42+
// arrange, act
43+
_facade.AddCurrentAssembly();
44+
45+
// assert
46+
var graph = _graphBuilder.Build();
47+
var testModelResource = graph.GetContextEntity(typeof(TestModel));
48+
Assert.NotNull(testModelResource);
49+
}
50+
51+
[Fact]
52+
public void AddCurrentAssembly_Adds_Services_To_Container()
53+
{
54+
// arrange, act
55+
_facade.AddCurrentAssembly();
56+
57+
// assert
58+
var services = _services.BuildServiceProvider();
59+
Assert.IsType<TestModelService>(services.GetService<IResourceService<TestModel>>());
60+
}
61+
62+
[Fact]
63+
public void AddCurrentAssembly_Adds_Repositories_To_Container()
64+
{
65+
// arrange, act
66+
_facade.AddCurrentAssembly();
67+
68+
// assert
69+
var services = _services.BuildServiceProvider();
70+
Assert.IsType<TestModelRepository>(services.GetService<IEntityRepository<TestModel>>());
71+
}
72+
73+
public class TestModel : Identifiable { }
74+
75+
public class TestModelService : EntityResourceService<TestModel>
76+
{
77+
private static IEntityRepository<TestModel> _repo = new Mock<IEntityRepository<TestModel>>().Object;
78+
private static IJsonApiContext _jsonApiContext = new Mock<IJsonApiContext>().Object;
79+
public TestModelService() : base(_jsonApiContext, _repo) { }
80+
}
81+
82+
public class TestModelRepository : DefaultEntityRepository<TestModel>
83+
{
84+
private static IDbContextResolver _dbContextResolver = new Mock<IDbContextResolver>().Object;
85+
private static IJsonApiContext _jsonApiContext = new Mock<IJsonApiContext>().Object;
86+
public TestModelRepository() : base(_jsonApiContext, _dbContextResolver) { }
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)