public async Task Update_resource_with_side_effects_and_missing_resource_controller_hides_links()
        {
            // Arrange
            Playlist existingPlaylist = _fakers.Playlist.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Playlists.Add(existingPlaylist);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new object[]
                {
                    new
                    {
                        op   = "update",
                        data = new
                        {
                            type       = "playlists",
                            id         = existingPlaylist.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(1);

            ResourceObject singleData = responseDocument.Results[0].SingleData;

            singleData.Should().NotBeNull();
            singleData.Links.Should().BeNull();
            singleData.Relationships.Should().BeNull();
        }
        public async Task Update_resource_with_side_effects_returns_absolute_links()
        {
            // Arrange
            TextLanguage  existingLanguage = _fakers.TextLanguage.Generate();
            RecordCompany existingCompany  = _fakers.RecordCompany.Generate();

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

            var requestBody = new
            {
                atomic__operations = new object[]
                {
                    new
                    {
                        op   = "update",
                        data = new
                        {
                            type       = "textLanguages",
                            id         = existingLanguage.StringId,
                            attributes = new
                            {
                            }
                        }
                    },
                    new
                    {
                        op   = "update",
                        data = new
                        {
                            type       = "recordCompanies",
                            id         = existingCompany.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);

            string languageLink = HostPrefix + "/textLanguages/" + existingLanguage.StringId;

            ResourceObject singleData1 = responseDocument.Results[0].SingleData;

            singleData1.Should().NotBeNull();
            singleData1.Links.Should().NotBeNull();
            singleData1.Links.Self.Should().Be(languageLink);
            singleData1.Relationships.Should().NotBeEmpty();
            singleData1.Relationships["lyrics"].Links.Should().NotBeNull();
            singleData1.Relationships["lyrics"].Links.Self.Should().Be(languageLink + "/relationships/lyrics");
            singleData1.Relationships["lyrics"].Links.Related.Should().Be(languageLink + "/lyrics");

            string companyLink = HostPrefix + "/recordCompanies/" + existingCompany.StringId;

            ResourceObject singleData2 = responseDocument.Results[1].SingleData;

            singleData2.Should().NotBeNull();
            singleData2.Links.Should().NotBeNull();
            singleData2.Links.Self.Should().Be(companyLink);
            singleData2.Relationships.Should().NotBeEmpty();
            singleData2.Relationships["tracks"].Links.Should().NotBeNull();
            singleData2.Relationships["tracks"].Links.Self.Should().Be(companyLink + "/relationships/tracks");
            singleData2.Relationships["tracks"].Links.Related.Should().Be(companyLink + "/tracks");
        }
        public async Task Can_create_resources()
        {
            // Arrange
            const int elementCount = 5;

            List <MusicTrack> newTracks = _fakers.MusicTrack.Generate(elementCount);

            var operationElements = new List <object>(elementCount);

            for (int index = 0; index < elementCount; index++)
            {
                operationElements.Add(new
                {
                    op   = "add",
                    data = new
                    {
                        type       = "musicTracks",
                        attributes = new
                        {
                            title           = newTracks[index].Title,
                            lengthInSeconds = newTracks[index].LengthInSeconds,
                            genre           = newTracks[index].Genre,
                            releasedAt      = newTracks[index].ReleasedAt
                        }
                    }
                });
            }

            var requestBody = new
            {
                atomic__operations = operationElements
            };

            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(elementCount);

            for (int index = 0; index < elementCount; index++)
            {
                ResourceObject singleData = responseDocument.Results[index].SingleData;

                singleData.Should().NotBeNull();
                singleData.Type.Should().Be("musicTracks");
                singleData.Attributes["title"].Should().Be(newTracks[index].Title);
                singleData.Attributes["lengthInSeconds"].As <decimal?>().Should().BeApproximately(newTracks[index].LengthInSeconds);
                singleData.Attributes["genre"].Should().Be(newTracks[index].Genre);
                singleData.Attributes["releasedAt"].Should().BeCloseTo(newTracks[index].ReleasedAt);
                singleData.Relationships.Should().NotBeEmpty();
            }

            Guid[] newTrackIds = responseDocument.Results.Select(result => Guid.Parse(result.SingleData.Id)).ToArray();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                List <MusicTrack> tracksInDatabase = await dbContext.MusicTracks.Where(musicTrack => newTrackIds.Contains(musicTrack.Id)).ToListAsync();

                tracksInDatabase.Should().HaveCount(elementCount);

                for (int index = 0; index < elementCount; index++)
                {
                    MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == newTrackIds[index]);

                    trackInDatabase.Title.Should().Be(newTracks[index].Title);
                    trackInDatabase.LengthInSeconds.Should().BeApproximately(newTracks[index].LengthInSeconds);
                    trackInDatabase.Genre.Should().Be(newTracks[index].Genre);
                    trackInDatabase.ReleasedAt.Should().BeCloseTo(newTracks[index].ReleasedAt);
                }
            });
        }