Skip to content

Commit 86e7055

Browse files
committed
test: unit tests passing resource hooks
1 parent 2838443 commit 86e7055

24 files changed

+770
-683
lines changed

src/Examples/JsonApiDotNetCoreExample/Resources/ArticleResource.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ namespace JsonApiDotNetCoreExample.Resources
1010
{
1111
public class ArticleResource : ResourceDefinition<Article>
1212
{
13-
public override IEnumerable<Article> AfterRead(IEnumerable<Article> entities, ResourceAction pipeline, bool nestedHook = false)
14-
{
15-
if (pipeline == ResourceAction.GetSingle && entities.Single().Name == "Classified")
16-
{
17-
throw new JsonApiException(403, "You are not allowed to see this article!", new UnauthorizedAccessException());
18-
}
19-
return entities.Where(t => t.Name != "This should be not be included");
20-
}
13+
//public override IEnumerable<Article> AfterRead(IEnumerable<Article> entities, ResourceAction pipeline, bool nestedHook = false)
14+
//{
15+
// if (pipeline == ResourceAction.GetSingle && entities.Single().Name == "Classified")
16+
// {
17+
// throw new JsonApiException(403, "You are not allowed to see this article!", new UnauthorizedAccessException());
18+
// }
19+
// return entities.Where(t => t.Name != "This should be not be included");
20+
//}
2121
}
2222
}
2323

src/Examples/JsonApiDotNetCoreExample/Resources/TagResource.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ namespace JsonApiDotNetCoreExample.Resources
88
{
99
public class TagResource : ResourceDefinition<Tag>
1010
{
11-
public override IEnumerable<Tag> AfterRead(IEnumerable<Tag> entities, ResourceAction pipeline, bool nestedHook = false)
12-
{
13-
return entities.Where(t => t.Name != "This should be not be included");
14-
}
11+
//public override IEnumerable<Tag> AfterRead(IEnumerable<Tag> entities, ResourceAction pipeline, bool nestedHook = false)
12+
//{
13+
// return entities.Where(t => t.Name != "This should be not be included");
14+
//}
1515
}
1616
}

src/JsonApiDotNetCore/Hooks/IResourceHookExecutor.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ public interface IResourceHookContainer<T> : IBeforeHooks<T>, IAfterHooks<T>, IO
1111

1212
public interface IAfterHooks<T> where T : class, IIdentifiable
1313
{
14-
void AfterCreate(IEnumerable<T> entities, ResourceHook pipeline);
15-
void AfterRead(IEnumerable<T> entities, ResourceHook pipeline);
16-
void AfterUpdate(IEnumerable<T> entities, ResourceHook pipeline);
17-
void AfterDelete(IEnumerable<T> entities, ResourceHook pipeline);
14+
void AfterCreate(IEnumerable<T> entities, ResourceAction pipeline);
15+
void AfterRead(IEnumerable<T> entities, ResourceAction pipeline, bool isRelated = false);
16+
void AfterUpdate(IEnumerable<T> entities, ResourceAction pipeline);
17+
void AfterDelete(IEnumerable<T> entities, ResourceAction pipeline, bool succeeded);
18+
void AfterUpdateRelationship(IUpdatedRelationshipHelper<T> relationshipHelper, ResourceAction pipeline);
1819
}
1920

2021
public interface IBeforeHooks<T> where T : class, IIdentifiable
@@ -45,10 +46,10 @@ public interface IBeforeExecutor
4546

4647
public interface IAfterExecutor
4748
{
48-
void AfterCreate<TEntity>(IEnumerable<TEntity> entities, ResourceHook pipeline) where TEntity : class, IIdentifiable;
49-
void AfterRead<TEntity>(IEnumerable<TEntity> entities, ResourceHook pipeline) where TEntity : class, IIdentifiable;
50-
void AfterUpdate<TEntity>(IEnumerable<TEntity> entities, ResourceHook pipeline) where TEntity : class, IIdentifiable;
51-
void AfterDelete<TEntity>(IEnumerable<TEntity> entities, ResourceHook pipeline) where TEntity : class, IIdentifiable;
49+
void AfterCreate<TEntity>(IEnumerable<TEntity> entities, ResourceAction pipeline) where TEntity : class, IIdentifiable;
50+
void AfterRead<TEntity>(IEnumerable<TEntity> entities, ResourceAction pipeline) where TEntity : class, IIdentifiable;
51+
void AfterUpdate<TEntity>(IEnumerable<TEntity> entities, ResourceAction pipeline) where TEntity : class, IIdentifiable;
52+
void AfterDelete<TEntity>(IEnumerable<TEntity> entities, ResourceAction pipeline, bool succeeded) where TEntity : class, IIdentifiable;
5253
}
5354

5455
public interface IOnExecutor

src/JsonApiDotNetCore/Hooks/ResourceHookExecutor.cs

Lines changed: 198 additions & 164 deletions
Large diffs are not rendered by default.

src/JsonApiDotNetCore/Hooks/TreeTraversal/ResourceHookEnum.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ public enum ResourceHook
88
{
99
None, // https://stackoverflow.com/questions/24151354/is-it-a-good-practice-to-add-a-null-or-none-member-to-the-enum
1010
BeforeCreate,
11-
AfterCreate,
1211
BeforeRead,
13-
AfterRead,
1412
BeforeUpdate,
15-
AfterUpdate,
1613
BeforeDelete,
17-
AfterDelete,
1814
BeforeUpdateRelationship,
1915
BeforeImplicitUpdateRelationship,
20-
//AfterUpdateRelationship
16+
OnReturn,
17+
AfterCreate,
18+
AfterRead,
19+
AfterUpdate,
20+
AfterDelete,
21+
AfterUpdateRelationship,
2122
}
2223

2324
}

src/JsonApiDotNetCore/Internal/ResourceGraph.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public interface IResourceGraph
6969
/// </summary>
7070
/// <param name="internalAttributeName">The internal attribute name for a <see cref="AttrAttribute" />.</param>
7171
string GetPublicAttributeName<TParent>(string internalAttributeName);
72+
RelationshipAttribute GetInverseRelationship(RelationshipAttribute relationship);
7273

7374
/// <summary>
7475
/// Was built against an EntityFrameworkCore DbContext ?
@@ -181,7 +182,7 @@ public string GetPublicAttributeName<TParent>(string internalAttributeName)
181182
public RelationshipAttribute GetInverseRelationship(RelationshipAttribute relationship)
182183
{
183184
if (relationship.InverseNavigation == null) return null;
184-
var attr = GetContextEntity(relationship.DependentType).Relationships.SingleOrDefault(r => r.InternalRelationshipName == relationship.InverseNavigation);
185+
return GetContextEntity(relationship.DependentType).Relationships.SingleOrDefault(r => r.InternalRelationshipName == relationship.InverseNavigation);
185186
}
186187
}
187188
}

src/JsonApiDotNetCore/Models/ResourceDefinition.cs

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -167,63 +167,19 @@ private List<AttrAttribute> GetOutputAttrs()
167167
public virtual QueryFilters GetQueryFilters() => null;
168168

169169

170-
/// <inheritdoc/>
171-
public virtual IEnumerable<T> BeforeCreate(IEnumerable<T> entities, ResourceAction pipeline)
172-
{
173-
return entities;
174-
}
175-
176-
/// <inheritdoc/>
177-
public virtual IEnumerable<T> AfterCreate(IEnumerable<T> entities, HookExecutionContext<T> context)
178-
{
179-
return entities;
180-
}
181-
182-
/// <inheritdoc/>
183-
public virtual void BeforeRead(ResourceAction pipeline, bool nestedHook = false, string stringId = null)
184-
{
185-
return;
186-
}
187-
188-
/// <inheritdoc/>
189-
public virtual IEnumerable<T> AfterRead(IEnumerable<T> entities, ResourceAction pipeline, bool nestedHook = false)
190-
{
191-
return entities;
192-
}
193-
194-
/// <inheritdoc/>
195-
public virtual IEnumerable<T> BeforeUpdate(EntityDiff<T> entityDiff, ResourceAction pipeline)
196-
{
197-
return entityDiff.RequestEntities;
198-
}
199-
170+
public virtual void AfterCreate(IEnumerable<T> entities, ResourceAction pipeline) { }
171+
public virtual void AfterRead(IEnumerable<T> entities, ResourceAction pipeline, bool isRelated = false) { }
172+
public virtual void AfterUpdate(IEnumerable<T> entities, ResourceAction pipeline) { }
173+
public virtual void AfterDelete(IEnumerable<T> entities, ResourceAction pipeline, bool succeeded) { }
174+
public virtual void AfterUpdateRelationship(IUpdatedRelationshipHelper<T> relationshipHelper, ResourceAction pipeline) { }
175+
public virtual IEnumerable<T> BeforeCreate(IEnumerable<T> entities, ResourceAction pipeline) { return entities; }
176+
public virtual void BeforeRead(ResourceAction pipeline, bool nestedHook = false, string stringId = null) { }
177+
public virtual IEnumerable<T> BeforeUpdate(EntityDiff<T> entityDiff, ResourceAction pipeline) { return entityDiff.RequestEntities; }
178+
public virtual IEnumerable<T> BeforeDelete(IEnumerable<T> entities, ResourceAction pipeline) { return entities; }
179+
public virtual IEnumerable<string> BeforeUpdateRelationship(IEnumerable<string> ids, IUpdatedRelationshipHelper<T> relationshipHelper, ResourceAction pipeline) { return ids; }
180+
public virtual void BeforeImplicitUpdateRelationship(IUpdatedRelationshipHelper<T> relationshipHelper, ResourceAction pipeline) { }
181+
public virtual IEnumerable<T> OnReturn(IEnumerable<T> entities, ResourceAction pipeline) { return entities; }
200182

201-
/// <inheritdoc/>
202-
public virtual IEnumerable<T> AfterUpdate(IEnumerable<T> entities, HookExecutionContext<T> context)
203-
{
204-
return entities;
205-
}
206-
207-
/// <inheritdoc/>
208-
public virtual IEnumerable<T> BeforeDelete(IEnumerable<T> entities, ResourceAction pipeline)
209-
{
210-
return entities;
211-
}
212-
213-
/// <inheritdoc/>
214-
public virtual void AfterDelete(IEnumerable<T> entities, bool succeeded)
215-
{
216-
}
217-
218-
public virtual IEnumerable<string> BeforeUpdateRelationship(IEnumerable<string> ids, IUpdatedRelationshipHelper<T> relationshipHelper, ResourceAction pipeline)
219-
{
220-
return ids;
221-
}
222-
223-
public virtual void BeforeImplicitUpdateRelationship(IUpdatedRelationshipHelper<T> relationshipHelper, ResourceAction pipeline)
224-
{
225-
return;
226-
}
227183

228184

229185
/// <summary>

src/JsonApiDotNetCore/Services/EntityResourceService.cs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ public virtual async Task<TResource> CreateAsync(TResource resource)
9797

9898
}
9999

100-
entity = IsNull(_hookExecutor, entity) ? entity : _hookExecutor.AfterRead(AsList(entity), ResourceAction.Create).SingleOrDefault();
100+
if (!IsNull(_hookExecutor, entity))
101+
{
102+
_hookExecutor.AfterCreate(AsList(entity), ResourceAction.Create);
103+
entity = _hookExecutor.OnReturn(AsList(entity), ResourceAction.Get).SingleOrDefault();
104+
}
101105

102106
return MapOut(entity);
103107
}
@@ -107,7 +111,7 @@ public virtual async Task<bool> DeleteAsync(TId id)
107111
var entity = await _entities.GetAsync(id);
108112
if (!IsNull(_hookExecutor, entity)) _hookExecutor.BeforeDelete(AsList(entity), ResourceAction.Delete);
109113
var succeeded = await _entities.DeleteAsync(entity);
110-
//if (!IsNull(_hookExecutor, entity)) _hookExecutor.AfterDelete(AsList(entity), ResourceAction.Delete, succeeded);
114+
if (!IsNull(_hookExecutor, entity)) _hookExecutor.AfterDelete(AsList(entity), ResourceAction.Delete, succeeded);
111115
return succeeded;
112116
}
113117

@@ -121,18 +125,18 @@ public virtual async Task<IEnumerable<TResource>> GetAsync()
121125
if (ShouldIncludeRelationships())
122126
entities = IncludeRelationships(entities, _jsonApiContext.QuerySet.IncludedRelationships);
123127

124-
/// entities.ToList() will execute the query. In the future, when EF
125-
/// Core #1833 is suported, we will support for filtered include
126-
/// prior to executing the query
127-
entities = IsNull(_hookExecutor, entities) ? entities : _hookExecutor.AfterRead(entities.ToList(), ResourceAction.Get).AsQueryable();
128+
if (_jsonApiContext.QuerySet?.Fields?.Count > 0)
129+
entities = _entities.Select(entities, _jsonApiContext.QuerySet.Fields);
128130

131+
if (!IsNull(_hookExecutor, entities))
132+
{
133+
_hookExecutor.AfterRead(entities.ToList(), ResourceAction.Get);
134+
entities = _hookExecutor.OnReturn(entities, ResourceAction.Get).AsQueryable();
135+
}
129136

130137
if (_jsonApiContext.Options.IncludeTotalRecordCount)
131138
_jsonApiContext.PageManager.TotalRecords = await _entities.CountAsync(entities);
132139

133-
if (_jsonApiContext.QuerySet?.Fields?.Count > 0)
134-
entities = _entities.Select(entities, _jsonApiContext.QuerySet.Fields);
135-
136140
// pagination should be done last since it will execute the query
137141
var pagedEntities = await ApplyPageQueryAsync(entities);
138142
return pagedEntities;
@@ -153,7 +157,8 @@ public virtual async Task<TResource> GetAsync(TId id)
153157
}
154158
if(!IsNull(_hookExecutor, entity))
155159
{
156-
entity = _hookExecutor.AfterRead(AsList(entity), ResourceAction.GetSingle).SingleOrDefault();
160+
_hookExecutor.AfterRead(AsList(entity), ResourceAction.GetSingle);
161+
entity = _hookExecutor.OnReturn(AsList(entity), ResourceAction.Get).SingleOrDefault();
157162
}
158163
return MapOut(entity);
159164

@@ -168,7 +173,12 @@ public virtual async Task<object> GetRelationshipAsync(TId id, string relationsh
168173

169174
_hookExecutor?.BeforeRead<TEntity>(ResourceAction.GetRelationship, id.ToString());
170175
var entity = await _entities.GetAndIncludeAsync(id, relationshipName);
171-
entity = IsNull(_hookExecutor, entity) ? entity : _hookExecutor.AfterRead(AsList(entity), ResourceAction.GetRelationship).SingleOrDefault();
176+
if (!IsNull(_hookExecutor, entity))
177+
{
178+
// TODO: should not fire after read for L=0
179+
_hookExecutor.AfterRead(AsList(entity), ResourceAction.GetSingle);
180+
entity = _hookExecutor.OnReturn(AsList(entity), ResourceAction.Get).SingleOrDefault();
181+
}
172182

173183

174184
// TODO: it would be better if we could distinguish whether or not the relationship was not found,
@@ -196,7 +206,12 @@ public virtual async Task<TResource> UpdateAsync(TId id, TResource resource)
196206

197207
entity = IsNull(_hookExecutor) ? entity : _hookExecutor.BeforeUpdate(AsList(entity), ResourceAction.Patch).SingleOrDefault();
198208
entity = await _entities.UpdateAsync(id, entity);
199-
entity = IsNull(_hookExecutor, entity) ? entity : _hookExecutor.AfterRead(AsList(entity), ResourceAction.Patch).SingleOrDefault();
209+
if (!IsNull(_hookExecutor, entity))
210+
{
211+
// TODO: should not fire after read for L=0
212+
_hookExecutor.AfterUpdate(AsList(entity), ResourceAction.Patch);
213+
entity = _hookExecutor.OnReturn(AsList(entity), ResourceAction.Patch).SingleOrDefault();
214+
}
200215

201216
return MapOut(entity);
202217
}
@@ -233,7 +248,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa
233248

234249
entity = IsNull(_hookExecutor) ? entity : _hookExecutor.BeforeUpdate(AsList(entity), ResourceAction.PatchRelationship).SingleOrDefault();
235250
await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds);
236-
entity = IsNull(_hookExecutor, entity) ? entity : _hookExecutor.AfterRead(AsList(entity), ResourceAction.PatchRelationship).SingleOrDefault();
251+
if (!IsNull(_hookExecutor, entity)) _hookExecutor.AfterUpdate(AsList(entity), ResourceAction.Patch);
237252

238253
relationship.Type = relationshipType;
239254
}
Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,12 @@
1-
using JsonApiDotNetCore.Builders;
21
using JsonApiDotNetCore.Models;
32
using JsonApiDotNetCore.Services;
4-
using JsonApiDotNetCoreExample.Models;
53
using System.Collections.Generic;
64
using Xunit;
75

86
namespace UnitTests.ResourceHooks
97
{
108
public class DiscoveryTests
119
{
12-
public DiscoveryTests()
13-
{
14-
// Build() exposes the static ResourceGraphBuilder.Instance member, which
15-
// is consumed by ResourceDefinition class.
16-
new ResourceGraphBuilder()
17-
.AddResource<TodoItem>()
18-
.AddResource<Person>()
19-
.Build();
20-
}
21-
2210
[Fact]
2311
public void Hook_Discovery()
2412
{
@@ -33,7 +21,7 @@ public class Dummy : Identifiable { }
3321
public class DummyResourceDefinition : ResourceDefinition<Dummy>
3422
{
3523
public override IEnumerable<Dummy> BeforeDelete(IEnumerable<Dummy> entities, ResourceAction pipeline) { return entities; }
36-
public override void AfterDelete(IEnumerable<Dummy> entities, bool succeeded) { }
24+
public override void AfterDelete(IEnumerable<Dummy> entities, ResourceAction pipeline, bool succeeded) { }
3725
}
3826
}
3927
}

test/UnitTests/ResourceHooks/ResourceHookExecutor/AfterCreateTests.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,8 @@
88

99
//namespace UnitTests.ResourceHooks
1010
//{
11-
// public class AfterCreateTests : ResourceHooksTestBase
11+
// public class AfterCreateTests : HooksTestsSetup
1212
// {
13-
// public AfterCreateTests()
14-
// {
15-
// // Build() exposes the static ResourceGraphBuilder.Instance member, which
16-
// // is consumed by ResourceDefinition class.
17-
// new ResourceGraphBuilder()
18-
// .AddResource<TodoItem>()
19-
// .AddResource<Person>()
20-
// .Build();
21-
// }
22-
2313
// [Fact]
2414
// public void AfterCreate()
2515
// {

test/UnitTests/ResourceHooks/ResourceHookExecutor/AfterDeleteTests.cs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using JsonApiDotNetCore.Builders;
2-
using JsonApiDotNetCore.Models;
31
using JsonApiDotNetCore.Services;
42
using JsonApiDotNetCoreExample.Models;
53
using Moq;
@@ -8,18 +6,8 @@
86

97
namespace UnitTests.ResourceHooks
108
{
11-
public class AfterDeleteTests : ResourceHooksTestBase
9+
public class AfterDeleteTests : HooksTestsSetup
1210
{
13-
public AfterDeleteTests()
14-
{
15-
// Build() exposes the static ResourceGraphBuilder.Instance member, which
16-
// is consumed by ResourceDefinition class.
17-
new ResourceGraphBuilder()
18-
.AddResource<TodoItem>()
19-
.AddResource<Person>()
20-
.Build();
21-
}
22-
2311
[Fact]
2412
public void AfterDelete()
2513
{
@@ -29,9 +17,9 @@ public void AfterDelete()
2917

3018
var todoList = CreateTodoWithOwner();
3119
// act
32-
hookExecutor.AfterDelete(todoList, It.IsAny<bool>());
20+
hookExecutor.AfterDelete(todoList, ResourceAction.Delete, It.IsAny<bool>());
3321
// assert
34-
resourceDefinitionMock.Verify(rd => rd.AfterDelete(It.IsAny<IEnumerable<TodoItem>>(), It.IsAny<bool>()), Times.Once());
22+
resourceDefinitionMock.Verify(rd => rd.AfterDelete(It.IsAny<IEnumerable<TodoItem>>(), ResourceAction.Delete, It.IsAny<bool>()), Times.Once());
3523
resourceDefinitionMock.VerifyNoOtherCalls();
3624

3725
}
@@ -45,7 +33,7 @@ public void AfterDelete_Without_Any_Hook_Implemented()
4533

4634
var todoList = CreateTodoWithOwner();
4735
// act
48-
hookExecutor.AfterDelete(todoList, It.IsAny<bool>());
36+
hookExecutor.AfterDelete(todoList, ResourceAction.Delete, It.IsAny<bool>());
4937
// assert
5038
resourceDefinitionMock.VerifyNoOtherCalls();
5139
}

test/UnitTests/ResourceHooks/ResourceHookExecutor/AfterUpdateTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
//namespace UnitTests.ResourceHooks
1010
//{
11-
// public class AfterUpdateTests : ResourceHooksTestBase
11+
// public class AfterUpdateTests : HooksTestsSetup
1212
// {
1313
// public AfterUpdateTests()
1414
// {

0 commit comments

Comments
 (0)