public async Task Can_create_OneToOne_relationship_from_principal_side()
        {
            // Arrange
            var existingTrack = _fakers.MusicTrack.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.MusicTracks.Add(existingTrack);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type          = "lyrics",
                            relationships = new
                            {
                                track = new
                                {
                                    data = new
                                    {
                                        type = "musicTracks",
                                        id   = existingTrack.StringId
                                    }
                                }
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync <AtomicOperationsDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Results.Should().HaveCount(1);
            responseDocument.Results[0].SingleData.Should().NotBeNull();
            responseDocument.Results[0].SingleData.Type.Should().Be("lyrics");
            responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty();
            responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty();

            var newLyricId = long.Parse(responseDocument.Results[0].SingleData.Id);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var lyricInDatabase = await dbContext.Lyrics
                                      .Include(lyric => lyric.Track)
                                      .FirstWithIdAsync(newLyricId);

                lyricInDatabase.Track.Should().NotBeNull();
                lyricInDatabase.Track.Id.Should().Be(existingTrack.Id);
            });
        }
        public async Task Cannot_add_to_HasOne_relationship()
        {
            // Arrange
            MusicTrack    existingTrack   = _fakers.MusicTrack.Generate();
            RecordCompany existingCompany = _fakers.RecordCompany.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.AddRange(existingTrack, existingCompany);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        @ref = new
                        {
                            type         = "musicTracks",
                            id           = existingTrack.StringId,
                            relationship = "ownedBy"
                        },
                        data = new
                        {
                            type = "recordCompanies",
                            id   = existingCompany.StringId
                        }
                    }
                }
            };

            const string route = "/operations";

            // Act
            (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync <ErrorDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity);

            responseDocument.Errors.Should().HaveCount(1);

            Error error = responseDocument.Errors[0];

            error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
            error.Title.Should().Be("Failed to deserialize request body: Only to-many relationships can be targeted in 'add' operations.");
            error.Detail.Should().Be("Relationship 'ownedBy' must be a to-many relationship.");
        }
        public async Task Can_select_fields_in_primary_resources()
        {
            // Arrange
            var store = _testContext.Factory.Services.GetRequiredService <ResourceCaptureStore>();

            store.Clear();

            var post = _fakers.BlogPost.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <BlogPost>();
                dbContext.Posts.Add(post);
                await dbContext.SaveChangesAsync();
            });

            const string route = "/blogPosts?fields[blogPosts]=caption,author";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(1);
            responseDocument.ManyData[0].Id.Should().Be(post.StringId);
            responseDocument.ManyData[0].Attributes.Should().HaveCount(1);
            responseDocument.ManyData[0].Attributes["caption"].Should().Be(post.Caption);
            responseDocument.ManyData[0].Relationships.Should().HaveCount(1);
            responseDocument.ManyData[0].Relationships["author"].Data.Should().BeNull();
            responseDocument.ManyData[0].Relationships["author"].Links.Self.Should().NotBeNull();
            responseDocument.ManyData[0].Relationships["author"].Links.Related.Should().NotBeNull();

            var postCaptured = (BlogPost)store.Resources.Should().ContainSingle(x => x is BlogPost).And.Subject.Single();

            postCaptured.Caption.Should().Be(post.Caption);
            postCaptured.Url.Should().BeNull();
        }
        public async Task Can_clear_OneToOne_relationship_from_principal_side()
        {
            // Arrange
            var existingLyric = _fakers.Lyric.Generate();

            existingLyric.Track = _fakers.MusicTrack.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <MusicTrack>();
                dbContext.Lyrics.Add(existingLyric);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "update",
                        @ref = new
                        {
                            type         = "lyrics",
                            id           = existingLyric.StringId,
                            relationship = "track"
                        },
                        data = (object)null
                    }
                }
            };

            var route = "/operations";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync <string>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var lyricInDatabase = await dbContext.Lyrics
                                      .Include(lyric => lyric.Track)
                                      .FirstAsync(lyric => lyric.Id == existingLyric.Id);

                lyricInDatabase.Track.Should().BeNull();

                var tracksInDatabase = await dbContext.MusicTracks.ToListAsync();
                tracksInDatabase.Should().HaveCount(1);
            });
        }
Пример #5
0
        public async Task Can_include_in_primary_resources()
        {
            // Arrange
            var post = _fakers.BlogPost.Generate();
            post.Author = _fakers.WebAccount.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync<BlogPost>();
                dbContext.Posts.Add(post);

                await dbContext.SaveChangesAsync();
            });

            var route = "blogPosts?include=author";

            // Act
            var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync<Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(1);
            responseDocument.ManyData[0].Id.Should().Be(post.StringId);
            responseDocument.ManyData[0].Attributes["caption"].Should().Be(post.Caption);

            responseDocument.Included.Should().HaveCount(1);
            responseDocument.Included[0].Type.Should().Be("webAccounts");
            responseDocument.Included[0].Id.Should().Be(post.Author.StringId);
            responseDocument.Included[0].Attributes["displayName"].Should().Be(post.Author.DisplayName);
        }
        public async Task Can_clear_HasMany_relationship()
        {
            // Arrange
            var existingTrack = _fakers.MusicTrack.Generate();

            existingTrack.Performers = _fakers.Performer.Generate(2);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <Performer>();
                dbContext.MusicTracks.Add(existingTrack);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "update",
                        @ref = new
                        {
                            type         = "musicTracks",
                            id           = existingTrack.StringId,
                            relationship = "performers"
                        },
                        data = new object[0]
                    }
                }
            };

            const string route = "/operations";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync <string>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var trackInDatabase = await dbContext.MusicTracks
                                      .Include(musicTrack => musicTrack.Performers)
                                      .FirstWithIdAsync(existingTrack.Id);

                trackInDatabase.Performers.Should().BeEmpty();

                var performersInDatabase = await dbContext.Performers.ToListAsync();
                performersInDatabase.Should().HaveCount(2);
            });
        }
        public async Task Can_paginate_in_primary_resources()
        {
            // Arrange
            List <BlogPost> posts = _fakers.BlogPost.Generate(2);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <BlogPost>();
                dbContext.Posts.AddRange(posts);
                await dbContext.SaveChangesAsync();
            });

            const string route = "/blogPosts?page[number]=2&page[size]=1";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(1);
            responseDocument.ManyData[0].Id.Should().Be(posts[1].StringId);

            responseDocument.Links.Should().NotBeNull();
            responseDocument.Links.Self.Should().Be(HostPrefix + route);
            responseDocument.Links.First.Should().Be(HostPrefix + "/blogPosts?page[size]=1");
            responseDocument.Links.Last.Should().Be(responseDocument.Links.Self);
            responseDocument.Links.Prev.Should().Be(responseDocument.Links.First);
            responseDocument.Links.Next.Should().BeNull();
        }
Пример #8
0
        public async Task Can_create_HasMany_relationship()
        {
            // Arrange
            var existingUserAccounts = _fakers.UserAccount.Generate(2);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.UserAccounts.AddRange(existingUserAccounts);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type          = "workItems",
                    relationships = new
                    {
                        subscribers = new
                        {
                            data = new[]
                            {
                                new
                                {
                                    type = "userAccounts",
                                    id   = existingUserAccounts[0].StringId
                                },
                                new
                                {
                                    type = "userAccounts",
                                    id   = existingUserAccounts[1].StringId
                                }
                            }
                        }
                    }
                }
            };

            var route = "/workItems";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecutePostAsync <Document>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.Created);

            responseDocument.SingleData.Should().NotBeNull();
            responseDocument.SingleData.Attributes.Should().NotBeEmpty();
            responseDocument.SingleData.Relationships.Should().NotBeEmpty();
            responseDocument.Included.Should().BeNull();

            var newWorkItemId = int.Parse(responseDocument.SingleData.Id);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var workItemInDatabase = await dbContext.WorkItems
                                         .Include(workItem => workItem.Subscribers)
                                         .FirstAsync(workItem => workItem.Id == newWorkItemId);

                workItemInDatabase.Subscribers.Should().HaveCount(2);
                workItemInDatabase.Subscribers.Should().ContainSingle(subscriber => subscriber.Id == existingUserAccounts[0].Id);
                workItemInDatabase.Subscribers.Should().ContainSingle(subscriber => subscriber.Id == existingUserAccounts[1].Id);
            });
        }
Пример #9
0
        public async Task Can_sort_in_primary_resources()
        {
            // Arrange
            var posts = _fakers.BlogPost.Generate(3);

            posts[0].Caption = "B";
            posts[1].Caption = "A";
            posts[2].Caption = "C";

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <BlogPost>();
                dbContext.Posts.AddRange(posts);
                await dbContext.SaveChangesAsync();
            });

            const string route = "/blogPosts?sort=caption";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(3);
            responseDocument.ManyData[0].Id.Should().Be(posts[1].StringId);
            responseDocument.ManyData[1].Id.Should().Be(posts[0].StringId);
            responseDocument.ManyData[2].Id.Should().Be(posts[2].StringId);
        }
        public async Task Get_primary_resources_excludes_soft_deleted()
        {
            // Arrange
            List <Department> departments = _fakers.Department.Generate(2);

            departments[0].SoftDeletedAt = SoftDeletionTime;

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <Department>();
                dbContext.Departments.AddRange(departments);
                await dbContext.SaveChangesAsync();
            });

            const string route = "/departments";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(1);
            responseDocument.ManyData[0].Id.Should().Be(departments[1].StringId);
        }
        public async Task Hides_text_in_update_resource_with_side_effects()
        {
            // Arrange
            var provider = _testContext.Factory.Services.GetRequiredService <LyricPermissionProvider>();

            provider.CanViewText = false;
            provider.HitCount    = 0;

            List <Lyric> existingLyrics = _fakers.Lyric.Generate(2);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Lyrics.AddRange(existingLyrics);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "update",
                        data = new
                        {
                            type       = "lyrics",
                            id         = existingLyrics[0].StringId,
                            attributes = new
                            {
                            }
                        }
                    },
                    new
                    {
                        op   = "update",
                        data = new
                        {
                            type       = "lyrics",
                            id         = existingLyrics[1].StringId,
                            attributes = new
                            {
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

            // Act
            (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) =
                await _testContext.ExecutePostAtomicAsync <AtomicOperationsDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Results.Should().HaveCount(2);

            responseDocument.Results[0].SingleData.Attributes["format"].Should().Be(existingLyrics[0].Format);
            responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("text");

            responseDocument.Results[1].SingleData.Attributes["format"].Should().Be(existingLyrics[1].Format);
            responseDocument.Results[1].SingleData.Attributes.Should().NotContainKey("text");

            provider.HitCount.Should().Be(4);
        }
Пример #12
0
        public async Task Can_get_resource_by_ID()
        {
            // Arrange
            var clock = (FrozenSystemClock)_testContext.Factory.Services.GetRequiredService <ISystemClock>();

            clock.UtcNow = 27.January(2021);

            var certificate = _fakers.GiftCertificate.Generate();

            certificate.IssueDate = 28.January(2020);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.GiftCertificates.Add(certificate);
                await dbContext.SaveChangesAsync();
            });

            var route = "/giftCertificates/" + certificate.StringId;

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.SingleData.Should().NotBeNull();
            responseDocument.SingleData.Id.Should().Be(certificate.StringId);
            responseDocument.SingleData.Attributes["issueDate"].Should().BeCloseTo(certificate.IssueDate);
            responseDocument.SingleData.Attributes["hasExpired"].Should().Be(false);
        }
Пример #13
0
        public async Task Deleting_principal_side_of_required_OneToMany_relationship_triggers_cascading_delete()
        {
            // Arrange
            Order existingOrder = _fakers.Orders.Generate();

            existingOrder.Customer = _fakers.Customers.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Orders.Add(existingOrder);
                await dbContext.SaveChangesAsync();
            });

            string route = $"/customers/{existingOrder.Customer.Id}";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteDeleteAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeNull();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                Customer existingCustomerInDatabase = await dbContext.Customers.FirstWithIdOrDefaultAsync(existingOrder.Customer.Id);
                existingCustomerInDatabase.Should().BeNull();

                Order existingOrderInDatabase = await dbContext.Orders.FirstWithIdOrDefaultAsync(existingOrder.Id);
                existingOrderInDatabase.Should().BeNull();
            });
        }
        public async Task Can_filter_in_primary_resources()
        {
            // Arrange
            List <BlogPost> posts = _fakers.BlogPost.Generate(2);

            posts[0].Caption = "One";
            posts[1].Caption = "Two";

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <BlogPost>();
                dbContext.Posts.AddRange(posts);
                await dbContext.SaveChangesAsync();
            });

            const string route = "/blogPosts?filter=equals(caption,'Two')";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(1);
            responseDocument.ManyData[0].Id.Should().Be(posts[1].StringId);
        }
Пример #15
0
        public async Task Can_clear_HasMany_relationship()
        {
            // Arrange
            WorkItem existingWorkItem = _fakers.WorkItem.Generate();

            existingWorkItem.Subscribers = _fakers.UserAccount.Generate(2).ToHashSet();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.WorkItems.Add(existingWorkItem);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new object[0]
            };

            string route = $"/workItems/{existingWorkItem.StringId}/relationships/subscribers";

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync <string>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                WorkItem workItemInDatabase = await dbContext.WorkItems.Include(workItem => workItem.Subscribers).FirstWithIdAsync(existingWorkItem.Id);

                workItemInDatabase.Subscribers.Should().BeEmpty();
            });
        }
        public async Task Cannot_remove_from_HasOne_relationship()
        {
            // Arrange
            var existingWorkItem = _fakers.WorkItem.Generate();

            existingWorkItem.Assignee = _fakers.UserAccount.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.WorkItems.Add(existingWorkItem);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type = "userAccounts",
                    id   = existingWorkItem.Assignee.StringId
                }
            };

            var route = $"/workItems/{existingWorkItem.StringId}/relationships/assignee";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecuteDeleteAsync <ErrorDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.Forbidden);

            responseDocument.Errors.Should().HaveCount(1);

            var error = responseDocument.Errors[0];

            error.StatusCode.Should().Be(HttpStatusCode.Forbidden);
            error.Title.Should().Be("Only to-many relationships can be updated through this endpoint.");
            error.Detail.Should().Be("Relationship 'assignee' must be a to-many relationship.");
        }
        public async Task Includes_version_with_ext_on_operations_endpoint()
        {
            // Arrange
            const int newArtistId   = 12345;
            string    newArtistName = _fakers.Performer.Generate().ArtistName;

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <Performer>();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "performers",
                            id         = newArtistId,
                            attributes = new
                            {
                                artistName = newArtistName
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync <string>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Should().BeJson(@"{
  ""jsonapi"": {
    ""version"": ""1.1"",
    ""ext"": [
      ""https://jsonapi.org/ext/atomic""
    ]
  },
  ""atomic:results"": [
    {
      ""data"": {
        ""type"": ""performers"",
        ""id"": """ + newArtistId + @""",
        ""attributes"": {
          ""artistName"": """ + newArtistName + @""",
          ""bornAt"": ""0001-01-01T01:00:00+01:00""
        },
        ""links"": {
          ""self"": ""http://localhost/performers/" + newArtistId + @"""
        }
      }
    }
  ]
}");
        }
        public async Task Can_clear_ManyToOne_relationship()
        {
            // Arrange
            var existingWorkItem = _fakers.WorkItem.Generate();

            existingWorkItem.Assignee = _fakers.UserAccount.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.WorkItems.Add(existingWorkItem);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type          = "workItems",
                    id            = existingWorkItem.StringId,
                    relationships = new
                    {
                        assignee = new
                        {
                            data = (object)null
                        }
                    }
                }
            };

            var route = "/workItems/" + existingWorkItem.StringId;

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecutePatchAsync <Document>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.SingleData.Should().NotBeNull();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var workItemInDatabase = await dbContext.WorkItems
                                         .Include(workItem => workItem.Assignee)
                                         .FirstWithIdAsync(existingWorkItem.Id);

                workItemInDatabase.Assignee.Should().BeNull();
            });
        }
        public async Task Can_create_resource_with_inherited_attributes()
        {
            // Arrange
            var newMan = new Man
            {
                FamilyName = "Smith",
                IsRetired  = true,
                HasBeard   = true
            };

            var requestBody = new
            {
                data = new
                {
                    type       = "men",
                    attributes = new
                    {
                        familyName = newMan.FamilyName,
                        isRetired  = newMan.IsRetired,
                        hasBeard   = newMan.HasBeard
                    }
                }
            };

            const string route = "/men";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync <Document>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.Created);

            responseDocument.SingleData.Should().NotBeNull();
            responseDocument.SingleData.Type.Should().Be("men");
            responseDocument.SingleData.Attributes["familyName"].Should().Be(newMan.FamilyName);
            responseDocument.SingleData.Attributes["isRetired"].Should().Be(newMan.IsRetired);
            responseDocument.SingleData.Attributes["hasBeard"].Should().Be(newMan.HasBeard);

            int newManId = int.Parse(responseDocument.SingleData.Id);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                Man manInDatabase = await dbContext.Men.FirstWithIdAsync(newManId);

                manInDatabase.FamilyName.Should().Be(newMan.FamilyName);
                manInDatabase.IsRetired.Should().Be(newMan.IsRetired);
                manInDatabase.HasBeard.Should().Be(newMan.HasBeard);
            });
        }
        public async Task Can_create_resource_with_annotated_relationships()
        {
            // Arrange
            var parentDirectory = new SystemDirectory
            {
                Name            = "Shared",
                IsCaseSensitive = true
            };

            var subdirectory = new SystemDirectory
            {
                Name            = "Open Source",
                IsCaseSensitive = true
            };

            var file = new SystemFile
            {
                FileName    = "Main.cs",
                SizeInBytes = 100
            };

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Directories.AddRange(parentDirectory, subdirectory);
                dbContext.Files.Add(file);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type       = "systemDirectories",
                    attributes = new
                    {
                        name            = "Projects",
                        isCaseSensitive = true
                    },
                    relationships = new
                    {
                        subdirectories = new
                        {
                            data = new[]
                            {
                                new
                                {
                                    type = "systemDirectories",
                                    id   = subdirectory.StringId
                                }
                            }
                        },
                        files = new
                        {
                            data = new[]
                            {
                                new
                                {
                                    type = "systemFiles",
                                    id   = file.StringId
                                }
                            }
                        },
                        parent = new
                        {
                            data = new
                            {
                                type = "systemDirectories",
                                id   = parentDirectory.StringId
                            }
                        }
                    }
                }
            };

            const string route = "/systemDirectories";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync <Document>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.Created);

            responseDocument.SingleData.Should().NotBeNull();
            responseDocument.SingleData.Attributes["name"].Should().Be("Projects");
            responseDocument.SingleData.Attributes["isCaseSensitive"].Should().Be(true);
        }
        public async Task Can_create_resource_with_annotated_relationship()
        {
            // Arrange
            var existingTrack   = _fakers.MusicTrack.Generate();
            var newPlaylistName = _fakers.Playlist.Generate().Name;

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.MusicTracks.Add(existingTrack);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "playlists",
                            attributes = new
                            {
                                name = newPlaylistName
                            },
                            relationships = new
                            {
                                tracks = new
                                {
                                    data = new[]
                                    {
                                        new
                                        {
                                            type = "musicTracks",
                                            id   = existingTrack.StringId
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            };

            var route = "/operations";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync <AtomicOperationsDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Results.Should().HaveCount(1);

            var newPlaylistId = long.Parse(responseDocument.Results[0].SingleData.Id);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var playlistInDatabase = await dbContext.Playlists
                                         .Include(playlist => playlist.PlaylistMusicTracks)
                                         .ThenInclude(playlistMusicTrack => playlistMusicTrack.MusicTrack)
                                         .FirstAsync(playlist => playlist.Id == newPlaylistId);

                playlistInDatabase.PlaylistMusicTracks.Should().HaveCount(1);
                playlistInDatabase.PlaylistMusicTracks[0].MusicTrack.Id.Should().Be(existingTrack.Id);
            });
        }
        public async Task Can_get_primary_resources_with_include()
        {
            // Arrange
            var meetings = _fakers.Meeting.Generate(1);

            meetings[0].Attendees = _fakers.MeetingAttendee.Generate(1);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <Meeting>();
                dbContext.Meetings.AddRange(meetings);
                await dbContext.SaveChangesAsync();
            });

            var route = "/meetings?include=attendees";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecuteGetAsync <string>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Should().BeJson(@"{
  ""links"": {
    ""self"": ""http://localhost/meetings?include=attendees"",
    ""first"": ""http://localhost/meetings?include=attendees""
  },
  ""data"": [
    {
      ""type"": ""meetings"",
      ""id"": """ + meetings[0].StringId + @""",
      ""attributes"": {
        ""title"": """ + meetings[0].Title + @""",
        ""startTime"": """ + meetings[0].StartTime.ToString("O") + @""",
        ""duration"": """ + meetings[0].Duration + @""",
        ""location"": {
          ""lat"": " + meetings[0].Location.Latitude.ToString(CultureInfo.InvariantCulture) + @",
          ""lng"": " + meetings[0].Location.Longitude.ToString(CultureInfo.InvariantCulture) + @"
        }
      },
      ""relationships"": {
        ""attendees"": {
          ""links"": {
            ""self"": ""http://localhost/meetings/" + meetings[0].StringId + @"/relationships/attendees"",
            ""related"": ""http://localhost/meetings/" + meetings[0].StringId + @"/attendees""
          },
          ""data"": [
            {
              ""type"": ""meetingAttendees"",
              ""id"": """ + meetings[0].Attendees[0].StringId + @"""
            }
          ]
        }
      },
      ""links"": {
        ""self"": ""http://localhost/meetings/" + meetings[0].StringId + @"""
      }
    }
  ],
  ""included"": [
    {
      ""type"": ""meetingAttendees"",
      ""id"": """ + meetings[0].Attendees[0].StringId + @""",
      ""attributes"": {
        ""displayName"": """ + meetings[0].Attendees[0].DisplayName + @"""
      },
      ""relationships"": {
        ""meeting"": {
          ""links"": {
            ""self"": ""http://localhost/meetingAttendees/" + meetings[0].Attendees[0].StringId + @"/relationships/meeting"",
            ""related"": ""http://localhost/meetingAttendees/" + meetings[0].Attendees[0].StringId + @"/meeting""
          }
        }
      },
      ""links"": {
        ""self"": ""http://localhost/meetingAttendees/" + meetings[0].Attendees[0].StringId + @"""
      }
    }
  ]
}");
        }
Пример #23
0
        public async Task Can_update_resource_without_attributes_or_relationships()
        {
            // Arrange
            UserAccount existingUserAccount = _fakers.UserAccount.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.UserAccounts.Add(existingUserAccount);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                data = new
                {
                    type       = "userAccounts",
                    id         = existingUserAccount.StringId,
                    attributes = new
                    {
                    },
                    relationships = new
                    {
                    }
                }
            };

            string route = "/userAccounts/" + existingUserAccount.StringId;

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePatchAsync <string>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent);

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                UserAccount userAccountInDatabase = await dbContext.UserAccounts.FirstWithIdAsync(existingUserAccount.Id);

                userAccountInDatabase.FirstName.Should().Be(existingUserAccount.FirstName);
                userAccountInDatabase.LastName.Should().Be(existingUserAccount.LastName);
            });
        }
Пример #24
0
        public async Task Returns_no_body_for_successful_HEAD_request()
        {
            // Arrange
            Meeting meeting = _fakers.Meeting.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Meetings.Add(meeting);
                await dbContext.SaveChangesAsync();
            });

            string route = "/meetings/" + meeting.StringId;

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecuteHeadAsync <string>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Should().BeEmpty();
        }
Пример #25
0
        public async Task Can_rollback_on_error()
        {
            // Arrange
            var newArtistName = _fakers.Performer.Generate().ArtistName;
            var newBornAt     = _fakers.Performer.Generate().BornAt;
            var newTitle      = _fakers.MusicTrack.Generate().Title;

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTablesAsync <Performer, MusicTrack>();
            });

            var requestBody = new
            {
                atomic__operations = new object[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "performers",
                            attributes = new
                            {
                                artistName = newArtistName,
                                bornAt     = newBornAt
                            }
                        }
                    },
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "musicTracks",
                            attributes = new
                            {
                                title = newTitle
                            },
                            relationships = new
                            {
                                performers = new
                                {
                                    data = new[]
                                    {
                                        new
                                        {
                                            type = "performers",
                                            id   = 99999999
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            };

            var route = "/operations";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecutePostAtomicAsync <ErrorDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound);

            responseDocument.Errors.Should().HaveCount(1);
            responseDocument.Errors[0].StatusCode.Should().Be(HttpStatusCode.NotFound);
            responseDocument.Errors[0].Title.Should().Be("A related resource does not exist.");
            responseDocument.Errors[0].Detail.Should().Be("Related resource of type 'performers' with ID '99999999' in relationship 'performers' does not exist.");
            responseDocument.Errors[0].Source.Pointer.Should().Be("/atomic:operations[1]");

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var performersInDatabase = await dbContext.Performers.ToListAsync();
                performersInDatabase.Should().BeEmpty();

                var tracksInDatabase = await dbContext.MusicTracks.ToListAsync();
                tracksInDatabase.Should().BeEmpty();
            });
        }
Пример #26
0
        public async Task Can_filter_on_ID_in_primary_resources()
        {
            // Arrange
            var car = new Car
            {
                RegionId     = 123,
                LicensePlate = "AA-BB-11"
            };

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <Car>();
                dbContext.Cars.Add(car);
                await dbContext.SaveChangesAsync();
            });

            const string route = "/cars?filter=any(id,'123:AA-BB-11','999:XX-YY-22')";

            // Act
            var(httpResponse, responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(1);
            responseDocument.ManyData[0].Id.Should().Be(car.StringId);
        }
Пример #27
0
        public async Task Can_create_user_with_password()
        {
            // Arrange
            User newUser = _fakers.User.Generate();

            IRequestSerializer serializer = GetRequestSerializer <User>(user => new
            {
                user.Password,
                user.UserName
            });

            string requestBody = serializer.Serialize(newUser);

            const string route = "/api/v1/users";

            // Act
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAsync <string>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.Created);

            User responseUser = GetResponseDeserializer().DeserializeSingle <User>(responseDocument).Data;
            var  document     = JsonConvert.DeserializeObject <Document>(responseDocument);

            document.SingleData.Attributes.Should().NotContainKey("password");
            document.SingleData.Attributes["userName"].Should().Be(newUser.UserName);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                User userInDatabase = await dbContext.Users.FirstWithIdAsync(responseUser.Id);

                userInDatabase.UserName.Should().Be(newUser.UserName);
                userInDatabase.Password.Should().Be(newUser.Password);
            });
        }
        public async Task Transforms_on_create_resource_with_side_effects()
        {
            // Arrange
            var hitCounter = _testContext.Factory.Services.GetRequiredService <ResourceDefinitionHitCounter>();

            List <RecordCompany> newCompanies = _fakers.RecordCompany.Generate(2);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <RecordCompany>();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "recordCompanies",
                            attributes = new
                            {
                                name = newCompanies[0].Name,
                                countryOfResidence = newCompanies[0].CountryOfResidence
                            }
                        }
                    },
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "recordCompanies",
                            attributes = new
                            {
                                name = newCompanies[1].Name,
                                countryOfResidence = newCompanies[1].CountryOfResidence
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

            // Act
            (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) =
                await _testContext.ExecutePostAtomicAsync <AtomicOperationsDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Results.Should().HaveCount(2);

            responseDocument.Results[0].SingleData.Attributes["name"].Should().Be(newCompanies[0].Name.ToUpperInvariant());
            responseDocument.Results[0].SingleData.Attributes["countryOfResidence"].Should().Be(newCompanies[0].CountryOfResidence.ToUpperInvariant());

            responseDocument.Results[1].SingleData.Attributes["name"].Should().Be(newCompanies[1].Name.ToUpperInvariant());
            responseDocument.Results[1].SingleData.Attributes["countryOfResidence"].Should().Be(newCompanies[1].CountryOfResidence.ToUpperInvariant());

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                List <RecordCompany> companiesInDatabase = await dbContext.RecordCompanies.ToListAsync();
                companiesInDatabase.Should().HaveCount(2);

                companiesInDatabase[0].Name.Should().Be(newCompanies[0].Name.ToUpperInvariant());
                companiesInDatabase[0].CountryOfResidence.Should().Be(newCompanies[0].CountryOfResidence);

                companiesInDatabase[1].Name.Should().Be(newCompanies[1].Name.ToUpperInvariant());
                companiesInDatabase[1].CountryOfResidence.Should().Be(newCompanies[1].CountryOfResidence);
            });

            hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[]
        public async Task Can_create_HasMany_relationship()
        {
            // Arrange
            List <Performer> existingPerformers = _fakers.Performer.Generate(2);
            string           newTitle           = _fakers.MusicTrack.Generate().Title;

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Performers.AddRange(existingPerformers);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "musicTracks",
                            attributes = new
                            {
                                title = newTitle
                            },
                            relationships = new
                            {
                                performers = new
                                {
                                    data = new[]
                                    {
                                        new
                                        {
                                            type = "performers",
                                            id   = existingPerformers[0].StringId
                                        },
                                        new
                                        {
                                            type = "performers",
                                            id   = existingPerformers[1].StringId
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

            // Act
            (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) =
                await _testContext.ExecutePostAtomicAsync <AtomicOperationsDocument>(route, requestBody);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.Results.Should().HaveCount(1);
            responseDocument.Results[0].SingleData.Should().NotBeNull();
            responseDocument.Results[0].SingleData.Type.Should().Be("musicTracks");
            responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty();
            responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty();

            Guid newTrackId = Guid.Parse(responseDocument.Results[0].SingleData.Id);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                MusicTrack trackInDatabase = await dbContext.MusicTracks.Include(musicTrack => musicTrack.Performers).FirstWithIdAsync(newTrackId);

                trackInDatabase.Performers.Should().HaveCount(2);
                trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingPerformers[0].Id);
                trackInDatabase.Performers.Should().ContainSingle(performer => performer.Id == existingPerformers[1].Id);
            });
        }
Пример #30
0
        public async Task Can_filter_equality_in_primary_resources()
        {
            // Arrange
            List <BankAccount> accounts = _fakers.BankAccount.Generate(2);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                await dbContext.ClearTableAsync <BankAccount>();
                dbContext.BankAccounts.AddRange(accounts);
                await dbContext.SaveChangesAsync();
            });

            string route = $"/bankAccounts?filter=equals(id,'{accounts[1].StringId}')";

            // Act
            (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync <Document>(route);

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);

            responseDocument.ManyData.Should().HaveCount(1);
            responseDocument.ManyData[0].Id.Should().Be(accounts[1].StringId);
        }