Skip to content

Commit cbc60a7

Browse files
author
Bart Koelman
committed
Added tests for serialization callbacks
1 parent f6f8acf commit cbc60a7

15 files changed

+888
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using System.Collections.Generic;
2+
using System.Net;
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
5+
using FluentAssertions;
6+
using JsonApiDotNetCore.Configuration;
7+
using JsonApiDotNetCore.Resources;
8+
using JsonApiDotNetCore.Serialization.Objects;
9+
using JsonApiDotNetCoreExample.Controllers;
10+
using JsonApiDotNetCoreExampleTests.Startups;
11+
using Microsoft.EntityFrameworkCore;
12+
using Microsoft.Extensions.DependencyInjection;
13+
using TestBuildingBlocks;
14+
using Xunit;
15+
16+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions.Serialization
17+
{
18+
public sealed class AtomicSerializationResourceDefinitionTests
19+
: IClassFixture<ExampleIntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext>>
20+
{
21+
private readonly ExampleIntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext> _testContext;
22+
private readonly OperationsFakers _fakers = new OperationsFakers();
23+
24+
public AtomicSerializationResourceDefinitionTests(ExampleIntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext> testContext)
25+
{
26+
_testContext = testContext;
27+
28+
testContext.UseController<OperationsController>();
29+
30+
testContext.ConfigureServicesAfterStartup(services =>
31+
{
32+
services.AddResourceDefinition<RecordCompanyDefinition>();
33+
services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>));
34+
});
35+
}
36+
37+
[Fact]
38+
public async Task Transforms_in_create_resource_with_side_effects()
39+
{
40+
// Arrange
41+
List<RecordCompany> newCompanies = _fakers.RecordCompany.Generate(2);
42+
43+
await _testContext.RunOnDatabaseAsync(async dbContext =>
44+
{
45+
await dbContext.ClearTableAsync<RecordCompany>();
46+
});
47+
48+
var requestBody = new
49+
{
50+
atomic__operations = new[]
51+
{
52+
new
53+
{
54+
op = "add",
55+
data = new
56+
{
57+
type = "recordCompanies",
58+
attributes = new
59+
{
60+
name = newCompanies[0].Name,
61+
countryOfResidence = newCompanies[0].CountryOfResidence
62+
}
63+
}
64+
},
65+
new
66+
{
67+
op = "add",
68+
data = new
69+
{
70+
type = "recordCompanies",
71+
attributes = new
72+
{
73+
name = newCompanies[1].Name,
74+
countryOfResidence = newCompanies[1].CountryOfResidence
75+
}
76+
}
77+
}
78+
}
79+
};
80+
81+
const string route = "/operations";
82+
83+
// Act
84+
(HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) =
85+
await _testContext.ExecutePostAtomicAsync<AtomicOperationsDocument>(route, requestBody);
86+
87+
// Assert
88+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
89+
90+
responseDocument.Results.Should().HaveCount(2);
91+
92+
responseDocument.Results[0].SingleData.Attributes["name"].Should().Be(newCompanies[0].Name.ToUpperInvariant());
93+
responseDocument.Results[0].SingleData.Attributes["countryOfResidence"].Should().Be(newCompanies[0].CountryOfResidence.ToUpperInvariant());
94+
95+
responseDocument.Results[1].SingleData.Attributes["name"].Should().Be(newCompanies[1].Name.ToUpperInvariant());
96+
responseDocument.Results[1].SingleData.Attributes["countryOfResidence"].Should().Be(newCompanies[1].CountryOfResidence.ToUpperInvariant());
97+
98+
await _testContext.RunOnDatabaseAsync(async dbContext =>
99+
{
100+
List<RecordCompany> companiesInDatabase = await dbContext.RecordCompanies.ToListAsync();
101+
companiesInDatabase.Should().HaveCount(2);
102+
103+
companiesInDatabase[0].Name.Should().Be(newCompanies[0].Name.ToUpperInvariant());
104+
companiesInDatabase[0].CountryOfResidence.Should().Be(newCompanies[0].CountryOfResidence);
105+
106+
companiesInDatabase[1].Name.Should().Be(newCompanies[1].Name.ToUpperInvariant());
107+
companiesInDatabase[1].CountryOfResidence.Should().Be(newCompanies[1].CountryOfResidence);
108+
});
109+
}
110+
111+
[Fact]
112+
public async Task Transforms_in_update_resource_with_side_effects()
113+
{
114+
// Arrange
115+
List<RecordCompany> existingCompanies = _fakers.RecordCompany.Generate(2);
116+
117+
await _testContext.RunOnDatabaseAsync(async dbContext =>
118+
{
119+
await dbContext.ClearTableAsync<RecordCompany>();
120+
dbContext.RecordCompanies.AddRange(existingCompanies);
121+
await dbContext.SaveChangesAsync();
122+
});
123+
124+
var requestBody = new
125+
{
126+
atomic__operations = new[]
127+
{
128+
new
129+
{
130+
op = "update",
131+
data = new
132+
{
133+
type = "recordCompanies",
134+
id = existingCompanies[0].StringId,
135+
attributes = new
136+
{
137+
}
138+
}
139+
},
140+
new
141+
{
142+
op = "update",
143+
data = new
144+
{
145+
type = "recordCompanies",
146+
id = existingCompanies[1].StringId,
147+
attributes = new
148+
{
149+
}
150+
}
151+
}
152+
}
153+
};
154+
155+
const string route = "/operations";
156+
157+
// Act
158+
(HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) =
159+
await _testContext.ExecutePostAtomicAsync<AtomicOperationsDocument>(route, requestBody);
160+
161+
// Assert
162+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
163+
164+
responseDocument.Results.Should().HaveCount(2);
165+
166+
responseDocument.Results[0].SingleData.Attributes["name"].Should().Be(existingCompanies[0].Name);
167+
responseDocument.Results[0].SingleData.Attributes["countryOfResidence"].Should().Be(existingCompanies[0].CountryOfResidence.ToUpperInvariant());
168+
169+
responseDocument.Results[1].SingleData.Attributes["name"].Should().Be(existingCompanies[1].Name);
170+
responseDocument.Results[1].SingleData.Attributes["countryOfResidence"].Should().Be(existingCompanies[1].CountryOfResidence.ToUpperInvariant());
171+
172+
await _testContext.RunOnDatabaseAsync(async dbContext =>
173+
{
174+
List<RecordCompany> companiesInDatabase = await dbContext.RecordCompanies.ToListAsync();
175+
companiesInDatabase.Should().HaveCount(2);
176+
177+
companiesInDatabase[0].Name.Should().Be(existingCompanies[0].Name);
178+
companiesInDatabase[0].CountryOfResidence.Should().Be(existingCompanies[0].CountryOfResidence);
179+
180+
companiesInDatabase[1].Name.Should().Be(existingCompanies[1].Name);
181+
companiesInDatabase[1].CountryOfResidence.Should().Be(existingCompanies[1].CountryOfResidence);
182+
});
183+
}
184+
}
185+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using JetBrains.Annotations;
2+
using JsonApiDotNetCore.Configuration;
3+
using JsonApiDotNetCore.Resources;
4+
5+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions.Serialization
6+
{
7+
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
8+
public sealed class RecordCompanyDefinition : JsonApiResourceDefinition<RecordCompany, short>
9+
{
10+
public RecordCompanyDefinition(IResourceGraph resourceGraph)
11+
: base(resourceGraph)
12+
{
13+
}
14+
15+
public override void OnDeserialize(RecordCompany resource)
16+
{
17+
if (!string.IsNullOrEmpty(resource.Name))
18+
{
19+
resource.Name = resource.Name.ToUpperInvariant();
20+
}
21+
}
22+
23+
public override void OnSerialize(RecordCompany resource)
24+
{
25+
if (!string.IsNullOrEmpty(resource.CountryOfResidence))
26+
{
27+
resource.CountryOfResidence = resource.CountryOfResidence.ToUpperInvariant();
28+
}
29+
}
30+
}
31+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using TestBuildingBlocks;
1313
using Xunit;
1414

15-
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions
15+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions.SparseFieldSets
1616
{
1717
public sealed class AtomicSparseFieldSetResourceDefinitionTests
1818
: IClassFixture<ExampleIntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext>>

test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/LyricPermissionProvider.cs renamed to test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/SparseFieldSets/LyricPermissionProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions
1+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions.SparseFieldSets
22
{
33
public sealed class LyricPermissionProvider
44
{

test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/LyricTextDefinition.cs renamed to test/JsonApiDotNetCoreExampleTests/IntegrationTests/AtomicOperations/ResourceDefinitions/SparseFieldSets/LyricTextDefinition.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using JsonApiDotNetCore.Queries.Expressions;
44
using JsonApiDotNetCore.Resources;
55

6-
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.AtomicOperations.ResourceDefinitions.SparseFieldSets
77
{
88
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
99
public sealed class LyricTextDefinition : JsonApiResourceDefinition<Lyric, long>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Security.Cryptography;
3+
using System.Text;
4+
5+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceDefinitions.Serialization
6+
{
7+
public sealed class AesEncryptionService : IEncryptionService
8+
{
9+
private static readonly byte[] CryptoKey = Encoding.UTF8.GetBytes("Secret!".PadRight(32, '-'));
10+
11+
public string Encrypt(string value)
12+
{
13+
using SymmetricAlgorithm cipher = CreateCipher();
14+
15+
using ICryptoTransform transform = cipher.CreateEncryptor();
16+
byte[] plaintext = Encoding.UTF8.GetBytes(value);
17+
byte[] cipherText = transform.TransformFinalBlock(plaintext, 0, plaintext.Length);
18+
19+
byte[] buffer = new byte[cipher.IV.Length + cipherText.Length];
20+
Buffer.BlockCopy(cipher.IV, 0, buffer, 0, cipher.IV.Length);
21+
Buffer.BlockCopy(cipherText, 0, buffer, cipher.IV.Length, cipherText.Length);
22+
23+
return Convert.ToBase64String(buffer);
24+
}
25+
26+
public string Decrypt(string value)
27+
{
28+
byte[] buffer = Convert.FromBase64String(value);
29+
30+
using SymmetricAlgorithm cipher = CreateCipher();
31+
32+
byte[] initVector = new byte[cipher.IV.Length];
33+
Buffer.BlockCopy(buffer, 0, initVector, 0, initVector.Length);
34+
cipher.IV = initVector;
35+
36+
using ICryptoTransform transform = cipher.CreateDecryptor();
37+
byte[] plainBytes = transform.TransformFinalBlock(buffer, initVector.Length, buffer.Length - initVector.Length);
38+
39+
return Encoding.UTF8.GetString(plainBytes);
40+
}
41+
42+
private static SymmetricAlgorithm CreateCipher()
43+
{
44+
var cipher = Aes.Create();
45+
cipher.Key = CryptoKey;
46+
47+
return cipher;
48+
}
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceDefinitions.Serialization
2+
{
3+
public interface IEncryptionService
4+
{
5+
string Encrypt(string value);
6+
string Decrypt(string value);
7+
}
8+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Collections.Generic;
2+
using JetBrains.Annotations;
3+
using JsonApiDotNetCore.Resources;
4+
using JsonApiDotNetCore.Resources.Annotations;
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceDefinitions.Serialization
7+
{
8+
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
9+
public sealed class Scholarship : Identifiable
10+
{
11+
[Attr]
12+
public string ProgramName { get; set; }
13+
14+
[Attr]
15+
public decimal Amount { get; set; }
16+
17+
[HasMany]
18+
public IList<Student> Participants { get; set; }
19+
20+
[HasOne]
21+
public Student PrimaryContact { get; set; }
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using JsonApiDotNetCore.Controllers;
3+
using JsonApiDotNetCore.Services;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceDefinitions.Serialization
7+
{
8+
public sealed class ScholarshipsController : JsonApiController<Scholarship>
9+
{
10+
public ScholarshipsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService<Scholarship> resourceService)
11+
: base(options, loggerFactory, resourceService)
12+
{
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using JetBrains.Annotations;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
// @formatter:wrap_chained_method_calls chop_always
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceDefinitions.Serialization
7+
{
8+
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
9+
public sealed class SerializationDbContext : DbContext
10+
{
11+
public DbSet<Student> Students { get; set; }
12+
public DbSet<Scholarship> Scholarships { get; set; }
13+
14+
public SerializationDbContext(DbContextOptions<SerializationDbContext> options)
15+
: base(options)
16+
{
17+
}
18+
19+
protected override void OnModelCreating(ModelBuilder builder)
20+
{
21+
builder.Entity<Scholarship>()
22+
.HasMany(scholarship => scholarship.Participants)
23+
.WithOne(student => student.Scholarship);
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using Bogus;
3+
using Bogus.Extensions.UnitedStates;
4+
using TestBuildingBlocks;
5+
6+
// @formatter:wrap_chained_method_calls chop_always
7+
// @formatter:keep_existing_linebreaks true
8+
9+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceDefinitions.Serialization
10+
{
11+
internal sealed class SerializationFakers : FakerContainer
12+
{
13+
private readonly Lazy<Faker<Student>> _lazyStudentFaker = new Lazy<Faker<Student>>(() =>
14+
new Faker<Student>()
15+
.UseSeed(GetFakerSeed())
16+
.RuleFor(student => student.Name, faker => faker.Person.FullName)
17+
.RuleFor(student => student.SocialSecurityNumber, faker => faker.Person.Ssn()));
18+
19+
private readonly Lazy<Faker<Scholarship>> _lazyScholarshipFaker = new Lazy<Faker<Scholarship>>(() =>
20+
new Faker<Scholarship>()
21+
.UseSeed(GetFakerSeed())
22+
.RuleFor(scholarship => scholarship.ProgramName, faker => faker.Commerce.Department())
23+
.RuleFor(scholarship => scholarship.Amount, faker => faker.Finance.Amount()));
24+
25+
public Faker<Student> Student => _lazyStudentFaker.Value;
26+
public Faker<Scholarship> Scholarship => _lazyScholarshipFaker.Value;
27+
}
28+
}

0 commit comments

Comments
 (0)