Skip to content

Commit cab3dc6

Browse files
author
Bart Koelman
committed
Improved tests for includes
1 parent 1e6d18e commit cab3dc6

File tree

6 files changed

+176
-9
lines changed

6 files changed

+176
-9
lines changed

src/JsonApiDotNetCore/Queries/Expressions/IncludeElementExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public override bool Equals(object obj)
6464

6565
var other = (IncludeElementExpression)obj;
6666

67-
return Relationship.Equals(other.Relationship) == Children.SequenceEqual(other.Children);
67+
return Relationship.Equals(other.Relationship) && Children.SequenceEqual(other.Children);
6868
}
6969

7070
public override int GetHashCode()

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs

Lines changed: 147 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -447,37 +447,176 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
447447

448448
responseDocument.Data.SingleValue.Should().NotBeNull();
449449
responseDocument.Data.SingleValue.Id.Should().Be(blog.StringId);
450-
responseDocument.Data.SingleValue.Attributes["title"].Should().Be(blog.Title);
450+
responseDocument.Data.SingleValue.Relationships["posts"].Data.ManyValue[0].Type.Should().Be("blogPosts");
451+
responseDocument.Data.SingleValue.Relationships["posts"].Data.ManyValue[0].Id.Should().Be(blog.Posts[0].StringId);
451452

452453
responseDocument.Included.Should().HaveCount(7);
453454

454455
responseDocument.Included[0].Type.Should().Be("blogPosts");
455456
responseDocument.Included[0].Id.Should().Be(blog.Posts[0].StringId);
456-
responseDocument.Included[0].Attributes["caption"].Should().Be(blog.Posts[0].Caption);
457+
responseDocument.Included[0].Relationships["author"].Data.SingleValue.Type.Should().Be("webAccounts");
458+
responseDocument.Included[0].Relationships["author"].Data.SingleValue.Id.Should().Be(blog.Posts[0].Author.StringId);
459+
responseDocument.Included[0].Relationships["comments"].Data.ManyValue[0].Type.Should().Be("comments");
460+
responseDocument.Included[0].Relationships["comments"].Data.ManyValue[0].Id.Should().Be(blog.Posts[0].Comments.ElementAt(0).StringId);
457461

458462
responseDocument.Included[1].Type.Should().Be("webAccounts");
459463
responseDocument.Included[1].Id.Should().Be(blog.Posts[0].Author.StringId);
460-
responseDocument.Included[1].Attributes["userName"].Should().Be(blog.Posts[0].Author.UserName);
464+
responseDocument.Included[1].Relationships["preferences"].Data.SingleValue.Type.Should().Be("accountPreferences");
465+
responseDocument.Included[1].Relationships["preferences"].Data.SingleValue.Id.Should().Be(blog.Posts[0].Author.Preferences.StringId);
466+
responseDocument.Included[1].Relationships["posts"].Data.Value.Should().BeNull();
461467

462468
responseDocument.Included[2].Type.Should().Be("accountPreferences");
463469
responseDocument.Included[2].Id.Should().Be(blog.Posts[0].Author.Preferences.StringId);
464-
responseDocument.Included[2].Attributes["useDarkTheme"].Should().Be(blog.Posts[0].Author.Preferences.UseDarkTheme);
465470

466471
responseDocument.Included[3].Type.Should().Be("comments");
467472
responseDocument.Included[3].Id.Should().Be(blog.Posts[0].Comments.ElementAt(0).StringId);
468-
responseDocument.Included[3].Attributes["text"].Should().Be(blog.Posts[0].Comments.ElementAt(0).Text);
473+
responseDocument.Included[3].Relationships["author"].Data.SingleValue.Type.Should().Be("webAccounts");
474+
responseDocument.Included[3].Relationships["author"].Data.SingleValue.Id.Should().Be(blog.Posts[0].Comments.ElementAt(0).Author.StringId);
469475

470476
responseDocument.Included[4].Type.Should().Be("webAccounts");
471477
responseDocument.Included[4].Id.Should().Be(blog.Posts[0].Comments.ElementAt(0).Author.StringId);
472-
responseDocument.Included[4].Attributes["userName"].Should().Be(blog.Posts[0].Comments.ElementAt(0).Author.UserName);
478+
responseDocument.Included[4].Relationships["posts"].Data.ManyValue[0].Type.Should().Be("blogPosts");
479+
responseDocument.Included[4].Relationships["posts"].Data.ManyValue[0].Id.Should().Be(blog.Posts[0].Comments.ElementAt(0).Author.Posts[0].StringId);
480+
responseDocument.Included[4].Relationships["preferences"].Data.Value.Should().BeNull();
473481

474482
responseDocument.Included[5].Type.Should().Be("blogPosts");
475483
responseDocument.Included[5].Id.Should().Be(blog.Posts[0].Comments.ElementAt(0).Author.Posts[0].StringId);
476-
responseDocument.Included[5].Attributes["caption"].Should().Be(blog.Posts[0].Comments.ElementAt(0).Author.Posts[0].Caption);
484+
responseDocument.Included[5].Relationships["author"].Data.Value.Should().BeNull();
485+
responseDocument.Included[5].Relationships["comments"].Data.Value.Should().BeNull();
477486

478487
responseDocument.Included[6].Type.Should().Be("comments");
479488
responseDocument.Included[6].Id.Should().Be(blog.Posts[0].Comments.ElementAt(1).StringId);
480-
responseDocument.Included[6].Attributes["text"].Should().Be(blog.Posts[0].Comments.ElementAt(1).Text);
489+
responseDocument.Included[5].Relationships["author"].Data.Value.Should().BeNull();
490+
}
491+
492+
[Fact]
493+
public async Task Can_include_chain_of_relationships_with_reused_resources()
494+
{
495+
WebAccount author = _fakers.WebAccount.Generate();
496+
author.Preferences = _fakers.AccountPreferences.Generate();
497+
author.LoginAttempts = _fakers.LoginAttempt.Generate(1);
498+
499+
WebAccount reviewer = _fakers.WebAccount.Generate();
500+
reviewer.Preferences = _fakers.AccountPreferences.Generate();
501+
reviewer.LoginAttempts = _fakers.LoginAttempt.Generate(1);
502+
503+
BlogPost post1 = _fakers.BlogPost.Generate();
504+
post1.Author = author;
505+
post1.Reviewer = reviewer;
506+
507+
WebAccount person = _fakers.WebAccount.Generate();
508+
person.Preferences = _fakers.AccountPreferences.Generate();
509+
person.LoginAttempts = _fakers.LoginAttempt.Generate(1);
510+
511+
BlogPost post2 = _fakers.BlogPost.Generate();
512+
post2.Author = person;
513+
post2.Reviewer = person;
514+
515+
await _testContext.RunOnDatabaseAsync(async dbContext =>
516+
{
517+
await dbContext.ClearTableAsync<BlogPost>();
518+
dbContext.Posts.AddRange(post1, post2);
519+
await dbContext.SaveChangesAsync();
520+
});
521+
522+
const string route = "/blogPosts?include=reviewer.loginAttempts,author.preferences";
523+
524+
// Act
525+
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
526+
527+
// Assert
528+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
529+
530+
responseDocument.Data.ManyValue.Should().HaveCount(2);
531+
532+
responseDocument.Data.ManyValue[0].Type.Should().Be("blogPosts");
533+
responseDocument.Data.ManyValue[0].Id.Should().Be(post1.StringId);
534+
responseDocument.Data.ManyValue[0].Relationships["author"].Data.SingleValue.Type.Should().Be("webAccounts");
535+
responseDocument.Data.ManyValue[0].Relationships["author"].Data.SingleValue.Id.Should().Be(author.StringId);
536+
responseDocument.Data.ManyValue[0].Relationships["reviewer"].Data.SingleValue.Type.Should().Be("webAccounts");
537+
responseDocument.Data.ManyValue[0].Relationships["reviewer"].Data.SingleValue.Id.Should().Be(reviewer.StringId);
538+
539+
responseDocument.Data.ManyValue[1].Type.Should().Be("blogPosts");
540+
responseDocument.Data.ManyValue[1].Id.Should().Be(post2.StringId);
541+
responseDocument.Data.ManyValue[1].Relationships["author"].Data.SingleValue.Type.Should().Be("webAccounts");
542+
responseDocument.Data.ManyValue[1].Relationships["author"].Data.SingleValue.Id.Should().Be(person.StringId);
543+
responseDocument.Data.ManyValue[1].Relationships["reviewer"].Data.SingleValue.Type.Should().Be("webAccounts");
544+
responseDocument.Data.ManyValue[1].Relationships["reviewer"].Data.SingleValue.Id.Should().Be(person.StringId);
545+
546+
responseDocument.Included.Should().HaveCount(7);
547+
548+
responseDocument.Included[0].Type.Should().Be("webAccounts");
549+
responseDocument.Included[0].Id.Should().Be(author.StringId);
550+
responseDocument.Included[0].Relationships["preferences"].Data.SingleValue.Type.Should().Be("accountPreferences");
551+
responseDocument.Included[0].Relationships["preferences"].Data.SingleValue.Id.Should().Be(author.Preferences.StringId);
552+
responseDocument.Included[0].Relationships["loginAttempts"].Data.Value.Should().BeNull();
553+
554+
responseDocument.Included[1].Type.Should().Be("accountPreferences");
555+
responseDocument.Included[1].Id.Should().Be(author.Preferences.StringId);
556+
557+
responseDocument.Included[2].Type.Should().Be("webAccounts");
558+
responseDocument.Included[2].Id.Should().Be(reviewer.StringId);
559+
responseDocument.Included[2].Relationships["preferences"].Data.Value.Should().BeNull();
560+
responseDocument.Included[2].Relationships["loginAttempts"].Data.ManyValue[0].Type.Should().Be("loginAttempts");
561+
responseDocument.Included[2].Relationships["loginAttempts"].Data.ManyValue[0].Id.Should().Be(reviewer.LoginAttempts[0].StringId);
562+
563+
responseDocument.Included[3].Type.Should().Be("loginAttempts");
564+
responseDocument.Included[3].Id.Should().Be(reviewer.LoginAttempts[0].StringId);
565+
566+
responseDocument.Included[4].Type.Should().Be("webAccounts");
567+
responseDocument.Included[4].Id.Should().Be(person.StringId);
568+
responseDocument.Included[4].Relationships["preferences"].Data.SingleValue.Type.Should().Be("accountPreferences");
569+
responseDocument.Included[4].Relationships["preferences"].Data.SingleValue.Id.Should().Be(person.Preferences.StringId);
570+
responseDocument.Included[4].Relationships["loginAttempts"].Data.ManyValue[0].Type.Should().Be("loginAttempts");
571+
responseDocument.Included[4].Relationships["loginAttempts"].Data.ManyValue[0].Id.Should().Be(person.LoginAttempts[0].StringId);
572+
573+
responseDocument.Included[5].Type.Should().Be("accountPreferences");
574+
responseDocument.Included[5].Id.Should().Be(person.Preferences.StringId);
575+
576+
responseDocument.Included[6].Type.Should().Be("loginAttempts");
577+
responseDocument.Included[6].Id.Should().Be(person.LoginAttempts[0].StringId);
578+
}
579+
580+
[Fact]
581+
public async Task Can_include_chain_with_cyclic_dependency()
582+
{
583+
List<BlogPost> posts = _fakers.BlogPost.Generate(1);
584+
585+
Blog blog = _fakers.Blog.Generate();
586+
blog.Posts = posts;
587+
blog.Posts[0].Author = _fakers.WebAccount.Generate();
588+
blog.Posts[0].Author.Posts = posts;
589+
590+
await _testContext.RunOnDatabaseAsync(async dbContext =>
591+
{
592+
dbContext.Blogs.Add(blog);
593+
await dbContext.SaveChangesAsync();
594+
});
595+
596+
string route = $"/blogs/{blog.StringId}?include=posts.author.posts.author.posts.author";
597+
598+
// Act
599+
(HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);
600+
601+
// Assert
602+
httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
603+
604+
responseDocument.Data.SingleValue.Type.Should().Be("blogs");
605+
responseDocument.Data.SingleValue.Id.Should().Be(blog.StringId);
606+
responseDocument.Data.SingleValue.Relationships["posts"].Data.ManyValue[0].Type.Should().Be("blogPosts");
607+
responseDocument.Data.SingleValue.Relationships["posts"].Data.ManyValue[0].Id.Should().Be(blog.Posts[0].StringId);
608+
609+
responseDocument.Included.Should().HaveCount(2);
610+
611+
responseDocument.Included[0].Type.Should().Be("blogPosts");
612+
responseDocument.Included[0].Id.Should().Be(blog.Posts[0].StringId);
613+
responseDocument.Included[0].Relationships["author"].Data.SingleValue.Type.Should().Be("webAccounts");
614+
responseDocument.Included[0].Relationships["author"].Data.SingleValue.Id.Should().Be(blog.Posts[0].Author.StringId);
615+
616+
responseDocument.Included[1].Type.Should().Be("webAccounts");
617+
responseDocument.Included[1].Id.Should().Be(blog.Posts[0].Author.StringId);
618+
responseDocument.Included[1].Relationships["posts"].Data.ManyValue[0].Type.Should().Be("blogPosts");
619+
responseDocument.Included[1].Relationships["posts"].Data.ManyValue[0].Id.Should().Be(blog.Posts[0].StringId);
481620
}
482621

483622
[Fact]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using JetBrains.Annotations;
3+
using JsonApiDotNetCore.Resources;
4+
using JsonApiDotNetCore.Resources.Annotations;
5+
6+
namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings
7+
{
8+
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
9+
public sealed class LoginAttempt : Identifiable
10+
{
11+
[Attr]
12+
public DateTimeOffset TriedAt { get; set; }
13+
14+
[Attr]
15+
public bool IsSucceeded { get; set; }
16+
}
17+
}

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public sealed class QueryStringDbContext : DbContext
1414
public DbSet<Comment> Comments { get; set; }
1515
public DbSet<WebAccount> Accounts { get; set; }
1616
public DbSet<AccountPreferences> AccountPreferences { get; set; }
17+
public DbSet<LoginAttempt> LoginAttempts { get; set; }
1718
public DbSet<Calendar> Calendars { get; set; }
1819
public DbSet<Appointment> Appointments { get; set; }
1920

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringFakers.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ internal sealed class QueryStringFakers : FakerContainer
4343
.RuleFor(webAccount => webAccount.DateOfBirth, faker => faker.Person.DateOfBirth)
4444
.RuleFor(webAccount => webAccount.EmailAddress, faker => faker.Internet.Email()));
4545

46+
private readonly Lazy<Faker<LoginAttempt>> _lazyLoginAttemptFaker = new(() =>
47+
new Faker<LoginAttempt>()
48+
.UseSeed(GetFakerSeed())
49+
.RuleFor(loginAttempt => loginAttempt.TriedAt, faker => faker.Date.PastOffset())
50+
.RuleFor(loginAttempt => loginAttempt.IsSucceeded, faker => faker.Random.Bool()));
51+
4652
private readonly Lazy<Faker<AccountPreferences>> _lazyAccountPreferencesFaker = new(() =>
4753
new Faker<AccountPreferences>()
4854
.UseSeed(GetFakerSeed())
@@ -67,6 +73,7 @@ internal sealed class QueryStringFakers : FakerContainer
6773
public Faker<Label> Label => _lazyLabelFaker.Value;
6874
public Faker<Comment> Comment => _lazyCommentFaker.Value;
6975
public Faker<WebAccount> WebAccount => _lazyWebAccountFaker.Value;
76+
public Faker<LoginAttempt> LoginAttempt => _lazyLoginAttemptFaker.Value;
7077
public Faker<AccountPreferences> AccountPreferences => _lazyAccountPreferencesFaker.Value;
7178
public Faker<Calendar> Calendar => _lazyCalendarFaker.Value;
7279
public Faker<Appointment> Appointment => _lazyAppointmentFaker.Value;

test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/WebAccount.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@ public sealed class WebAccount : Identifiable
2929

3030
[HasOne]
3131
public AccountPreferences Preferences { get; set; }
32+
33+
[HasMany]
34+
public IList<LoginAttempt> LoginAttempts { get; set; }
3235
}
3336
}

0 commit comments

Comments
 (0)