Exemple #1
0
        public async Task Cannot_process_more_operations_than_maximum()
        {
            // Arrange
            var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService <IJsonApiOptions>();

            options.MaximumOperationsPerRequest = 2;

            var requestBody = new
            {
                atomic__operations = new object[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                        }
                    },
                    new
                    {
                        op   = "remove",
                        data = new
                        {
                        }
                    },
                    new
                    {
                        op   = "update",
                        data = new
                        {
                        }
                    }
                }
            };

            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: Request exceeds the maximum number of operations.");
            error.Detail.Should().Be("The number of operations in this request (3) is higher than 2.");
            error.Source.Pointer.Should().BeNull();
        }
        public async Task Can_clear_HasMany_relationship()
        {
            // Arrange
            MusicTrack 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
            (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync <string>(route, requestBody);

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

            responseDocument.Should().BeEmpty();

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

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

                List <Performer> performersInDatabase = await dbContext.Performers.ToListAsync();
                performersInDatabase.Should().HaveCount(2);
            });
        }
Exemple #3
0
        public async Task Cannot_remove_from_HasOne_relationship()
        {
            // Arrange
            MusicTrack existingTrack = _fakers.MusicTrack.Generate();

            existingTrack.OwnedBy = _fakers.RecordCompany.Generate();

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

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "remove",
                        @ref = new
                        {
                            type         = "musicTracks",
                            id           = existingTrack.StringId,
                            relationship = "ownedBy"
                        },
                        data = new
                        {
                            type = "recordCompanies",
                            id   = existingTrack.OwnedBy.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 'remove' operations.");
            error.Detail.Should().Be("Relationship 'ownedBy' must be a to-many relationship.");
        }
        public async Task Can_create_resource()
        {
            // Arrange
            var newArtistName = _fakers.Performer.Generate().ArtistName;
            var newBornAt     = _fakers.Performer.Generate().BornAt;

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

            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("performers");
            responseDocument.Results[0].SingleData.Attributes["artistName"].Should().Be(newArtistName);
            responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(newBornAt);
            responseDocument.Results[0].SingleData.Relationships.Should().BeNull();

            var newPerformerId = int.Parse(responseDocument.Results[0].SingleData.Id);

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                var performerInDatabase = await dbContext.Performers.FirstWithIdAsync(newPerformerId);

                performerInDatabase.ArtistName.Should().Be(newArtistName);
                performerInDatabase.BornAt.Should().BeCloseTo(newBornAt);
            });
        }
        public async Task Can_create_resource_with_client_generated_guid_ID_having_side_effects()
        {
            // Arrange
            TextLanguage newLanguage = _fakers.TextLanguage.Generate();

            newLanguage.Id = Guid.NewGuid();

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "textLanguages",
                            id         = newLanguage.StringId,
                            attributes = new
                            {
                                isoCode = newLanguage.IsoCode
                            }
                        }
                    }
                }
            };

            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("textLanguages");
            responseDocument.Results[0].SingleData.Attributes["isoCode"].Should().Be(newLanguage.IsoCode);
            responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("concurrencyToken");
            responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                TextLanguage languageInDatabase = await dbContext.TextLanguages.FirstWithIdAsync(newLanguage.Id);

                languageInDatabase.IsoCode.Should().Be(newLanguage.IsoCode);
            });
        }
Exemple #6
0
        public async Task Returns_JsonApi_ContentType_header_with_AtomicOperations_extension()
        {
            // Arrange
            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "policies",
                            attributes = new
                            {
                                name = "some"
                            }
                        }
                    }
                }
            };

            var route = "/operations";

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

            // Assert
            httpResponse.Should().HaveStatusCode(HttpStatusCode.OK);
            httpResponse.Content.Headers.ContentType.ToString().Should().Be(HeaderConstants.AtomicOperationsMediaType);
        }
Exemple #7
0
        public async Task Can_clear_OneToOne_relationship_from_principal_side()
        {
            // Arrange
            Lyric 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
                    }
                }
            };

            const string route = "/operations";

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

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

            responseDocument.Should().BeEmpty();

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

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

                List <MusicTrack> tracksInDatabase = await dbContext.MusicTracks.ToListAsync();
                tracksInDatabase.Should().HaveCount(1);
            });
        }
        public async Task Can_create_resources_for_matching_resource_type()
        {
            // Arrange
            var newTitle1 = _fakers.MusicTrack.Generate().Title;
            var newTitle2 = _fakers.MusicTrack.Generate().Title;

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "musicTracks",
                            attributes = new
                            {
                                title = newTitle1
                            }
                        }
                    },
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "musicTracks",
                            attributes = new
                            {
                                title = newTitle2
                            }
                        }
                    }
                }
            };

            var route = "/operations/musicTracks/create";

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

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

            responseDocument.Results.Should().HaveCount(2);
        }
Exemple #9
0
        public async Task Returns_resource_meta_in_create_resource_with_side_effects()
        {
            // Arrange
            var hitCounter = _testContext.Factory.Services.GetRequiredService <ResourceDefinitionHitCounter>();

            string newTitle1 = _fakers.MusicTrack.Generate().Title;
            string newTitle2 = _fakers.MusicTrack.Generate().Title;

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "musicTracks",
                            attributes = new
                            {
                                title      = newTitle1,
                                releasedAt = 1.January(2018)
                            }
                        }
                    },
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "musicTracks",
                            attributes = new
                            {
                                title      = newTitle2,
                                releasedAt = 23.August(1994)
                            }
                        }
                    }
                }
            };

            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.Meta.Should().HaveCount(1);
            responseDocument.Results[0].SingleData.Meta["Copyright"].Should().Be("(C) 2018. All rights reserved.");

            responseDocument.Results[1].SingleData.Meta.Should().HaveCount(1);
            responseDocument.Results[1].SingleData.Meta["Copyright"].Should().Be("(C) 1994. All rights reserved.");

            hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[]
Exemple #10
0
        public async Task Cannot_create_resource_with_multiple_violations()
        {
            // Arrange
            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "musicTracks",
                            attributes = new
                            {
                                lengthInSeconds = -1
                            }
                        }
                    }
                }
            };

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

            Error error1 = responseDocument.Errors[0];

            error1.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
            error1.Title.Should().Be("Input validation failed.");
            error1.Detail.Should().Be("The Title field is required.");
            error1.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/title");

            Error error2 = responseDocument.Errors[1];

            error2.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
            error2.Title.Should().Be("Input validation failed.");
            error2.Detail.Should().Be("The field LengthInSeconds must be between 1 and 1440.");
            error2.Source.Pointer.Should().Be("/atomic:operations[0]/data/attributes/lengthInSeconds");
        }
Exemple #11
0
        public async Task Returns_top_level_meta_in_create_resource_with_side_effects()
        {
            // Arrange
            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "performers",
                            attributes = new
                            {
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

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

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

            responseDocument.Meta.Should().HaveCount(3);
            responseDocument.Meta["license"].Should().Be("MIT");
            responseDocument.Meta["projectUrl"].Should().Be("https://github.com/json-api-dotnet/JsonApiDotNetCore/");

            string[] versionArray = ((IEnumerable <JToken>)responseDocument.Meta["versions"]).Select(token => token.ToString()).ToArray();

            versionArray.Should().HaveCount(4);
            versionArray.Should().Contain("v4.0.0");
            versionArray.Should().Contain("v3.1.0");
            versionArray.Should().Contain("v2.5.2");
            versionArray.Should().Contain("v1.3.1");
        }
Exemple #12
0
        public async Task Cannot_use_non_transactional_repository()
        {
            // Arrange
            var requestBody = new
            {
                atomic__operations = new object[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "performers",
                            attributes = new
                            {
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

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

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

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

            var error = responseDocument.Errors[0];

            error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);
            error.Title.Should().Be("Unsupported resource type in atomic:operations request.");
            error.Detail.Should().Be("Operations on resources of type 'performers' cannot be used because transaction support is unavailable.");
            error.Source.Pointer.Should().Be("/atomic:operations[0]");
        }
        public async Task Cannot_include_on_operations_endpoint()
        {
            // Arrange
            var requestBody = new
            {
                atomic__operations = new object[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "recordCompanies",
                            attributes = new
                            {
                            }
                        }
                    }
                }
            };

            const string route = "/operations?include=recordCompanies";

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

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

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

            Error error = responseDocument.Errors[0];

            error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
            error.Title.Should().Be("Usage of one or more query string parameters is not allowed at the requested endpoint.");
            error.Detail.Should().Be("The parameter 'include' cannot be used at this endpoint.");
            error.Source.Parameter.Should().Be("include");
        }
        public async Task Can_delete_existing_resource()
        {
            // Arrange
            Performer existingPerformer = _fakers.Performer.Generate();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                dbContext.Performers.Add(existingPerformer);
                await dbContext.SaveChangesAsync();
            });

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "remove",
                        @ref = new
                        {
                            type = "performers",
                            id   = existingPerformer.StringId
                        }
                    }
                }
            };

            const string route = "/operations";

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

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

            responseDocument.Should().BeEmpty();

            await _testContext.RunOnDatabaseAsync(async dbContext =>
            {
                Performer performerInDatabase = await dbContext.Performers.FirstWithIdOrDefaultAsync(existingPerformer.Id);

                performerInDatabase.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");
        }
Exemple #16
0
        public async Task Can_create_OneToOne_relationship_from_principal_side()
        {
            // Arrange
            MusicTrack 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
            (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("lyrics");
            responseDocument.Results[0].SingleData.Attributes.Should().NotBeEmpty();
            responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty();

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

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

                lyricInDatabase.Track.Should().NotBeNull();
                lyricInDatabase.Track.Id.Should().Be(existingTrack.Id);
            });
        }
        public async Task Update_resource_with_side_effects_returns_absolute_links()
        {
            // Arrange
            var existingLanguage = _fakers.TextLanguage.Generate();
            var 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
                            {
                            }
                        }
                    }
                }
            };

            var route = "/operations";

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

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

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

            responseDocument.Results[0].SingleData.Should().NotBeNull();
            responseDocument.Results[0].SingleData.Links.Should().NotBeNull();
            responseDocument.Results[0].SingleData.Links.Self.Should().Be("http://localhost/textLanguages/" + existingLanguage.StringId);
            responseDocument.Results[0].SingleData.Relationships.Should().NotBeEmpty();
            responseDocument.Results[0].SingleData.Relationships["lyrics"].Links.Should().NotBeNull();
            responseDocument.Results[0].SingleData.Relationships["lyrics"].Links.Self.Should().Be($"http://localhost/textLanguages/{existingLanguage.StringId}/relationships/lyrics");
            responseDocument.Results[0].SingleData.Relationships["lyrics"].Links.Related.Should().Be($"http://localhost/textLanguages/{existingLanguage.StringId}/lyrics");

            responseDocument.Results[1].SingleData.Should().NotBeNull();
            responseDocument.Results[1].SingleData.Links.Should().NotBeNull();
            responseDocument.Results[1].SingleData.Links.Self.Should().Be("http://localhost/recordCompanies/" + existingCompany.StringId);
            responseDocument.Results[1].SingleData.Relationships.Should().NotBeEmpty();
            responseDocument.Results[1].SingleData.Relationships["tracks"].Links.Should().NotBeNull();
            responseDocument.Results[1].SingleData.Relationships["tracks"].Links.Self.Should().Be($"http://localhost/recordCompanies/{existingCompany.StringId}/relationships/tracks");
            responseDocument.Results[1].SingleData.Relationships["tracks"].Links.Related.Should().Be($"http://localhost/recordCompanies/{existingCompany.StringId}/tracks");
        }
Exemple #18
0
        public async Task Create_resource_with_side_effects_returns_relative_links()
        {
            // Arrange
            var requestBody = new
            {
                atomic__operations = new object[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "textLanguages",
                            attributes = new
                            {
                            }
                        }
                    },
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "recordCompanies",
                            attributes = new
                            {
                            }
                        }
                    }
                }
            };

            const string route = "/api/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.Should().NotBeNull();

            string languageLink = "/api/textLanguages/" + Guid.Parse(responseDocument.Results[0].SingleData.Id);

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

            responseDocument.Results[1].SingleData.Should().NotBeNull();

            string companyLink = "/api/recordCompanies/" + short.Parse(responseDocument.Results[1].SingleData.Id);

            responseDocument.Results[1].SingleData.Links.Should().NotBeNull();
            responseDocument.Results[1].SingleData.Links.Self.Should().Be(companyLink);
            responseDocument.Results[1].SingleData.Relationships.Should().NotBeEmpty();
            responseDocument.Results[1].SingleData.Relationships["tracks"].Links.Should().NotBeNull();
            responseDocument.Results[1].SingleData.Relationships["tracks"].Links.Self.Should().Be(companyLink + "/relationships/tracks");
            responseDocument.Results[1].SingleData.Relationships["tracks"].Links.Related.Should().Be(companyLink + "/tracks");
        }
Exemple #19
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();
            });
        }
        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 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 Hides_text_in_create_resource_with_side_effects()
        {
            // Arrange
            var provider = _testContext.Factory.Services.GetRequiredService <LyricPermissionProvider>();

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

            var newLyrics = _fakers.Lyric.Generate(2);

            var requestBody = new
            {
                atomic__operations = new[]
                {
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "lyrics",
                            attributes = new
                            {
                                format = newLyrics[0].Format,
                                text   = newLyrics[0].Text
                            }
                        }
                    },
                    new
                    {
                        op   = "add",
                        data = new
                        {
                            type       = "lyrics",
                            attributes = new
                            {
                                format = newLyrics[1].Format,
                                text   = newLyrics[1].Text
                            }
                        }
                    }
                }
            };

            const string route = "/operations";

            // Act
            var(httpResponse, 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(newLyrics[0].Format);
            responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("text");

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

            provider.HitCount.Should().Be(4);
        }
        public async Task Cannot_process_for_missing_request_body()
        {
            // Arrange
            const string route = "/operations";

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

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

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

            Error error = responseDocument.Errors[0];

            error.StatusCode.Should().Be(HttpStatusCode.BadRequest);
            error.Title.Should().Be("Missing request body.");
            error.Detail.Should().BeNull();
            error.Source.Pointer.Should().BeNull();

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

                List <MusicTrack> tracksInDatabase = await dbContext.MusicTracks.ToListAsync();
                tracksInDatabase.Should().BeEmpty();
            });
        }
        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);
            });
        }