diff --git a/CHANGELOG.md b/CHANGELOG.md index 477c9832..a62c646e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Added +1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). ### Changed 1. Updated Flexberry ORM up to 7.2.0-beta01. ### Fixed 1. Fixed loading of object with crushing of already loaded masters. -2. Fixed loading of details. +2. Fixed loading of details. ## [7.1.1] - 2023.06.08 @@ -106,7 +107,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). 3. Support for limits on master details. 4. Support for limits on pseudodetails. 5. Decode Excel export column name. -6. HttpConfiguretion MapDataObjectRoute() extension method. +6. HttpConfiguretion MapDataObjectRoute() extension method. ### Changed @@ -160,13 +161,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). 3. Add support actions. 4. Add handler, called after exception appears. 5. In user functions and actions add possibility to return collections of primitive types and enums. In actions add possibility to use primitive types and enums as parameters. - + ### Fixed 1. Fix reading properties of files. 2. Fix error which occured in Mono in method `DefaultODataPathHandler.Parse(IEdmModel model, string serviceRoot, string odataPath)`. 3. Fix errors in work of user functions. 4. Fix error in association object enumeration filtration. - + ### Changed 1. Update dependencies. 2. Update ODataService package version to according ORM package version. diff --git a/NewPlatform.Flexberry.ORM.ODataService.nuspec b/NewPlatform.Flexberry.ORM.ODataService.nuspec index 5afc0ad2..3fcf28b3 100644 --- a/NewPlatform.Flexberry.ORM.ODataService.nuspec +++ b/NewPlatform.Flexberry.ORM.ODataService.nuspec @@ -1,111 +1,115 @@ - - - - NewPlatform.Flexberry.ORM.ODataService - 7.2.0-beta02 - Flexberry ORM ODataService - New Platform Ltd. - New Platform Ltd. - http://flexberry.ru/License-FlexberryOrm-Runtime - https://flexberry.net/ru/developers-flexberry-orm.html - https://flexberry.net/img/logo-color.png - true - Flexberry ORM OData Service Package. - - Changed - 1. Updated Flexberry ORM up to 7.2.0-beta01. - Fixed - 1. Fixed loading of object with crushing of already loaded masters. - 2. Fixed loading of details. - - Copyright New Platform Ltd 2023 - Flexberry ORM OData ODataService - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + NewPlatform.Flexberry.ORM.ODataService + 7.2.0-beta02 + Flexberry ORM ODataService + New Platform Ltd. + New Platform Ltd. + http://flexberry.ru/License-FlexberryOrm-Runtime + https://flexberry.net/ru/developers-flexberry-orm.html + https://flexberry.net/img/logo-color.png + true + Flexberry ORM OData Service Package. + + Added + 1. `updateViews` parameter of `DefaultDataObjectEdmModelBuilder` class. It allows to change update views for data objects (update view is used for loading a data object during OData update requests). + + Changed + 1. Updated Flexberry ORM up to 7.2.0-beta01. + + Fixed + 1. Fixed loading of object with crushing of already loaded masters. + 2. Fixed loading of details. + + Copyright New Platform Ltd 2023 + Flexberry ORM OData ODataService + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs index 9b8ce899..e5b682bd 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Controllers/DataObjectController.ModifyData.cs @@ -266,35 +266,35 @@ private NoContentResult DeleteEntity(object key) Init(); /* В ситуации, когда мастер и детейл одного типа, ORM без подгрузки копии данных не может корректно разобрать порядок, - * в котором объекты должны быть удалены. - */ + * в котором объекты должны быть удалены. + */ bool needDataCopyLoad = _typesWithSameDetailAndMaster.Contains(type); if (!_typesWithNotSameDetailAndMaster.Contains(type) && !needDataCopyLoad) { - string[] props = Information.GetAllPropertyNames(type); - int length = props.Length; - int index = 0; - - while(!needDataCopyLoad && index < length) - { - string prop = props[index]; - Type propType = Information.GetPropertyType(type, prop); - needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; - - index++; - } + string[] props = Information.GetAllPropertyNames(type); + int length = props.Length; + int index = 0; + + while (!needDataCopyLoad && index < length) + { + string prop = props[index]; + Type propType = Information.GetPropertyType(type, prop); + needDataCopyLoad = propType.IsSubclassOf(typeof(DataObject)) && Information.GetDetailArrayPropertyName(type, propType) != null; + + index++; + } } DataObject obj = null; if (!needDataCopyLoad) { - obj = DataObjectCache.CreateDataObject(type, key); - _typesWithNotSameDetailAndMaster.Add(type); + obj = DataObjectCache.CreateDataObject(type, key); + _typesWithNotSameDetailAndMaster.Add(type); } else { - obj = LoadObject(type, key.ToString()); - _typesWithSameDetailAndMaster.Add(type); + obj = LoadObject(type, key.ToString()); + _typesWithSameDetailAndMaster.Add(type); } // Удаляем объект с заданным ключем. @@ -676,7 +676,7 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) { // Создадим объект данных по пришедшей сущности. // В переменной objs сформируем список всех объектов для обновления в нужном порядке: сам объект и зависимые всех уровней. - DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs); + DataObject obj = GetDataObjectByEdmEntity(edmEntity, key, objs, useUpdateView: true); for (int i = 0; i < objs.Count; i++) { @@ -739,12 +739,13 @@ private DataObject UpdateObject(EdmEntityObject edmEntity, object key) } /// - /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению по умолчанию, иначе - создаётся новый. + /// Получить объект данных по ключу: если объект есть в хранилище, то возвращается загруженным по представлению , иначе - создаётся новый. /// /// Тип объекта, не может быть null. - /// Значение ключа.> + /// Значение ключа. + /// Представление для загрузки объекта. /// Объект данных. - private DataObject ReturnDataObject(Type objType, object keyValue) + private DataObject ReturnDataObject(Type objType, object keyValue, View view) { if (objType == null) { @@ -754,7 +755,6 @@ private DataObject ReturnDataObject(Type objType, object keyValue) if (keyValue != null) { DataObject dataObjectFromCache = DataObjectCache.GetLivingDataObject(objType, keyValue); - View view = _model.GetDataObjectDefaultView(objType); if (dataObjectFromCache != null) { @@ -764,12 +764,12 @@ private DataObject ReturnDataObject(Type objType, object keyValue) { // Для обратной совместимости сравним перечень загруженных свойств и свойств в представлении. /* Данный код срабатывает, например, если в кэше был объект, который загрузился только на уровне первичного ключа. - * - * Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В. - * При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В. - * Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние LightLoaded. - * Догружать необходимо только те свойства, что ещё не загружались (потому что загруженные уже могут быть изменены). - */ + * + * Данный код также срабатывает в следующей ситуации: есть класс А, у него детейл Б, у которого есть наследник В. + * При загрузке объекта класса А подгрузятся его детейлы, однако они будут подгружены по представлению, которое соответствует классу Б, даже если детейлы класса В. + * Таким образом, в кэше окажутся объекты класса В, которые загружены только по свойствам Б. Раз не все свойства подгружены, то состояние LightLoaded. + * Догружать необходимо только те свойства, что ещё не загружались (потому что загруженные уже могут быть изменены). + */ string[] loadedProps = dataObjectFromCache.GetLoadedProperties(); IEnumerable ownProps = view.Properties.Where(p => !p.Name.Contains('.')); if (!ownProps.All(p => loadedProps.Contains(p.Name))) @@ -829,6 +829,28 @@ private DataObject ReturnDataObject(Type objType, object keyValue) return obj; } + /// + /// Добавляет объект данных в список на обновление, если его там ещё нет. + /// + /// Список на обновление. + /// Объект данных, который добавляем. + /// Добавлять в конец списка. + private static void AddObjectToUpdate(List objsToUpdate, DataObject dataObject, bool insertToEnd) + { + bool objAlreadyExists = objsToUpdate.Any(o => PKHelper.EQDataObject(o, dataObject, false)); + if (!objAlreadyExists) + { + if (insertToEnd) + { + objsToUpdate.Add(dataObject); // Добавляем в конец списка. + } else + { + objsToUpdate.Insert(0, dataObject); // Добавляем объект в начало списка. + } + + } + } + /// /// Построение объекта данных по сущности OData. /// @@ -836,21 +858,20 @@ private DataObject ReturnDataObject(Type objType, object keyValue) /// Значение ключевого поля сущности. /// Список объектов для обновления. /// Признак, что объект добавляется в конец списка обновления. + /// Использовать представление для обновления (вместо представления по умолчанию). /// Объект данных. - private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false) + private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object key, List dObjs, bool endObject = false, bool useUpdateView = false) { if (edmEntity == null) { return null; } - IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; - Type objType = _model.GetDataObjectType(_model.GetEdmEntitySet(entityType).Name); - // Значение свойства. object value; // Получим значение ключа. + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; IEnumerable entityProps = entityType.Properties().ToList(); var keyProperty = entityProps.FirstOrDefault(prop => prop.Name == _model.KeyPropertyName); if (key != null) @@ -862,26 +883,24 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke edmEntity.TryGetPropertyValue(keyProperty.Name, out value); } - // Загрузим объект из хранилища, если он там есть (используем представление по умолчанию), или создадим, если нет, но только для POST. + // Загрузим объект из хранилища, если он там есть, или создадим, если нет, но только для POST. // Тем самым гарантируем загруженность свойств при необходимости обновления и установку нужного статуса. - DataObject obj = ReturnDataObject(objType, value); + Type objType = _model.GetDataObjectType(edmEntity); - // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. - var objInList = dObjs.FirstOrDefault(o => PKHelper.EQDataObject(o, obj, false)); - if (objInList == null) + View view = null; + if (useUpdateView) { - if (!endObject) - { - // Добавляем объект в начало списка. - dObjs.Insert(0, obj); - } - else - { - // Добавляем в конец списка. - dObjs.Add(obj); - } + view = _model.GetDataObjectUpdateView(objType) ?? _model.GetDataObjectDefaultView(objType); + } else + { + view = _model.GetDataObjectDefaultView(objType); } + DataObject obj = ReturnDataObject(objType, value, view); + + // Добавляем объект в список для обновления, если там ещё нет объекта с таким ключом. + AddObjectToUpdate(dObjs, obj, endObject); + // Все свойства объекта данных означим из пришедшей сущности, если они были там установлены(изменены). string agregatorPropertyName = Information.GetAgregatePropertyName(objType); IEnumerable changedPropNames = edmEntity.GetChangedPropertyNames(); @@ -923,7 +942,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke { // Порядок вставки влияет на порядок отправки объектов в UpdateObjects это в свою очередь влияет на то, как срабатывают бизнес-серверы. Бизнес-сервер мастера должен сработать после, а агрегатора перед этим объектом. bool insertIntoEnd = string.IsNullOrEmpty(agregatorPropertyName); - DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd); + DataObject master = GetDataObjectByEdmEntity(edmMaster, null, dObjs, insertIntoEnd, useUpdateView); Information.SetPropValueByName(obj, dataObjectPropName, master); @@ -960,7 +979,8 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke (EdmEntityObject)edmEnt, null, dObjs, - true); + true, + useUpdateView); if (det.__PrimaryKey == null) { @@ -1055,20 +1075,7 @@ private DataObject GetDataObjectByEdmEntity(EdmEntityObject edmEntity, object ke if (agregator != null) { - DataObject existObject = dObjs.FirstOrDefault(o => PKHelper.EQDataObject(o, agregator, false)); - if (existObject == null) - { - if (!endObject) - { - // Добавляем объект в начало списка. - dObjs.Insert(0, agregator); - } - else - { - // Добавляем в конец списка. - dObjs.Add(agregator); - } - } + AddObjectToUpdate(dObjs, agregator, endObject); } } diff --git a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs index e0245aa6..b7639b38 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Extensions/DataServiceExtensions.cs @@ -226,6 +226,37 @@ public static void SafeLoadDetails(this IDataService dataService, View view, ILi } } + /// + /// Догрузка объекта по указанному представлению, с загрузкой детейлов с сохранением состояния изменения. + /// + /// Экземпляр сервиса данных. + /// Объект данных, который нужно догрузить. + /// Представление, которое используется для догрузки. + /// Кеш объектов данных. + public static void SafeLoadObject(this IDataService dataService, DataObject dataObject, View view, DataObjectCache dataObjectCache) + { + if (dataService == null) + { + throw new ArgumentNullException(nameof(dataService)); + } + + if (view == null) + { + throw new ArgumentNullException(nameof(view)); + } + + // Вычитывать объект сразу с детейлами нельзя, поскольку в этой же транзакции могут уже оказаться отдельные операции с детейлами и перевычитка затрёт эти изменения. + View miniView = view.Clone(); + DetailInView[] miniViewDetails = miniView.Details; + miniView.Details = new DetailInView[0]; + dataService.LoadObject(miniView, dataObject, false, true, dataObjectCache); + + if (miniViewDetails.Length > 0) + { + dataService.SafeLoadDetails(view, new DataObject[] { dataObject }, dataObjectCache); + } + } + /// /// Add detail object to agregator according detail type. /// diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs index c386f791..dcfae14f 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmModel.cs @@ -511,6 +511,21 @@ public View GetDataObjectDefaultView(Type dataObjectType) } return _metadata[dataObjectType].DefaultView.Clone(); + } + + /// + /// Осуществляет получение представления для обновления объекта, соответствующего заданному типу объекта данных. + /// + /// Тип объекта данных, для которого требуется получить представление для обновления. + /// Представление для обновления объекта, соответствующее заданному типу объекта данных. + public View GetDataObjectUpdateView(Type dataObjectType) + { + if (dataObjectType == null) + { + throw new ArgumentNullException(nameof(dataObjectType), "Contract assertion not met: dataObjectType != null"); + } + + return _metadata[dataObjectType].UpdateView?.Clone(); } /// @@ -534,7 +549,7 @@ public List GetTypes(List strTypes) /// /// Осуществляет получение типа объекта данных, соответствующего заданному имени набора сущностей в EDM-модели. /// - /// Имя набора сущностей в EDM-модели, для которого требуется получить представление по умолчанию. + /// Имя набора сущностей в EDM-модели, для которого требуется получить тип. /// Типа объекта данных, соответствующий заданному имени набора сущностей в EDM-модели. public Type GetDataObjectType(string edmEntitySetName) { @@ -554,6 +569,22 @@ public Type GetDataObjectType(string edmEntitySetName) return dataObjectType; } + /// + /// Осуществляет получение типа объекта данных, соответствующего заданной сущности в EDM-модели. + /// + /// Сущность в EDM-модели, для которой требуется получить тип. + /// Типа объекта данных, соответствующий заданной сущности в EDM-модели. + public Type GetDataObjectType(EdmEntityObject edmEntity) + { + if (edmEntity == null) + { + throw new ArgumentNullException(nameof(edmEntity)); + } + + IEdmEntityType entityType = (IEdmEntityType)edmEntity.ActualEdmType; + return GetDataObjectType(GetEdmEntitySet(entityType).Name); + } + /// /// Получает список зарегистрированных в модели типов, которые являются дочерними к данному родительскому типу. /// В список добавляется также сам родительский тип. diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs index bcfcc3ef..4e4219a1 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DataObjectEdmTypeSettings.cs @@ -31,6 +31,11 @@ public sealed class DataObjectEdmTypeSettings /// public View DefaultView { get; set; } + /// + /// View to be used instead of DefaultView on updates (Patch/Batch). + /// + public View UpdateView { get; set; } + /// /// The list of exposed details. /// diff --git a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs index 63f84ab6..3e6ed225 100644 --- a/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs +++ b/NewPlatform.Flexberry.ORM.ODataService/Model/DefaultDataObjectEdmModelBuilder.cs @@ -69,6 +69,11 @@ public class DefaultDataObjectEdmModelBuilder : IDataObjectEdmModelBuilder /// public Func EntityPropertyNameBuilder { get; set; } + /// + /// Dictionary of views to be used to load objects on updates. Used to restrict properties for performance (all props are loaded if view is not specified). + /// + private Dictionary UpdateViews { get; set; } + private readonly PropertyInfo _keyProperty = Information.ExtractPropertyInfo(n => n.__PrimaryKey); /// @@ -82,7 +87,8 @@ public DefaultDataObjectEdmModelBuilder( IEnumerable searchAssemblies, bool useNamespaceInEntitySetName = true, PseudoDetailDefinitions pseudoDetailDefinitions = null, - Dictionary additionalMapping = null) + Dictionary additionalMapping = null, + IEnumerable> updateViews = null) { _searchAssemblies = searchAssemblies ?? throw new ArgumentNullException(nameof(searchAssemblies), "Contract assertion not met: searchAssemblies != null"); _useNamespaceInEntitySetName = useNamespaceInEntitySetName; @@ -94,6 +100,11 @@ public DefaultDataObjectEdmModelBuilder( EntityPropertyNameBuilder = BuildEntityPropertyName; EntityTypeNameBuilder = BuildEntityTypeName; EntityTypeNamespaceBuilder = BuildEntityTypeNamespace; + + if (updateViews != null) + { + SetUpdateView(updateViews); + } } /// @@ -173,6 +184,54 @@ public IPseudoDetailDefinition GetPseudoDetailDefinition(object pseudoDetail) .FirstOrDefault(); } + /// + /// Change default views that would be used to load objects on updates. + /// + /// Should be called before MapDataObjectRoute. + /// Key - DataObject type, value - view to be used for objects of that type on updates. + private void SetUpdateView(IEnumerable> updateViews) + { + if (this.UpdateViews is null) + { + this.UpdateViews = new Dictionary(); + } + + if (updateViews != null) + { + foreach (KeyValuePair kvp in updateViews) + { + SetUpdateView(kvp.Key, kvp.Value); + } + } + } + + /// + /// Change for a specific . Update view would be used to load these objects on updates. + /// + /// Should be called before MapDataObjectRoute. + /// DataObject type for which update view would be set. + /// Update view to be used for objects of type . Setting removes update view for the type. + private void SetUpdateView(Type dataObjectType, View updateView) + { + if (!dataObjectType.IsSubclassOf(typeof(DataObject))) + { + throw new ArgumentException("Update view can be set only for a DataObject.", nameof(dataObjectType)); + } + + if (updateView is null) + { + UpdateViews.Remove(dataObjectType); + return; + } + + if (!Information.CheckViewForClasses(updateView.Name, dataObjectType)) + { + throw new ArgumentException($"View from DataObject {updateView.DefineClassType} can not be set for a DataObject of type {dataObjectType}.", nameof(updateView)); + } + + UpdateViews[dataObjectType] = updateView; + } + /// /// Adds the property for exposing. /// @@ -238,12 +297,21 @@ private void AddDataObjectWithHierarchy(DataObjectEdmMetadata meta, Type dataObj AddDataObjectWithHierarchy(meta, baseType); + // Extract user-defined update view: + View updateView = null; + if (UpdateViews != null) + { + UpdateViews.TryGetValue(dataObjectType, out updateView); + } + var typeSettings = meta[dataObjectType] = new DataObjectEdmTypeSettings { EnableCollection = true, CollectionName = EntitySetNameBuilder(dataObjectType), - DefaultView = DynamicView.Create(dataObjectType, null).View + DefaultView = DynamicView.Create(dataObjectType, null).View, + UpdateView = updateView, }; + AddProperties(dataObjectType, typeSettings); if (typeSettings.KeyType != null) meta[baseType].KeyType = null; diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs index 22cac7b0..61d3c46b 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseIntegratedTest.cs @@ -30,9 +30,11 @@ public abstract class BaseIntegratedTest : IDisposable /// /// Base class for integration tests. /// - public abstract class BaseIntegratedTest : IClassFixture>, IDisposable + /// Startup class used for booting the application. + public abstract class BaseIntegratedTest : IClassFixture>, IDisposable + where TStartup : class { - protected readonly WebApplicationFactory _factory; + protected readonly WebApplicationFactory _factory; #endif protected ITestOutputHelper _output; @@ -139,7 +141,7 @@ protected BaseIntegratedTest(string tempDbNamePrefix, bool useGisDataService = f /// Unit tests debug output. /// Prefix for temp database name. /// Use DataService with Gis support. - protected BaseIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output, string tempDbNamePrefix, bool useGisDataService = false) + protected BaseIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output, string tempDbNamePrefix, bool useGisDataService = false) { _factory = factory; _output = output; diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs index e904d023..eee8605f 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/BaseODataServiceIntegratedTest.cs @@ -35,7 +35,13 @@ /// /// Базовый класс для тестирования работы с данными через ODataService. /// +#if NETFRAMEWORK public class BaseODataServiceIntegratedTest : BaseIntegratedTest +#endif +#if NETCOREAPP + public class BaseODataServiceIntegratedTest : BaseIntegratedTest + where TStartup : class +#endif { protected IDataObjectEdmModelBuilder _builder; @@ -72,7 +78,7 @@ public BaseODataServiceIntegratedTest( } #endif #if NETCOREAPP - public BaseODataServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output = null, bool useNamespaceInEntitySetName = false, bool useGisDataService = false, PseudoDetailDefinitions pseudoDetailDefinitions = null) + public BaseODataServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output = null, bool useNamespaceInEntitySetName = false, bool useGisDataService = false, PseudoDetailDefinitions pseudoDetailDefinitions = null) : base(factory, output, "ODataDB", useGisDataService) { Init(useNamespaceInEntitySetName, pseudoDetailDefinitions); diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs index c0de8995..8dc8960e 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/BatchTest.cs @@ -1,255 +1,260 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Net.Http; - - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business.LINQProvider; - using ICSSoft.STORMNET.KeyGen; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; - using Xunit; - using View = ICSSoft.STORMNET.View; - - /// - /// The class of tests for CRUD operations at Batch form. - /// There are extra batch tests at . - /// - public class BatchTest : BaseODataServiceIntegratedTest - { -#if NETCOREAPP - /// - /// Default constructor. - /// - /// Factory for application. - /// Output for debug information. - public BatchTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) - : base(factory, output) - { - } -#endif - - /// - /// Test batch update of master-class with class at the same time. - /// It checks that dataobject cache is not crashed. - /// There are a master and object with link to master at batch request. Master is the first at the batch request. The link between object and master is not changed. - /// It is necessary that during batch processing master stay the same and is not overwriten. - /// - [Fact] - public void UpdateMasterAndClassTest() - { - ActODataService(args => - { - // Arrange. - string[] porodaPropertiesNames = - { - Information.ExtractPropertyPath<Порода>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Порода>(x => x.Название), - }; - string[] koshkaPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - }; - View porodaDynamicView = new View(new ViewAttribute("porodaDynamicView", porodaPropertiesNames), typeof(Порода)); - View koshkaDynamicView = new View(new ViewAttribute("koshkaDynamicView", koshkaPropertiesNames), typeof(Кошка)); - - const string InitialName = "Initial"; - const string OtherName = "Other"; - Порода poroda = new Порода() { Название = InitialName }; - Кошка koshka = new Кошка() { Кличка = InitialName, Порода = poroda}; - args.DataService.UpdateObject(koshka); - - Порода poroda1 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); - Кошка koshka1 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); - Assert.NotNull(poroda1); - Assert.NotNull(koshka1); - - poroda.Название = OtherName; - koshka.Кличка = OtherName; - - string requestJsonDatakoshka = koshka.ToJson(koshkaDynamicView, args.Token.Model); - DataObjectDictionary objJsonKoshka = DataObjectDictionary.Parse(requestJsonDatakoshka, koshkaDynamicView, args.Token.Model); - - objJsonKoshka.Add( - $"{nameof(Кошка.Порода)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name, - ((KeyGuid)poroda.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDatakoshka = objJsonKoshka.Serialize(); - - const string baseUrl = "http://localhost/odata"; - string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. - { - - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name}", - poroda.ToJson(porodaDynamicView, args.Token.Model), - poroda), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", - requestJsonDatakoshka, - koshka), - }; - - // Act. - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - // Assert. - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - Порода poroda2 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); - Кошка koshka2 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); - Assert.NotNull(poroda2); - Assert.NotNull(koshka2); - Assert.Equal(OtherName, poroda2.Название); - Assert.Equal(OtherName, koshka2.Кличка); - } - }); - } - - /// - /// Test batch update with inheritance. - /// It checks that dataobject cache is not crashed. - /// There are classes A, its detail B, that has descendant C. During class A loading its details are loaded too, but details are loaded by View of class B, while details are of class C. - /// Thus there are objects of type C at the cache while they are loaded by properties of class B only. That's why the state of details is LightLoaded. - /// It is necessary to post-load only propertues that are not loaded before (loaded properties can be changed). - /// - [Fact] - public void UpdateWithInheritanceAndDetailsTest() - { - ActODataService(args => - { - // Arrange. - const string InitialName = "Initial"; - const string OtherName = "Other"; - TestConfiguration testConfiguration = new TestConfiguration() { Name = InitialName }; - FirstLevel first = new FirstLevel() { Name = InitialName, TestConfiguration = testConfiguration }; - TestClass second1 = new TestClass { Name = InitialName, FirstLevel = first }; - TestAssociation second2 = new TestAssociation { Name = InitialName, FirstLevel = first, SecondLevel1 = second1 }; - ThirdLevel third = new ThirdLevel { Name = InitialName, TestClass = second1 }; - DataObject[] updateObjects = new DataObject[] { testConfiguration, first, second1, second2, third }; - args.DataService.UpdateObjects(ref updateObjects); - - second2.Name = OtherName; // Изменение значения детейла одного типа, который имеет мастеровую ссылку на детейл второго типа (второй тип имеет детейл собственный). - ThirdLevel third2 = new ThirdLevel { Name = OtherName, TestClass = second1 }; // Добавление детейлов в детейл второго типа. - - string[] firstPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View firstLevelDynamicView = new View(new ViewAttribute("firstLevelDynamicView", firstPropertiesNames), typeof(FirstLevel)); - - string[] second1PropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View second1DynamicView = new View(new ViewAttribute("second1DynamicView", second1PropertiesNames), typeof(TestClass)); - - string[] second2PropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View second2DynamicView = new View(new ViewAttribute("second2DynamicView", second2PropertiesNames), typeof(TestAssociation)); - - string[] thirdPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - }; - View thirdLevelDynamicView = new View(new ViewAttribute("thirdDynamicView", thirdPropertiesNames), typeof(ThirdLevel)); - - // Операция изменения детейла второго типа (он попадает в батч-запрос как агрегатор к добавляемому детейлу второго уровня). - string requestJsonDataSecond1 = second1.ToJson(second1DynamicView, args.Token.Model); - DataObjectDictionary objJsonSecond1 = DataObjectDictionary.Parse(requestJsonDataSecond1, second1DynamicView, args.Token.Model); - objJsonSecond1.Add( // Добавляется ссылка на агрегатор. - $"{nameof(TestClass.FirstLevel)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); - requestJsonDataSecond1 = objJsonSecond1.Serialize(); - - // Операция вставки детейла второго уровня. - string requestJsonDataThird2 = third2.ToJson(thirdLevelDynamicView, args.Token.Model); - DataObjectDictionary objJsonThird2 = DataObjectDictionary.Parse(requestJsonDataThird2, thirdLevelDynamicView, args.Token.Model); - objJsonThird2.Add( // Добавляется ссылка на агрегатор. - $"{nameof(ThirdLevel.TestClass)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); - requestJsonDataThird2 = objJsonThird2.Serialize(); - - // Операция изменения детейла первого типа. - string requestJsonDataSecond2 = second2.ToJson(second2DynamicView, args.Token.Model); - DataObjectDictionary objJsonSecond2 = DataObjectDictionary.Parse(requestJsonDataSecond2, second2DynamicView, args.Token.Model); - objJsonSecond2.Add( // Добавляется ссылка на агрегатор. - $"{nameof(TestAssociation.FirstLevel)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); - objJsonSecond2.Add( // Добавляется ссылка мастеровая на другой детейл. - $"{nameof(TestAssociation.SecondLevel1)}@odata.bind", - string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); - requestJsonDataSecond2 = objJsonSecond2.Serialize(); - - const string baseUrl = "http://localhost/odata"; - string[] changesets = new[] - { - - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name}", - requestJsonDataSecond1, - second1), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ThirdLevel)).Name}", - requestJsonDataThird2, - third2), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestAssociation)).Name}", - requestJsonDataSecond2, - second2), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - - // Код для удобства отлавливания исключений. - args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => - { - Exception currentException = exception; - - while (currentException != null) - { - currentException = currentException.InnerException; - } - - return exception; - }; - - // Act. - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - // Assert. - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.Created, HttpStatusCode.OK }); - - string[] thirdPropertiesNames2 = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name), - Information.ExtractPropertyPath(x => x.TestClass), - }; - View thirdLevelDynamicView2 = new View(new ViewAttribute("thirdDynamicView2", thirdPropertiesNames2), typeof(ThirdLevel)); - List thirdLevelList = args.DataService.Query(thirdLevelDynamicView2).Where(x => x.TestClass.__PrimaryKey == second1.__PrimaryKey).ToList(); - Assert.NotNull(thirdLevelList); - Assert.True(thirdLevelList.Any()); - Assert.Equal(2, thirdLevelList.Count); - - TestAssociation checkAssociation = args.DataService.Query(second2DynamicView).FirstOrDefault(x => x.__PrimaryKey == second2.__PrimaryKey); - Assert.NotNull(checkAssociation); - Assert.Equal(OtherName, checkAssociation.Name); - } - }); - } - } -} - +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business.LINQProvider; + using ICSSoft.STORMNET.KeyGen; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; + using Xunit; + using View = ICSSoft.STORMNET.View; + + /// + /// The class of tests for CRUD operations at Batch form. + /// There are extra batch tests at . + /// +#if NETFRAMEWORK + public class BatchTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BatchTest : BaseODataServiceIntegratedTest +#endif + { +#if NETCOREAPP + /// + /// Default constructor. + /// + /// Factory for application. + /// Output for debug information. + public BatchTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + : base(factory, output) + { + } +#endif + + /// + /// Test batch update of master-class with class at the same time. + /// It checks that dataobject cache is not crashed. + /// There are a master and object with link to master at batch request. Master is the first at the batch request. The link between object and master is not changed. + /// It is necessary that during batch processing master stay the same and is not overwriten. + /// + [Fact] + public void UpdateMasterAndClassTest() + { + ActODataService(args => + { + // Arrange. + string[] porodaPropertiesNames = + { + Information.ExtractPropertyPath<Порода>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Порода>(x => x.Название), + }; + string[] koshkaPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + }; + View porodaDynamicView = new View(new ViewAttribute("porodaDynamicView", porodaPropertiesNames), typeof(Порода)); + View koshkaDynamicView = new View(new ViewAttribute("koshkaDynamicView", koshkaPropertiesNames), typeof(Кошка)); + + const string InitialName = "Initial"; + const string OtherName = "Other"; + Порода poroda = new Порода() { Название = InitialName }; + Кошка koshka = new Кошка() { Кличка = InitialName, Порода = poroda}; + args.DataService.UpdateObject(koshka); + + Порода poroda1 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); + Кошка koshka1 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); + Assert.NotNull(poroda1); + Assert.NotNull(koshka1); + + poroda.Название = OtherName; + koshka.Кличка = OtherName; + + string requestJsonDatakoshka = koshka.ToJson(koshkaDynamicView, args.Token.Model); + DataObjectDictionary objJsonKoshka = DataObjectDictionary.Parse(requestJsonDatakoshka, koshkaDynamicView, args.Token.Model); + + objJsonKoshka.Add( + $"{nameof(Кошка.Порода)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name, + ((KeyGuid)poroda.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDatakoshka = objJsonKoshka.Serialize(); + + const string baseUrl = "http://localhost/odata"; + string[] changesets = new[] // Важно, чтобы сначала шёл мастер, потом объект, имеющий на него ссылку. + { + + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Порода)).Name}", + poroda.ToJson(porodaDynamicView, args.Token.Model), + poroda), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", + requestJsonDatakoshka, + koshka), + }; + + // Act. + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + // Assert. + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + Порода poroda2 = args.DataService.Query<Порода>(porodaDynamicView).FirstOrDefault(x => x.__PrimaryKey == poroda.__PrimaryKey); + Кошка koshka2 = args.DataService.Query<Кошка>(koshkaDynamicView).FirstOrDefault(x => x.__PrimaryKey == koshka.__PrimaryKey); + Assert.NotNull(poroda2); + Assert.NotNull(koshka2); + Assert.Equal(OtherName, poroda2.Название); + Assert.Equal(OtherName, koshka2.Кличка); + } + }); + } + + /// + /// Test batch update with inheritance. + /// It checks that dataobject cache is not crashed. + /// There are classes A, its detail B, that has descendant C. During class A loading its details are loaded too, but details are loaded by View of class B, while details are of class C. + /// Thus there are objects of type C at the cache while they are loaded by properties of class B only. That's why the state of details is LightLoaded. + /// It is necessary to post-load only propertues that are not loaded before (loaded properties can be changed). + /// + [Fact] + public void UpdateWithInheritanceAndDetailsTest() + { + ActODataService(args => + { + // Arrange. + const string InitialName = "Initial"; + const string OtherName = "Other"; + TestConfiguration testConfiguration = new TestConfiguration() { Name = InitialName }; + FirstLevel first = new FirstLevel() { Name = InitialName, TestConfiguration = testConfiguration }; + TestClass second1 = new TestClass { Name = InitialName, FirstLevel = first }; + TestAssociation second2 = new TestAssociation { Name = InitialName, FirstLevel = first, SecondLevel1 = second1 }; + ThirdLevel third = new ThirdLevel { Name = InitialName, TestClass = second1 }; + DataObject[] updateObjects = new DataObject[] { testConfiguration, first, second1, second2, third }; + args.DataService.UpdateObjects(ref updateObjects); + + second2.Name = OtherName; // Изменение значения детейла одного типа, который имеет мастеровую ссылку на детейл второго типа (второй тип имеет детейл собственный). + ThirdLevel third2 = new ThirdLevel { Name = OtherName, TestClass = second1 }; // Добавление детейлов в детейл второго типа. + + string[] firstPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View firstLevelDynamicView = new View(new ViewAttribute("firstLevelDynamicView", firstPropertiesNames), typeof(FirstLevel)); + + string[] second1PropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View second1DynamicView = new View(new ViewAttribute("second1DynamicView", second1PropertiesNames), typeof(TestClass)); + + string[] second2PropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View second2DynamicView = new View(new ViewAttribute("second2DynamicView", second2PropertiesNames), typeof(TestAssociation)); + + string[] thirdPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + }; + View thirdLevelDynamicView = new View(new ViewAttribute("thirdDynamicView", thirdPropertiesNames), typeof(ThirdLevel)); + + // Операция изменения детейла второго типа (он попадает в батч-запрос как агрегатор к добавляемому детейлу второго уровня). + string requestJsonDataSecond1 = second1.ToJson(second1DynamicView, args.Token.Model); + DataObjectDictionary objJsonSecond1 = DataObjectDictionary.Parse(requestJsonDataSecond1, second1DynamicView, args.Token.Model); + objJsonSecond1.Add( // Добавляется ссылка на агрегатор. + $"{nameof(TestClass.FirstLevel)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); + requestJsonDataSecond1 = objJsonSecond1.Serialize(); + + // Операция вставки детейла второго уровня. + string requestJsonDataThird2 = third2.ToJson(thirdLevelDynamicView, args.Token.Model); + DataObjectDictionary objJsonThird2 = DataObjectDictionary.Parse(requestJsonDataThird2, thirdLevelDynamicView, args.Token.Model); + objJsonThird2.Add( // Добавляется ссылка на агрегатор. + $"{nameof(ThirdLevel.TestClass)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); + requestJsonDataThird2 = objJsonThird2.Serialize(); + + // Операция изменения детейла первого типа. + string requestJsonDataSecond2 = second2.ToJson(second2DynamicView, args.Token.Model); + DataObjectDictionary objJsonSecond2 = DataObjectDictionary.Parse(requestJsonDataSecond2, second2DynamicView, args.Token.Model); + objJsonSecond2.Add( // Добавляется ссылка на агрегатор. + $"{nameof(TestAssociation.FirstLevel)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(FirstLevel)).Name, ((KeyGuid)first.__PrimaryKey).Guid.ToString("D"))); + objJsonSecond2.Add( // Добавляется ссылка мастеровая на другой детейл. + $"{nameof(TestAssociation.SecondLevel1)}@odata.bind", + string.Format("{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name, ((KeyGuid)second1.__PrimaryKey).Guid.ToString("D"))); + requestJsonDataSecond2 = objJsonSecond2.Serialize(); + + const string baseUrl = "http://localhost/odata"; + string[] changesets = new[] + { + + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestClass)).Name}", + requestJsonDataSecond1, + second1), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ThirdLevel)).Name}", + requestJsonDataThird2, + third2), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(TestAssociation)).Name}", + requestJsonDataSecond2, + second2), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + + // Код для удобства отлавливания исключений. + args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => + { + Exception currentException = exception; + + while (currentException != null) + { + currentException = currentException.InnerException; + } + + return exception; + }; + + // Act. + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + // Assert. + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.Created, HttpStatusCode.OK }); + + string[] thirdPropertiesNames2 = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name), + Information.ExtractPropertyPath(x => x.TestClass), + }; + View thirdLevelDynamicView2 = new View(new ViewAttribute("thirdDynamicView2", thirdPropertiesNames2), typeof(ThirdLevel)); + List thirdLevelList = args.DataService.Query(thirdLevelDynamicView2).Where(x => x.TestClass.__PrimaryKey == second1.__PrimaryKey).ToList(); + Assert.NotNull(thirdLevelList); + Assert.True(thirdLevelList.Any()); + Assert.Equal(2, thirdLevelList.Count); + + TestAssociation checkAssociation = args.DataService.Query(second2DynamicView).FirstOrDefault(x => x.__PrimaryKey == second2.__PrimaryKey); + Assert.NotNull(checkAssociation); + Assert.Equal(OtherName, checkAssociation.Name); + } + }); + } + } +} + diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs index 6f1bfb16..27b8c4c4 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/BusinessServersTest.cs @@ -13,7 +13,12 @@ /// /// Класс тестов для тестирования бизнес-серверов. /// +#if NETFRAMEWORK public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class BusinessServersTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs index a340a454..ccae2f74 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/ChangeMasterInBSTest.cs @@ -11,7 +11,12 @@ /// /// Класс тестов для тестирования изменения мастера при создании детейла. /// +#if NETFRAMEWORK public class ChangeMasterInBSTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ChangeMasterInBSTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -19,7 +24,7 @@ public class ChangeMasterInBSTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ChangeMasterInBSTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ChangeMasterInBSTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs index f1aedc2b..7a364423 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Create/CreateWithPseudoDetailDefinedTest.cs @@ -11,7 +11,12 @@ /// /// Unit-test class for creation entity instance with pseudodetail field defined through OData service. /// +#if NETFRAMEWORK public class CreateWithPseudoDetailDefinedTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class CreateWithPseudoDetailDefinedTest : BaseODataServiceIntegratedTest +#endif { private static PseudoDetailDefinitions GetPseudoDetailDefinitions() { @@ -31,7 +36,7 @@ public CreateWithPseudoDetailDefinedTest() : base(pseudoDetailDefinitions: GetPs } #endif #if NETCOREAPP - public CreateWithPseudoDetailDefinedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output, pseudoDetailDefinitions: GetPseudoDetailDefinitions()) + public CreateWithPseudoDetailDefinedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output, pseudoDetailDefinitions: GetPseudoDetailDefinitions()) { } #endif diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs index 104a1681..cd2d9985 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/GisCRUDTest.cs @@ -21,7 +21,12 @@ /// /// Класс тестов для тестирования работы с гео-данными. /// +#if NETFRAMEWORK public class GisCRUDTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class GisCRUDTest : BaseODataServiceIntegratedTest +#endif { #if NETFRAMEWORK /// @@ -37,7 +42,7 @@ public GisCRUDTest() /// /// Фабрика для приложения. /// Вывод диагностической информации по тестам. - public GisCRUDTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + public GisCRUDTest(CustomWebApplicationFactory factory, ITestOutputHelper output) : base(factory, output, false, true) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs index 238df9a2..6bcbfb42 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/MultiThreadTests.cs @@ -33,7 +33,12 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD /// /// Тесты CRUD операций с множеством пользователей. /// +#if NETFRAMEWORK public class MultiThreadTests : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class MultiThreadTests : BaseODataServiceIntegratedTest +#endif { private const int ThreadCount = 50; @@ -54,7 +59,7 @@ public MultiThreadTests(Xunit.Abstractions.ITestOutputHelper output) /// /// Фабрика для приложения. /// Вывод отладочной информации. - public MultiThreadTests(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public MultiThreadTests(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -337,7 +342,7 @@ private static void RegisterCustomUser(IUnityContainer container) container.RegisterType(); #if NETCOREAPP container.RegisterType(); - #endif +#endif container.RegisterType(); } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs index 70df3068..0922578b 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/BuiltinQueryFunctionsTest.cs @@ -20,7 +20,12 @@ /// /// Класс тестов для тестирования применения $filter в OData-сервисе. /// +#if NETFRAMEWORK public class BuiltinQueryFunctionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BuiltinQueryFunctionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -28,7 +33,7 @@ public class BuiltinQueryFunctionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BuiltinQueryFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BuiltinQueryFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs index 9eabb8aa..1ab78bf1 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/Excel/ExcelExportTest.cs @@ -13,7 +13,12 @@ /// /// A class for testing exports from Excel. /// +#if NETFRAMEWORK public class ExcelExportTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ExcelExportTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class ExcelExportTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ExcelExportTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ExcelExportTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs index 478014a6..4df72efb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterDetailFieldTest.cs @@ -16,7 +16,12 @@ /// /// Unit-test class for filtering data through OData service by master details fields. /// +#if NETFRAMEWORK public class FilterByMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterByMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -24,7 +29,7 @@ public class FilterByMasterDetailFieldTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterByMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -145,42 +150,42 @@ public void TestFilterByDetailMaster() Медведь медведь3 = new Медведь() { ПорядковыйНомер = 3 }; Лес лес1 = new Лес() { Название = "Шишкин" }; - Лес лес2 = new Лес() { Название = "Ёжкин" }; + Лес лес2 = new Лес() { Название = "Ёжкин" }; Лес лес3 = new Лес() { Название = "Пыжкин" }; Берлога берлога1 = new Берлога() { Наименование = "Берлога 1", ЛесРасположения = лес1, Заброшена = true }; Берлога берлога2 = new Берлога() { Наименование = "Берлога 2", ЛесРасположения = лес1, Заброшена = false }; Берлога берлога3 = new Берлога() { Наименование = "Берлога 3", ЛесРасположения = лес2, Заброшена = false }; - Берлога берлога4 = new Берлога() { Наименование = "Берлога 4", ЛесРасположения = лес2, Заброшена = false }; + Берлога берлога4 = new Берлога() { Наименование = "Берлога 4", ЛесРасположения = лес2, Заброшена = false }; Берлога берлога5 = new Берлога() { Наименование = "Берлога 5", ЛесРасположения = лес3, Заброшена = false }; Берлога берлога6 = new Берлога() { Наименование = "Берлога 6", ЛесРасположения = лес3, Заброшена = true}; медведь1.Берлога.AddRange(берлога1, берлога2); - медведь2.Берлога.AddRange(берлога3, берлога4); + медведь2.Берлога.AddRange(берлога3, берлога4); медведь3.Берлога.AddRange(берлога5, берлога6); DataObject[] newDataObjects = new DataObject[] { лес1, лес2, лес3, медведь1, медведь2, берлога1, берлога2, берлога3, берлога4, берлога5, берлога6 }; args.DataService.UpdateObjects(ref newDataObjects); - ExternalLangDef.LanguageDef.DataService = args.DataService; - - // Act. + ExternalLangDef.LanguageDef.DataService = args.DataService; + + // Act. string requestUrl = string.Format( "http://localhost/odata/{0}?$filter={1}", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, "(Медведь/Берлога/any(f:(f/Заброшена eq true)))"); using (var response = args.HttpClient.GetAsync(requestUrl).Result) - { - // Assert. + { + // Assert. string receivedStr = response.Content.ReadAsStringAsync().Result.Beautify(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedStr); Assert.Equal(4, ((JArray)receivedDict["value"]).Count); } }); - } - + } + /// /// Tests filtering data by detail enum field with complex predicate. /// @@ -188,39 +193,39 @@ public void TestFilterByDetailMaster() public void TestFilterByEnumDetailMaster() { ActODataService(args => - { - // Arrange. - Driver driver1 = new Driver { CarCount = 2, Documents = true, Name = "Driver1" }; - Driver driver2 = new Driver { CarCount = 2, Documents = true, Name = "Driver2" }; - Driver driver3 = new Driver { CarCount = 2, Documents = true, Name = "Driver3" }; + { + // Arrange. + Driver driver1 = new Driver { CarCount = 2, Documents = true, Name = "Driver1" }; + Driver driver2 = new Driver { CarCount = 2, Documents = true, Name = "Driver2" }; + Driver driver3 = new Driver { CarCount = 2, Documents = true, Name = "Driver3" }; Car car1d1 = new Car { Model = "ВАЗ", TipCar = tTip.sedan }; Car car2d1 = new Car { Model = "ГАЗ", TipCar = tTip.sedan }; Car car1d2 = new Car { Model = "BMW", TipCar = tTip.crossover }; Car car2d2 = new Car { Model = "Porsche", TipCar = tTip.sedan }; - + Car car1d3 = new Car { Model = "Lamborghini", TipCar = tTip.crossover }; - Car car2d3 = new Car { Model = "Subaru", TipCar = tTip.sedan }; - + Car car2d3 = new Car { Model = "Subaru", TipCar = tTip.sedan }; + driver1.Car.AddRange(car1d1, car2d1); - driver2.Car.AddRange(car1d2, car2d2); + driver2.Car.AddRange(car1d2, car2d2); driver3.Car.AddRange(car1d3, car2d3); DataObject[] newDataObjects = new DataObject[] { driver1, driver2, driver3, car1d1, car2d1, car1d2, car2d2, car1d3, car2d3 }; args.DataService.UpdateObjects(ref newDataObjects); - ExternalLangDef.LanguageDef.DataService = args.DataService; - - // Act. + ExternalLangDef.LanguageDef.DataService = args.DataService; + + // Act. string requestUrl = string.Format( "http://localhost/odata/{0}?$filter={1}", args.Token.Model.GetEdmEntitySet(typeof(Car)).Name, "(Driver/Car/any(f:(f/TipCar eq NewPlatform.Flexberry.ORM.ODataService.Tests.tTip'crossover')))"); using (var response = args.HttpClient.GetAsync(requestUrl).Result) - { - // Assert. + { + // Assert. string receivedStr = response.Content.ReadAsStringAsync().Result.Beautify(); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedStr); diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs index 283adb51..d6ae5e9b 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterFieldTest.cs @@ -15,15 +15,20 @@ /// /// Unit-test class for filtering data through OData service by master fields. /// +#if NETFRAMEWORK public class FilterByMasterFieldTest : BaseODataServiceIntegratedTest - { +#endif +#if NETCOREAPP + public class FilterByMasterFieldTest : BaseODataServiceIntegratedTest +#endif + { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterByMasterFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByMasterFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs index f8298cab..8d983e31 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByMasterMasterDetailFieldTest.cs @@ -16,7 +16,12 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Read /// /// Unit-test class for filtering data through OData service by master master details fields. /// +#if NETFRAMEWORK public class FilterByMasterMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterByMasterMasterDetailFieldTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -24,7 +29,7 @@ public class FilterByMasterMasterDetailFieldTest : BaseODataServiceIntegratedTes /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterByMasterMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByMasterMasterDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs index c9474cff..aa9a6fb4 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterByPseudoDetailFieldTest.cs @@ -16,7 +16,12 @@ /// /// Unit-test class for filtering data through OData service by pseudodetail field. /// +#if NETFRAMEWORK public class FilterByPseudoDetailFieldTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterByPseudoDetailFieldTest : BaseODataServiceIntegratedTest +#endif { private static PseudoDetailDefinitions GetPseudoDetailDefinitions() { @@ -36,7 +41,7 @@ public FilterByPseudoDetailFieldTest() : base(pseudoDetailDefinitions: GetPseudo } #endif #if NETCOREAPP - public FilterByPseudoDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterByPseudoDetailFieldTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output, pseudoDetailDefinitions: GetPseudoDetailDefinitions()) { IUnityContainer container = UnityFactory.GetContainer(); diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs index d6cdbb27..affba784 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/FilterTest.cs @@ -19,7 +19,12 @@ /// /// Класс тестов для тестирования применения $filter в OData-сервисе. /// +#if NETFRAMEWORK public class FilterTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FilterTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -27,7 +32,7 @@ public class FilterTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FilterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FilterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs index 019014ad..8b69831f 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/GetTest.cs @@ -19,7 +19,12 @@ /// /// Класс тестов для проверки корректной обработки Get-запросов. /// +#if NETFRAMEWORK public class GetTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class GetTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -27,15 +32,15 @@ public class GetTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public GetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public GetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } #endif - /// - /// Проверка получения данных для классов, в которых есть нехранимые поля, который не содержат setter'ов. - /// (Такие варианты присутствуют в старом коде). + /// + /// Проверка получения данных для классов, в которых есть нехранимые поля, который не содержат setter'ов. + /// (Такие варианты присутствуют в старом коде). /// [Fact] public void TestGetNotStored() @@ -67,8 +72,8 @@ public void TestGetNotStored() }); } - /// - /// Проверка значение в атрибуте @odata.type. + /// + /// Проверка значение в атрибуте @odata.type. /// [Fact] public void TestGetWithMaster() diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs index e90fbf41..e3c9c00d 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/MetaDataTest.cs @@ -15,7 +15,12 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class MetaDataTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class MetaDataTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -23,7 +28,7 @@ public class MetaDataTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public MetaDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public MetaDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs index ecbed583..14963a4f 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/ReferenceToMasterTest.cs @@ -18,7 +18,12 @@ /// /// Unit-test class for read data with reference to master through OData service. /// +#if NETFRAMEWORK public class ReferenceToMasterTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ReferenceToMasterTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -26,7 +31,7 @@ public class ReferenceToMasterTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ReferenceToMasterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ReferenceToMasterTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs index bc7f74ac..cc3c4ac9 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/SkipTopOrderByTest.cs @@ -20,7 +20,12 @@ /// /// Класс тестов для тестирования $skip, $top, $orderby. /// +#if NETFRAMEWORK public class SkipTopOrderByTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class SkipTopOrderByTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -28,7 +33,7 @@ public class SkipTopOrderByTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public SkipTopOrderByTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + public SkipTopOrderByTest(CustomWebApplicationFactory factory, ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs index 931f1a75..adacb75c 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Read/UtfRequestsTest.cs @@ -18,15 +18,20 @@ /// /// Unit-test class for read data through OData service with using UTF8 requests. /// +#if NETFRAMEWORK public class UtfRequestsTest : BaseODataServiceIntegratedTest - { +#endif +#if NETCOREAPP + public class UtfRequestsTest : BaseODataServiceIntegratedTest +#endif + { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// /// Фабрика для приложения. /// Вывод отладочной информации. - public UtfRequestsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public UtfRequestsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs index c10ae918..847d48e0 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/BusinessServersTest.cs @@ -19,7 +19,12 @@ /// /// Класс тестов для тестирования бизнес-серверов. /// +#if NETFRAMEWORK public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BusinessServersTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -27,7 +32,7 @@ public class BusinessServersTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BusinessServersTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs index 66353bfb..1fd0c9f3 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/ModifyDataTest.cs @@ -1,1778 +1,1930 @@ -namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Net.Http; - using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing; - using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business; - using ICSSoft.STORMNET.Business.LINQProvider; - using ICSSoft.STORMNET.Exceptions; - using ICSSoft.STORMNET.KeyGen; - using ICSSoft.STORMNET.Windows.Forms; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; - - using Newtonsoft.Json; - using Xunit; - - /// - /// Класс тестов для тестирования операций модификации данных OData-сервисом (вставка, обновление, удаление). - /// - public class ModifyDataTest : BaseODataServiceIntegratedTest - { -#if NETCOREAPP - /// - /// Конструктор по-умолчанию. - /// - /// Фабрика для приложения. - /// Вывод отладочной информации. - public ModifyDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) - : base(factory, output) - { - } -#endif - - /// - /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. - /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . - /// Тест проверяет следующие факты: - /// - /// Вставка связи мастерового объекта. - /// Удаление связи мастеровго объекта путём присвоения null свойству. - /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. - /// - /// - [Fact] - public void PatchNavigationPropertiesTest() - { - ActODataService(args => - { - ExternalLangDef.LanguageDef.DataService = args.DataService; - string[] берлогаPropertiesNames = - { - Information.ExtractPropertyPath<Берлога>(x => x.ПолеБС), - Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Берлога>(x => x.Наименование), - Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) - }; - string[] лесPropertiesNames = - { - Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь), - Information.ExtractPropertyPath<Лес>(x => x.Название), - Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) - }; - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Медведь>(x => x.ПолеБС), - Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Медведь>(x => x.Вес), - - // Information.ExtractPropertyPath<Медведь>(x => x.Пол), - Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), - Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) - }; - var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); - var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); - var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); - - // Объекты для тестирования создания. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - Лес лес2 = new Лес { Название = "Березовая роща" }; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; - медв.Берлога.Add(берлога1); - var objs = new DataObject[] { медв, лес1, лес2, берлога1 }; - args.DataService.UpdateObjects(ref objs); - string requestUrl; - - string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - - objJson.Add("ЛесОбитания@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, - ((KeyGuid)лес1.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); - objJson.Add("Медведь", null); - requestJsonDataБерлога = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) - { - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - - requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - objJson.Add("ЛесОбитания@odata.bind", null); - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - objJson.Add("ЛесОбитания", null); - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - objJson.Add("Берлога@odata.bind", null); - requestJsonDataМедв = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах происходит вставка и удаление связей объекта. - /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . - /// Тест проверяет следующие факты: - /// - /// Вставка связи мастерового объекта. - /// Удаление связи мастеровго объекта путём присвоения null свойству. - /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. - /// - /// - [Fact] - public void PostNavigationPropertiesTest() - { - string[] берлогаPropertiesNames = - { - // Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Берлога>(x => x.Наименование), - Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) - }; - string[] лесPropertiesNames = - { - // Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь), - Information.ExtractPropertyPath<Лес>(x => x.Название), - Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) - }; - string[] медвPropertiesNames = - { - // Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Медведь>(x => x.Вес), - - // Information.ExtractPropertyPath<Медведь>(x => x.Пол), - Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), - Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) - }; - var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); - var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); - var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); - - // Объекты для тестирования создания. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - ActODataService(args => - { - string requestUrl; - string receivedJsonЛес1, receivedJsonМедв; - string requestJsonDataЛес1 = лес1.ToJson(лесDynamicView, args.Token.Model); - requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataЛес1).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом (в ней должна вернуться созданная сущность). - receivedJsonЛес1 = response.Content.ReadAsStringAsync().Result.Beautify(); - } - - string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); - DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); - Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedJsonЛес1); - - objJsonМедв.Add("ЛесОбитания@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, - receivedDict["__PrimaryKey"])); - objJsonМедв.Add("Берлога@odata.bind", null); - - requestJsonDataМедв = objJsonМедв.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом (в ней должна вернуться созданная сущность). - receivedJsonМедв = response.Content.ReadAsStringAsync().Result.Beautify(); - } - - var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); - var objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); - receivedDict = JsonConvert.DeserializeObject>(receivedJsonМедв); - objJson.Add("Медведь@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, - receivedDict["__PrimaryKey"])); - requestJsonDataБерлога = objJson.Serialize(); - requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name); - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) - { - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах происходит вставка объекта, - /// зависимые объекты (мастера, детейлы) обрабатываются в зависимости от наличия в БД - вставляются или обновляются. - /// - [Fact] - public void PostComplexObjectTest() - { - ActODataService(args => - { - string[] берлогаPropertiesNames = - { - Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Берлога>(x => x.Наименование), - Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения), - Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения.Название), - }; - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Медведь>(x => x.Вес), - Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания), - Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания.Название), - }; - var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); - var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); - медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); - - // Объекты для тестирования создания. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - Лес лес2 = new Лес { Название = "Березовая роща" }; - медв.ЛесОбитания = лес1; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; - медв.Берлога.Add(берлога1); - медв.Берлога.Add(берлога2); - - string json = медв.ToJson(медвDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; - - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом. - string receivedJsonObjs = response.Content.ReadAsStringAsync().Result.Beautify(); - - // В ответе приходит объект с созданной сущностью. - // Преобразуем полученный объект в словарь. - Dictionary receivedObjs = JsonConvert.DeserializeObject>(receivedJsonObjs); - - // Проверяем созданный объект, вычитав с помощью DataService - DataObject createdObj = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - args.DataService.LoadObject(createdObj); - - Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); - Assert.Equal(((Медведь)createdObj).Вес, (int)(long)receivedObjs["Вес"]); - - // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService - var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; - LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); - lcs.LoadingTypes = new[] { typeof(Лес) }; - DataObject[] dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(2, dobjs.Length); - - lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); - lcs.LoadingTypes = new[] { typeof(Берлога) }; - dobjs = args.DataService.LoadObjects(lcs); - Assert.Equal(2, dobjs.Length); - - // Создание объекта и обновление связанных - // Создаем нового медведя: в его мастере ЛесОбитания - лес1, но в нём изменим Название; в детейлы заберем от первого медведя детейл2, изменив Название в мастере детейла. - // Подготовка тестовых данных в формате OData. - Медведь медвежонок = new Медведь { Вес = 12 }; - var берлога3 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - медвежонок.Берлога.Add(берлога3); - - медв.Берлога.Remove(берлога2); - медвежонок.Берлога.Add(берлога2); - - лес1.Название = лес1.Название + "(обновл)"; - лес2.Название = лес2.Название + "(обновл)"; - - json = медвежонок.ToJson(медвDynamicView, args.Token.Model); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; - - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Проверяем созданный объект, вычитав с помощью DataService - createdObj = new Медведь { __PrimaryKey = медвежонок.__PrimaryKey }; - args.DataService.LoadObject(createdObj); - - Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); - Assert.Equal(12, ((Медведь)createdObj).Вес); - - // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService - ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; - lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); - lcs.LoadingTypes = new[] { typeof(Лес) }; - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), - лес1.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Single(dobjs); - Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); - - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), - лес2.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Single(dobjs); - Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); - - lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); - lcs.LoadingTypes = new[] { typeof(Берлога) }; - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), - медв.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Single(dobjs); - - lcs.LimitFunction = ldef.GetFunction( - ldef.funcEQ, - new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), - медвежонок.__PrimaryKey); - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(2, dobjs.Length); - }); - } - - /// - /// Осуществляет проверку создания сущности с датой и незаданным первичным ключом. - /// - [Fact] - public void PostObjDateTimeNoPKTest() - { - ActODataService(args => - { - // Создаем объект данных. - Лес country = new Лес { Площадь = 10, Название = "Бор", ДатаПоследнегоОсмотра = (ICSSoft.STORMNET.UserDataTypes.NullableDateTime)DateTime.Now }; - - // Преобразуем объект данных в JSON-строку. - string[] contryPropertiesNames = - { - Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь), - Information.ExtractPropertyPath<Лес>(x => x.Название), - Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) - }; - var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Лес)); - - string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. - /// - [Fact] - public void PostDataTimeValueTest() - { - ActODataService(args => - { - // Создаем объект данных. - КлассСМножествомТипов класс = new КлассСМножествомТипов() { PropertyDateTime = DateTime.Now }; - - // Преобразуем объект данных в JSON-строку. - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.PropertyDateTime) - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(КлассСМножествомТипов)); - - string requestJsonData = класс.ToJson(classDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(КлассСМножествомТипов)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. - /// - [Fact] - public void PostSimpleObjectTest() - { - ActODataService(args => - { - // Создаем объект данных. - Страна country = new Страна { Название = "Russia" }; - - // Преобразуем объект данных в JSON-строку. - string[] contryPropertiesNames = - { - Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Страна>(x => x.Название) - }; - var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Страна)); - - string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Страна)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - - // Получим строку с ответом (в ней должна вернуться созданная сущность). - string receivedJsonCountry = response.Content.ReadAsStringAsync().Result.Beautify(); - - // Преобразуем полученный объект в словарь (c приведением типов значений к типам свойств объекта данных). - DataObjectDictionary receivedDictionaryCountry = DataObjectDictionary.Parse(receivedJsonCountry, contryDynamicView, args.Token.Model); - - // Сравним значения полученного и исходного объектов. - Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); - Assert.Equal(country.__PrimaryKey, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); - - Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.Название))); - Assert.Equal(country.Название, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.Название))); - - // Проверяем что объект данных был корректно создан в базе. - Страна createdCountry = new Страна { __PrimaryKey = country.__PrimaryKey }; - args.DataService.LoadObject(contryDynamicView, createdCountry); - - Assert.Equal(country.Название, createdCountry.Название); - } - }); - } - - /// - /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) - /// для простейшего объекта, т.е. мастера и детейлы не заданы и не модифицируются. - /// Объект с изменениями передается JSON-строкой. - /// - [Fact] - public void PatchSimpleObjectTest() - { - ActODataService(args => - { - // Создаем объект данных, который потом будем обновлять, и добавляем в базу обычным сервисом данных. - Лес лес = new Лес { Название = "Чаща", Площадь = 100 }; - args.DataService.UpdateObject(лес); - - // Обновляем часть атрибутов. - лес.Площадь = 150; - - // Представление, по которому будем обновлять. - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лес>(x => x.Площадь) - }; - var лесDynamicView = new View(new ViewAttribute("лесDynamicView", медвPropertiesNames), typeof(Лес)); - - // Преобразуем объект данных в JSON-строку. - string requestJsonData = лес.ToJson(лесDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)лес.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. - Лес updatedЛес = new Лес { __PrimaryKey = лес.__PrimaryKey }; - args.DataService.LoadObject(updatedЛес); - - Assert.Equal(лес.Площадь, updatedЛес.Площадь); - Assert.Equal(лес.Название, updatedЛес.Название); - } - }); - } - - /// - /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) - /// для мастера в детейле. - /// По стандарту сервер OData не должен обрабатывать такой запрос и поэтому вернёт HTTP Код 400. - /// Объект с изменениями передается JSON-строкой. - /// - [Fact] - public void PatchComplexObjectTest() - { - ActODataService(args => - { - // Объекты для тестирования обновления. - Медведь медв = new Медведь { Вес = 48 }; - Лес лес1 = new Лес { Название = "Бор" }; - Лес лес2 = new Лес { Название = "Березовая роща" }; - медв.ЛесОбитания = лес1; - var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; - var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; - медв.Берлога.Add(берлога1); - медв.Берлога.Add(берлога2); - - var objs = new DataObject[] { медв, лес1, лес2, берлога1, берлога2 }; - - args.DataService.UpdateObjects(ref objs); - - // Преобразуем объект данных в JSON-строку. - string[] медвPropertiesNames = - { - Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), - }; - var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); - - string[] берлогаPropertiesNames = - { - Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения), - }; - var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); - - медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); - - Медведь медвДляЗапроса = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - Берлога берлогаДляЗапроса = new Берлога { __PrimaryKey = берлога1.__PrimaryKey, ЛесРасположения = лес2 }; - медвДляЗапроса.Берлога.Add(берлогаДляЗапроса); - - string requestJsonData = медвДляЗапроса.ToJson(медвDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - }); - } - - /// - /// Осуществляет проверку удаления данных. - /// - [Fact] - public void DeleteObjectTest() - { - ActODataService(args => - { - // ------------------ Удаление простого объекта с ключом __PrimaryKey в виде строки ----------------------------- - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - var класс = new КлассСоСтроковымКлючом(); - args.DataService.UpdateObject(класс); - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}('{1}')", args.Token.Model.GetEdmEntitySet(typeof(КлассСоСтроковымКлючом)).Name, класс.__PrimaryKey); - - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - bool exists = true; - КлассСоСтроковымКлючом deletedКлассСоСтроковымКлючом = new КлассСоСтроковымКлючом { __PrimaryKey = класс.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedКлассСоСтроковымКлючом); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - } - - // ------------------ Удаление простого объекта ----------------------------- - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - Медведь медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; - args.DataService.UpdateObject(медв); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - bool exists = true; - Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedМедв); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - } - - // ------------------ Удаление детейла и объекта с детейлами ----------------------------- - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; - медв.Берлога.Add(new Берлога { Наименование = "Берлога для хорошего настроения" }); - медв.Берлога.Add(new Берлога { Наименование = "Берлога для плохого настроения" }); - Берлога delБерлога = new Берлога { Наименование = "Отдельно удаляемая берлога" }; - медв.Берлога.Add(delБерлога); - args.DataService.UpdateObject(медв); - - // Проверяем что до вызова удалений в базе есть все детейлы. - var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; - LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); - lcs.LoadingTypes = new[] { typeof(Берлога) }; - ICSSoft.STORMNET.DataObject[] dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(3, dobjs.Length); - - // Формируем URL запроса к OData-сервису для удаления объекта-детейла (с идентификатором удаляемой сущности). - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)delБерлога.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект-детейл был удален из базы. - bool exists = true; - Берлога deletedБерлога = new Берлога { __PrimaryKey = delБерлога.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedБерлога); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - - // Проверяем что объект-агрегатор остался в базе. - exists = true; - Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedМедв); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.True(exists); - - // Проверяем что детейлов объекта в базе осталось на 1 меньше, чем создавали. - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(2, dobjs.Length); - } - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису для удаления объекта с детейлами и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - bool exists = true; - Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; - try - { - args.DataService.LoadObject(deletedМедв); - } - catch (Exception ex) - { - if (ex is CantFindDataObjectException) - exists = false; - } - - Assert.False(exists); - - // Проверяем что детейлов объекта в базе не осталось. - dobjs = args.DataService.LoadObjects(lcs); - - Assert.Equal(0, dobjs.Length); - } - }); - } - - /// - /// Осуществляет проверку обновления мастера с иерархическими детейлами. - /// Мастер и детейлы заданы и модифицируются. - /// Объект с изменениями передается JSON-строкой. - /// - [Fact] - public void UpdateCicleDeteilTest() - { - ActODataService(args => - { - // Мастер тестирования обновления. - TestMaster testMaster1 = new TestMaster { TestMasterName = "TestMasterName" }; - var objs = new DataObject[] { testMaster1 }; - args.DataService.UpdateObjects(ref objs); - - // Колличество создаваемых детейлов. - int deteilCount = 20; - - // Детейлы тестирования обновления. - TestDetailWithCicle[] testDetailWithCicleArray = new TestDetailWithCicle[deteilCount]; - TestDetailWithCicle testDetailWithCicle = null; - - for (int i = 0; i < deteilCount; i++) - { - if (i == 0) - { - testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName0", TestMaster = testMaster1 }; - } - else - { - testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName" + i.ToString(), TestMaster = testMaster1, Parent = testDetailWithCicle }; - } - - testDetailWithCicleArray[i] = testDetailWithCicle; - objs = new DataObject[] { testDetailWithCicle }; - args.DataService.UpdateObjects(ref objs); - } - - // Обновляем атрибут мастера. - testMaster1.TestMasterName = "TestMasterNameUpdate"; - - // Представление, по которому будем обновлять. - string[] testMasterPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.TestMasterName) - }; - var testMasterDynamicView = new View(new ViewAttribute("testMasterDynamicView", testMasterPropertiesNames), typeof(TestMaster)); - - // Преобразуем объект данных в JSON-строку. - string requestJsonData = testMaster1.ToJson(testMasterDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)testMaster1.__PrimaryKey).Guid.ToString()); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. - TestMaster updatedTestMaster = new TestMaster { __PrimaryKey = testMaster1.__PrimaryKey }; - args.DataService.LoadObject(updatedTestMaster); - - Assert.Equal(testMaster1.TestMasterName, updatedTestMaster.TestMasterName); - } - - // Обновление атрибутов Детейлов. - for (int i = 0; i < deteilCount; i++) - { - testDetailWithCicleArray[i].TestDetailName += "Update"; - } - - // Представление, по которому будем обновлять. - string[] testDetailWithCiclePropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.TestDetailName) - }; - - var testDetailWithCicleDynamicView = new View(new ViewAttribute("testDetailWithCicleDynamicView", testDetailWithCiclePropertiesNames), typeof(TestDetailWithCicle)); - - for (int i = 0; i < deteilCount; i++) - { - // Преобразуем объект данных в JSON-строку. - string requestJsonDatatestDetailWithCicle = testDetailWithCicleArray[i].ToJson(testDetailWithCicleDynamicView, args.Token.Model); - DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDatatestDetailWithCicle, testDetailWithCicleDynamicView, args.Token.Model); - - objJson.Add("TestMaster@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, - ((KeyGuid)testMaster1.__PrimaryKey).Guid.ToString("D"))); - - if (i != 0) - { - objJson.Add("Parent@odata.bind", string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, - ((KeyGuid)testDetailWithCicleArray[i - 1].__PrimaryKey).Guid.ToString("D"))); - } - - requestJsonDatatestDetailWithCicle = objJson.Serialize(); - - // Формируем URL запроса к OData-сервису. - requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, ((KeyGuid)testDetailWithCicleArray[i].__PrimaryKey).Guid.ToString()); - - using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDatatestDetailWithCicle).Result) - { - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. - TestDetailWithCicle updatedTestDetailWithCicle = new TestDetailWithCicle { __PrimaryKey = testDetailWithCicleArray[i].__PrimaryKey }; - args.DataService.LoadObject(updatedTestDetailWithCicle); - - Assert.Equal(testDetailWithCicleArray[i].TestDetailName, updatedTestDetailWithCicle.TestDetailName); - } - } - }); - } - - /// - /// Test save details with inheritance. - /// - [Fact] - public void SaveDetailWithInheritanceTest() - { - ActODataService(args => - { - var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; - var детейл = new ДетейлНаследник() { prop1 = 1 }; - базовыйКласс.Детейл.Add(детейл); - - args.DataService.UpdateObject(базовыйКласс); - int newValue = 2; - детейл.prop1 = newValue; - - const string baseUrl = "http://localhost/odata"; - - string detJson = детейл.ToJson(ДетейлНаследник.Views.ДетейлНаследникE, args.Token.Model); - detJson = detJson.Replace(nameof(ДетейлНаследник.БазовыйКласс), $"{nameof(ДетейлНаследник.БазовыйКласс)}@odata.bind"); - detJson = detJson.Replace("{\"__PrimaryKey\":\"", $"\"{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}("); - detJson = detJson.Replace("\"}", ")\""); - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", - "{}", - базовыйКласс), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ДетейлНаследник)).Name}", - detJson, - детейл), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - args.DataService.LoadObject(БазовыйКласс.Views.БазовыйКлассE, базовыйКласс); - - var детейлы = базовыйКласс.Детейл.Cast<ДетейлНаследник>(); - - Assert.Equal(1, детейлы.Count()); - Assert.Equal(newValue, детейлы.First().prop1); - } - }); - } - - /// - /// Test update details with Aggregator. - /// - [Fact] - public void UpdateDetailWithAggregatorTest() - { - ActODataService(args => - { - string[] лапаPropertiesNames = - { - Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лапа>(x => x.Размер), - }; - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; - var лапа = new Лапа() { Размер = 50 }; - кошка.Лапа.Add(лапа); - - args.DataService.UpdateObject(кошка); - - кошка.Кличка = "100"; - кошка.Тип = ТипКошки.Дикая; - лапа.Размер = 100; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", - кошка.ToJson(кошкаDynamicView, args.Token.Model), - кошка), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа, - лапа), - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); - - args.DataService.LoadObject(кошкаDynamicView, кошка); - - var лапы = кошка.Лапа.Cast<Лапа>(); - - Assert.Equal("100", кошка.Кличка); - Assert.Equal(ТипКошки.Дикая, кошка.Тип); - Assert.Equal(1, лапы.Count(б => б.Размер == 100)); - } - }); - } - - /// - /// Test update details with Aggregator. - /// - [Fact] - public void UpdateSecondDetailWithAggregatorTest() - { - ActODataService(args => - { - // Arrange. - DateTime date = new DateTime(2010, 10, 10, 10, 10, 10, DateTimeKind.Local); - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода }; - var лапа = new Лапа() { Размер = 50 }; - кошка.Лапа.Add(лапа); - var перелом = new Перелом() { Дата = DateTime.UtcNow, Тип = ТипПерелома.Открытый }; - лапа.Перелом.Add(перелом); - - args.DataService.UpdateObject(кошка); - - string[] переломPropertiesNames = - { - Information.ExtractPropertyPath<Перелом>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Перелом>(x => x.Дата), - }; - var переломDynamicView = new View(new ViewAttribute("переломDynamicView", переломPropertiesNames), typeof(Перелом)); - - string[] лапаPropertiesNames = - { - Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лапа>(x => x.Размер), - Information.ExtractPropertyPath<Лапа>(x => x.РазмерСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - лапа.Размер = 100; - перелом.Дата = date; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - string requestJsonDataПерелом = перелом.ToJson(переломDynamicView, args.Token.Model); - DataObjectDictionary objJsonПерелом = DataObjectDictionary.Parse(requestJsonDataПерелом, переломDynamicView, args.Token.Model); - - objJsonПерелом.Add( - $"{nameof(Перелом.Лапа)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name, - ((KeyGuid)лапа.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataПерелом = objJsonПерелом.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа, - лапа), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Перелом)).Name}", - requestJsonDataПерелом, - перелом), - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); - лапаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Лапа>(x => x.Перелом), переломDynamicView, true); - - args.DataService.LoadObject(кошкаDynamicView, кошка); - - var лапы = кошка.Лапа.Cast<Лапа>(); - - var переломы = лапы.FirstOrDefault().Перелом.Cast<Перелом>(); - - Assert.Equal("50", кошка.Кличка); - Assert.Equal(1, лапы.Count(б => б.Размер == 100)); - Assert.Equal(1, переломы.Count(б => б.Дата == date.ToUniversalTime())); - } - }); - } - - /// - /// Test delete and add detail. - /// - [Fact] - public void UpdateDeletedAndAddedDetailWithAggregatorTest() - { - ActODataService(args => - { - string[] лапаPropertiesNames = - { - Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лапа>(x => x.Размер), - }; - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; - var лапа = new Лапа() { Размер = 50 }; - var лапа2 = new Лапа() { Размер = 1000 }; - var лапа3 = new Лапа() { Размер = 2000 }; - кошка.Лапа.Add(лапа); - кошка.Лапа.Add(лапа2); - - args.DataService.UpdateObject(кошка); - - кошка.Кличка = "100"; - кошка.Тип = ТипКошки.Дикая; - лапа.Размер = 100; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - лапа2.SetStatus(ObjectStatus.Deleted); - - string requestJsonDataЛапа3 = лапа3.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа3 = DataObjectDictionary.Parse(requestJsonDataЛапа3, лапаDynamicView, args.Token.Model); - - objJsonЛапа3.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа3 = objJsonЛапа3.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - string.Empty, - лапа2), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа3, - лапа3), - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.NoContent, HttpStatusCode.Created }); - - кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); - - args.DataService.LoadObject(кошкаDynamicView, кошка); - - var лапы = кошка.Лапа.Cast<Лапа>(); - - Assert.Equal(2, лапы.Count()); - Assert.Equal(1, лапы.Count(x => x.Размер == 2000)); - Assert.Equal(0, лапы.Count(x => x.Размер == 1000)); - } - }); - } - - /// - /// Test batch update error handling when business server throws exception. - /// - [Fact] - public void BatchUpdateErrorHandlingTest() - { - ActODataService(args => - { - string[] лапаPropertiesNames = - { - Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Лапа>(x => x.Размер), - }; - string[] кошкаPropertiesNames = - { - Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Кошка>(x => x.Кличка), - Information.ExtractPropertyPath<Кошка>(x => x.Тип), - Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), - }; - var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); - var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); - - var порода = new Порода() { Название = "Первая" }; - var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; - var лапа = new Лапа() { Размер = 50 }; - кошка.Лапа.Add(лапа); - - args.DataService.UpdateObject(кошка); - - кошка.Кличка = "100"; - кошка.Тип = ТипКошки.Дикая; - - // Этот размер лапы указан в CatsBS как недопустимый размер, будет сгенерировано исключение. - лапа.Размер = 100899; - - const string baseUrl = "http://localhost/odata"; - - string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); - DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); - - objJsonЛапа.Add( - $"{nameof(Лапа.Кошка)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, - ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); - - requestJsonDataЛапа = objJsonЛапа.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", - кошка.ToJson(кошкаDynamicView, args.Token.Model), - кошка), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", - requestJsonDataЛапа, - лапа), - }; - - int exceptionHandled = 0; - - args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => - { - Exception currentException = exception; - - while (currentException != null) - { - if (currentException.Message == "Недопустимый размер кошачьей лапы!") - { - exceptionHandled++; - } - - currentException = currentException.InnerException; - } - - return exception; - }; - - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); - Assert.Equal(1, exceptionHandled); - } - }); - } - - /// - /// Test update agregator with inheritance details. - /// - [Fact] - public void UpdateAgregatorWithInheritanceDetailsTest() - { - ActODataService(args => - { - var son = new Son() { Name = "Yakov", SuspendersColor = "Brown" }; - var daughter = new Daughter() { Name = "Yana", DressColor = "Red" }; - var person = new Person() { Name = "Yan" }; - person.Childrens.AddRange(son, daughter); - - args.DataService.UpdateObject(person); - - person.Name = "Yan Yakovlevich"; - - // Преобразуем объект данных в JSON-строку. - string[] personPropertiesNames = - { - Information.ExtractPropertyPath(x => x.__PrimaryKey), - Information.ExtractPropertyPath(x => x.Name) - }; - - var personDynamicView = new View(new ViewAttribute("personDynamicView", personPropertiesNames), typeof(Person)); - - string requestJsonData = person.ToJson(personDynamicView, args.Token.Model); - - // Формируем URL запроса к OData-сервису. - string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Person)).Name); - - // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. - using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) - { - // Убедимся, что запрос завершился успешно. - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - }); - } - - /// - /// Test batch update detail of detail. - /// - [Fact] - public void UpdateDetailOfDetailTest() - { - ActODataService(args => - { - var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; - var детейл = new Детейл() { prop1 = 1 }; - базовыйКласс.Детейл.Add(детейл); - var детейл2 = new Детейл2() { prop2 = "2" }; - детейл.Детейл2.Add(детейл2); - - args.DataService.UpdateObject(базовыйКласс); - string newValue = "new"; - базовыйКласс.Свойство1 = newValue; - детейл2.prop2 = newValue; - - const string baseUrl = "http://localhost/odata"; - - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), - }; - - string[] detail2PropertiesNames = - { - Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Детейл2>(x => x.prop2), - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); - var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); - - string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); - string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); - - DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); - - objJson.Add( - $"{nameof(Детейл2.Детейл)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, - ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); - - detJson = objJson.Serialize(); - - detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", - classJson, - базовыйКласс), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", - detJson, - детейл2), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - args.DataService.LoadObject(detail2DynamicView, детейл2); - - Assert.Equal(newValue, детейл2.prop2); - } - }); - } - - /// - /// Test batch update detail of detail. - /// - [Fact] - public void UpdateDetailOfDetailDescOrderTest() - { - ActODataService(args => - { - var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; - var детейл = new Детейл() { prop1 = 1 }; - базовыйКласс.Детейл.Add(детейл); - var детейл2 = new Детейл2() { prop2 = "2" }; - детейл.Детейл2.Add(детейл2); - - args.DataService.UpdateObject(базовыйКласс); - string newValue = "new"; - базовыйКласс.Свойство1 = newValue; - детейл2.prop2 = newValue; - - const string baseUrl = "http://localhost/odata"; - - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), - }; - - string[] detail2PropertiesNames = - { - Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Детейл2>(x => x.prop2), - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); - var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); - - string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); - string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); - - DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); - - objJson.Add( - $"{nameof(Детейл2.Детейл)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, - ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); - - detJson = objJson.Serialize(); - - detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", - detJson, - детейл2), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", - classJson, - базовыйКласс), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); - - args.DataService.LoadObject(detail2DynamicView, детейл2); - - Assert.Equal(newValue, детейл2.prop2); - } - }); - } - - /// - /// Test batch update detail and another type detail. - /// - [Fact] - public void UpdateDetailAndDetailTest() - { - ActODataService(args => - { - var базовыйКласс = new Библиотека() { Адрес = "ул. Пушкина" }; - var мастер = new Автор() { Имя = "Александр" }; - var детейл1 = new Книга() { Название = "Му-Му", Автор1 = мастер }; - базовыйКласс.Книга.Add(детейл1); - var детейл2 = new Журнал() { Номер = 2, Автор2 = мастер }; - базовыйКласс.Журнал.Add(детейл2); - - args.DataService.UpdateObject(базовыйКласс); - string newValue = "ул. Лермонтова"; - int newIntValue = 3; - базовыйКласс.Адрес = newValue; - детейл1.Название = newValue; - детейл2.Номер = newIntValue; - - const string baseUrl = "http://localhost/odata"; - - string[] classPropertiesNames = - { - Information.ExtractPropertyPath<Библиотека>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Библиотека>(x => x.Адрес), - }; - - string[] detail1PropertiesNames = - { - Information.ExtractPropertyPath<Книга>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Книга>(x => x.Название), - }; - - string[] detail2PropertiesNames = - { - Information.ExtractPropertyPath<Журнал>(x => x.__PrimaryKey), - Information.ExtractPropertyPath<Журнал>(x => x.Номер), - }; - - var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(Библиотека)); - var detail1DynamicView = new View(new ViewAttribute("detailDynamicView", detail1PropertiesNames), typeof(Книга)); - var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Журнал)); - - string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); - string det1Json = детейл1.ToJson(detail1DynamicView, args.Token.Model); - string det2Json = детейл2.ToJson(detail2DynamicView, args.Token.Model); - - DataObjectDictionary obj1Json = DataObjectDictionary.Parse(det1Json, detail1DynamicView, args.Token.Model); - - obj1Json.Add( - $"{nameof(Книга.Библиотека1)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, - ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); - - det1Json = obj1Json.Serialize(); - - DataObjectDictionary obj2Json = DataObjectDictionary.Parse(det2Json, detail2DynamicView, args.Token.Model); - - obj2Json.Add( - $"{nameof(Журнал.Библиотека2)}@odata.bind", - string.Format( - "{0}({1})", - args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, - ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); - - det2Json = obj2Json.Serialize(); - - string[] changesets = new[] - { - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name}", - classJson, - базовыйКласс), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Книга)).Name}", - det1Json, - детейл1), - CreateChangeset( - $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Журнал)).Name}", - det2Json, - детейл2), - }; - HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); - using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) - { - CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK }); - - classDynamicView.AddDetailInView(nameof(Библиотека.Книга), detail1DynamicView, true); - classDynamicView.AddDetailInView(nameof(Библиотека.Журнал), detail2DynamicView, true); - - args.DataService.LoadObject(classDynamicView, базовыйКласс); - - Assert.Equal(newValue, базовыйКласс.Адрес = newValue); - Assert.Equal(newValue, базовыйКласс.Книга[0].Название); - Assert.Equal(newIntValue, базовыйКласс.Журнал[0].Номер); - - } - }); - } - - /// - /// Осуществляет проверку удаления данных. - /// - [Fact] - public void DeletePlainObjectTest() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - Медведь agregator = new Медведь() { МедведьСтрокой = "Agregator" }; - args.DataService.UpdateObject(agregator); - - View view = new View(typeof(Медведь), View.ReadType.OnlyThatObject); - Медведь foundAgregator0 = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - //Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - Медведь foundAgregator = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - - /// - /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но при этом пустые. - /// - [Fact] - public void DeleteObjectWithSameDetailAndMasterEmptyTest() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; - args.DataService.UpdateObject(agregator); - - View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); - AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - - /// - /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но лишь детейл заполнен. - /// - [Fact] - public void DeleteObjectWithSameDetailAndMasterTest() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; - args.DataService.UpdateObject(agregator); - - DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; - agregator.Details.Add(dm); - args.DataService.UpdateObject(dm); - - AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; - args.DataService.UpdateObject(agregator2); - DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; - agregator2.Details.Add(dm2); - args.DataService.UpdateObject(dm2); - - View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); - AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - - /// - /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но не пустые. - /// - [Fact] - public void DeleteObjectWithSameDetailAndMaster2Test() - { - ActODataService(args => - { - // Arrange. - // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. - AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; - args.DataService.UpdateObject(agregator); - - DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; - agregator.Details.Add(dm); - args.DataService.UpdateObject(dm); - - AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; - args.DataService.UpdateObject(agregator2); - DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; - agregator2.Details.Add(dm2); - args.DataService.UpdateObject(dm2); - - agregator.Master = dm2; - args.DataService.UpdateObject(agregator); - - View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); - AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.NotNull(foundAgregator0); - - // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). - string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); - requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); - - // Act. - // Обращаемся к OData-сервису и обрабатываем ответ. - using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) - { - // Assert. - // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - - // Проверяем что объект данных был удален из базы. - AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); - Assert.Null(foundAgregator); - } - }); - } - } -} +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing; + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.Business; + using ICSSoft.STORMNET.Business.LINQProvider; + using ICSSoft.STORMNET.Exceptions; + using ICSSoft.STORMNET.KeyGen; + using ICSSoft.STORMNET.Windows.Forms; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; + + using Newtonsoft.Json; + using Xunit; + + /// + /// Класс тестов для тестирования операций модификации данных OData-сервисом (вставка, обновление, удаление). + /// +#if NETFRAMEWORK + public class ModifyDataTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ModifyDataTest : BaseODataServiceIntegratedTest +#endif + { +#if NETCOREAPP + /// + /// Конструктор по-умолчанию. + /// + /// Фабрика для приложения. + /// Вывод отладочной информации. + public ModifyDataTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + : base(factory, output) + { + } +#endif + + /// + /// Осуществляет проверку того, что при PATCH запросах происходит вставка и удаление связей объекта. + /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . + /// Тест проверяет следующие факты: + /// + /// Вставка связи мастерового объекта. + /// Удаление связи мастеровго объекта путём присвоения null свойству. + /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. + /// + /// + [Fact] + public void PatchNavigationPropertiesTest() + { + ActODataService(args => + { + ExternalLangDef.LanguageDef.DataService = args.DataService; + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.ПолеБС), + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Берлога>(x => x.Наименование), + Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) + }; + string[] лесPropertiesNames = + { + Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь), + Information.ExtractPropertyPath<Лес>(x => x.Название), + Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) + }; + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Медведь>(x => x.ПолеБС), + Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Медведь>(x => x.Вес), + + // Information.ExtractPropertyPath<Медведь>(x => x.Пол), + Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), + Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); + var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); + + // Объекты для тестирования создания. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + Лес лес2 = new Лес { Название = "Березовая роща" }; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; + медв.Берлога.Add(берлога1); + var objs = new DataObject[] { медв, лес1, лес2, берлога1 }; + args.DataService.UpdateObjects(ref objs); + string requestUrl; + + string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + + objJson.Add("ЛесОбитания@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, + ((KeyGuid)лес1.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); + objJson.Add("Медведь", null); + requestJsonDataБерлога = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) + { + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + objJson.Add("ЛесОбитания@odata.bind", null); + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + objJson.Add("ЛесОбитания", null); + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + } + + requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + objJson = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + objJson.Add("Берлога@odata.bind", null); + requestJsonDataМедв = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах происходит вставка и удаление связей объекта. + /// Зависимые объекты (мастера, детейлы) представлены в виде - Имя_Связи@odata.bind: Имя_Набора_Сущностей(ключ) или Имя_Связи@odata.bind: [ Имя_Набора_Сущностей(ключ) ] . + /// Тест проверяет следующие факты: + /// + /// Вставка связи мастерового объекта. + /// Удаление связи мастеровго объекта путём присвоения null свойству. + /// Удаление связи мастеровго объекта путём присвоения null для Имя_Связи@odata.bind. + /// + /// + [Fact] + public void PostNavigationPropertiesTest() + { + string[] берлогаPropertiesNames = + { + // Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Берлога>(x => x.Наименование), + Information.ExtractPropertyPath<Берлога>(x => x.Заброшена) + }; + string[] лесPropertiesNames = + { + // Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь), + Information.ExtractPropertyPath<Лес>(x => x.Название), + Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) + }; + string[] медвPropertiesNames = + { + // Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Медведь>(x => x.Вес), + + // Information.ExtractPropertyPath<Медведь>(x => x.Пол), + Information.ExtractPropertyPath<Медведь>(x => x.ДатаРождения), + Information.ExtractPropertyPath<Медведь>(x => x.ПорядковыйНомер) + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); + var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); + + // Объекты для тестирования создания. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + ActODataService(args => + { + string requestUrl; + string receivedJsonЛес1, receivedJsonМедв; + string requestJsonDataЛес1 = лес1.ToJson(лесDynamicView, args.Token.Model); + requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataЛес1).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом (в ней должна вернуться созданная сущность). + receivedJsonЛес1 = response.Content.ReadAsStringAsync().Result.Beautify(); + } + + string requestJsonDataМедв = медв.ToJson(медвDynamicView, args.Token.Model); + DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonDataМедв, медвDynamicView, args.Token.Model); + Dictionary receivedDict = JsonConvert.DeserializeObject>(receivedJsonЛес1); + + objJsonМедв.Add("ЛесОбитания@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, + receivedDict["__PrimaryKey"])); + objJsonМедв.Add("Берлога@odata.bind", null); + + requestJsonDataМедв = objJsonМедв.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataМедв).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом (в ней должна вернуться созданная сущность). + receivedJsonМедв = response.Content.ReadAsStringAsync().Result.Beautify(); + } + + var requestJsonDataБерлога = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + var objJson = DataObjectDictionary.Parse(requestJsonDataБерлога, берлогаDynamicView, args.Token.Model); + receivedDict = JsonConvert.DeserializeObject>(receivedJsonМедв); + objJson.Add("Медведь@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, + receivedDict["__PrimaryKey"])); + requestJsonDataБерлога = objJson.Serialize(); + requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name); + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonDataБерлога).Result) + { + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах происходит вставка объекта, + /// зависимые объекты (мастера, детейлы) обрабатываются в зависимости от наличия в БД - вставляются или обновляются. + /// + [Fact] + public void PostComplexObjectTest() + { + ActODataService(args => + { + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Берлога>(x => x.Наименование), + Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения), + Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения.Название), + }; + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Медведь>(x => x.Вес), + Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания), + Information.ExtractPropertyPath<Медведь>(x => x.ЛесОбитания.Название), + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); + медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); + + // Объекты для тестирования создания. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + Лес лес2 = new Лес { Название = "Березовая роща" }; + медв.ЛесОбитания = лес1; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; + медв.Берлога.Add(берлога1); + медв.Берлога.Add(берлога2); + + string json = медв.ToJson(медвDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; + + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом. + string receivedJsonObjs = response.Content.ReadAsStringAsync().Result.Beautify(); + + // В ответе приходит объект с созданной сущностью. + // Преобразуем полученный объект в словарь. + Dictionary receivedObjs = JsonConvert.DeserializeObject>(receivedJsonObjs); + + // Проверяем созданный объект, вычитав с помощью DataService + DataObject createdObj = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + args.DataService.LoadObject(createdObj); + + Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); + Assert.Equal(((Медведь)createdObj).Вес, (int)(long)receivedObjs["Вес"]); + + // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService + var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; + LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); + lcs.LoadingTypes = new[] { typeof(Лес) }; + DataObject[] dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(2, dobjs.Length); + + lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); + lcs.LoadingTypes = new[] { typeof(Берлога) }; + dobjs = args.DataService.LoadObjects(lcs); + Assert.Equal(2, dobjs.Length); + + // Создание объекта и обновление связанных + // Создаем нового медведя: в его мастере ЛесОбитания - лес1, но в нём изменим Название; в детейлы заберем от первого медведя детейл2, изменив Название в мастере детейла. + // Подготовка тестовых данных в формате OData. + Медведь медвежонок = new Медведь { Вес = 12 }; + var берлога3 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + медвежонок.Берлога.Add(берлога3); + + медв.Берлога.Remove(берлога2); + медвежонок.Берлога.Add(берлога2); + + лес1.Название = лес1.Название + "(обновл)"; + лес2.Название = лес2.Название + "(обновл)"; + + json = медвежонок.ToJson(медвDynamicView, args.Token.Model); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + response = args.HttpClient.PostAsJsonStringAsync(requestUrl, json).Result; + + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Проверяем созданный объект, вычитав с помощью DataService + createdObj = new Медведь { __PrimaryKey = медвежонок.__PrimaryKey }; + args.DataService.LoadObject(createdObj); + + Assert.Equal(ObjectStatus.UnAltered, createdObj.GetStatus()); + Assert.Equal(12, ((Медведь)createdObj).Вес); + + // Проверяем что созданы все зависимые объекты, вычитав с помощью DataService + ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; + lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Лес), Лес.Views.ЛесE); + lcs.LoadingTypes = new[] { typeof(Лес) }; + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), + лес1.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Single(dobjs); + Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); + + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.StormMainObjectKey), + лес2.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Single(dobjs); + Assert.EndsWith("(обновл)", ((Лес)dobjs[0]).Название); + + lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); + lcs.LoadingTypes = new[] { typeof(Берлога) }; + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), + медв.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Single(dobjs); + + lcs.LimitFunction = ldef.GetFunction( + ldef.funcEQ, + new ICSSoft.STORMNET.FunctionalLanguage.VariableDef(ldef.GuidType, "Медведь"), + медвежонок.__PrimaryKey); + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(2, dobjs.Length); + }); + } + + /// + /// Осуществляет проверку создания сущности с датой и незаданным первичным ключом. + /// + [Fact] + public void PostObjDateTimeNoPKTest() + { + ActODataService(args => + { + // Создаем объект данных. + Лес country = new Лес { Площадь = 10, Название = "Бор", ДатаПоследнегоОсмотра = (ICSSoft.STORMNET.UserDataTypes.NullableDateTime)DateTime.Now }; + + // Преобразуем объект данных в JSON-строку. + string[] contryPropertiesNames = + { + Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь), + Information.ExtractPropertyPath<Лес>(x => x.Название), + Information.ExtractPropertyPath<Лес>(x => x.ДатаПоследнегоОсмотра) + }; + var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Лес)); + + string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. + /// + [Fact] + public void PostDataTimeValueTest() + { + ActODataService(args => + { + // Создаем объект данных. + КлассСМножествомТипов класс = new КлассСМножествомТипов() { PropertyDateTime = DateTime.Now }; + + // Преобразуем объект данных в JSON-строку. + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<КлассСМножествомТипов>(x => x.PropertyDateTime) + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(КлассСМножествомТипов)); + + string requestJsonData = класс.ToJson(classDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(КлассСМножествомТипов)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку того, что при POST запросах, отправляющих простейшие объекты JSON-строкой, происходит корректная вставка. + /// + [Fact] + public void PostSimpleObjectTest() + { + ActODataService(args => + { + // Создаем объект данных. + Страна country = new Страна { Название = "Russia" }; + + // Преобразуем объект данных в JSON-строку. + string[] contryPropertiesNames = + { + Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Страна>(x => x.Название) + }; + var contryDynamicView = new View(new ViewAttribute("ContryDynamicView", contryPropertiesNames), typeof(Страна)); + + string requestJsonData = country.ToJson(contryDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Страна)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Получим строку с ответом (в ней должна вернуться созданная сущность). + string receivedJsonCountry = response.Content.ReadAsStringAsync().Result.Beautify(); + + // Преобразуем полученный объект в словарь (c приведением типов значений к типам свойств объекта данных). + DataObjectDictionary receivedDictionaryCountry = DataObjectDictionary.Parse(receivedJsonCountry, contryDynamicView, args.Token.Model); + + // Сравним значения полученного и исходного объектов. + Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); + Assert.Equal(country.__PrimaryKey, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.__PrimaryKey))); + + Assert.True(receivedDictionaryCountry.HasProperty(Information.ExtractPropertyPath<Страна>(x => x.Название))); + Assert.Equal(country.Название, receivedDictionaryCountry.GetPropertyValue(Information.ExtractPropertyPath<Страна>(x => x.Название))); + + // Проверяем что объект данных был корректно создан в базе. + Страна createdCountry = new Страна { __PrimaryKey = country.__PrimaryKey }; + args.DataService.LoadObject(contryDynamicView, createdCountry); + + Assert.Equal(country.Название, createdCountry.Название); + } + }); + } + + /// + /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) + /// для простейшего объекта, т.е. мастера и детейлы не заданы и не модифицируются. + /// Объект с изменениями передается JSON-строкой. + /// + [Fact] + public void PatchSimpleObjectTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем обновлять, и добавляем в базу обычным сервисом данных. + Лес лес = new Лес { Название = "Чаща", Площадь = 100 }; + args.DataService.UpdateObject(лес); + + // Обновляем часть атрибутов. + лес.Площадь = 150; + + // Представление, по которому будем обновлять. + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лес>(x => x.Площадь) + }; + var лесDynamicView = new View(new ViewAttribute("лесDynamicView", медвPropertiesNames), typeof(Лес)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = лес.ToJson(лесDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Лес)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)лес.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + Лес updatedЛес = new Лес { __PrimaryKey = лес.__PrimaryKey }; + args.DataService.LoadObject(updatedЛес); + + Assert.Equal(лес.Площадь, updatedЛес.Площадь); + Assert.Equal(лес.Название, updatedЛес.Название); + } + }); + } + + /// + /// Осуществляет проверку частичного обновления данных (передаются только значения модифицированных атрибутов) + /// для мастера в детейле. + /// По стандарту сервер OData не должен обрабатывать такой запрос и поэтому вернёт HTTP Код 400. + /// Объект с изменениями передается JSON-строкой. + /// + [Fact] + public void PatchComplexObjectTest() + { + ActODataService(args => + { + // Объекты для тестирования обновления. + Медведь медв = new Медведь { Вес = 48 }; + Лес лес1 = new Лес { Название = "Бор" }; + Лес лес2 = new Лес { Название = "Березовая роща" }; + медв.ЛесОбитания = лес1; + var берлога1 = new Берлога { Наименование = "Для хорошего настроения", ЛесРасположения = лес1 }; + var берлога2 = new Берлога { Наименование = "Для плохого настроения", ЛесРасположения = лес2 }; + медв.Берлога.Add(берлога1); + медв.Берлога.Add(берлога2); + + var objs = new DataObject[] { медв, лес1, лес2, берлога1, берлога2 }; + + args.DataService.UpdateObjects(ref objs); + + // Преобразуем объект данных в JSON-строку. + string[] медвPropertiesNames = + { + Information.ExtractPropertyPath<Медведь>(x => x.__PrimaryKey), + }; + var медвDynamicView = new View(new ViewAttribute("медвDynamicView", медвPropertiesNames), typeof(Медведь)); + + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Берлога>(x => x.ЛесРасположения), + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + + медвDynamicView.AddDetailInView(Information.ExtractPropertyPath<Медведь>(x => x.Берлога), берлогаDynamicView, true); + + Медведь медвДляЗапроса = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + Берлога берлогаДляЗапроса = new Берлога { __PrimaryKey = берлога1.__PrimaryKey, ЛесРасположения = лес2 }; + медвДляЗапроса.Берлога.Add(берлогаДляЗапроса); + + string requestJsonData = медвДляЗапроса.ToJson(медвDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + }); + } + + /// + /// Осуществляет проверку удаления данных. + /// + [Fact] + public void DeleteObjectTest() + { + ActODataService(args => + { + // ------------------ Удаление простого объекта с ключом __PrimaryKey в виде строки ----------------------------- + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + var класс = new КлассСоСтроковымКлючом(); + args.DataService.UpdateObject(класс); + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}('{1}')", args.Token.Model.GetEdmEntitySet(typeof(КлассСоСтроковымКлючом)).Name, класс.__PrimaryKey); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + bool exists = true; + КлассСоСтроковымКлючом deletedКлассСоСтроковымКлючом = new КлассСоСтроковымКлючом { __PrimaryKey = класс.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedКлассСоСтроковымКлючом); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + } + + // ------------------ Удаление простого объекта ----------------------------- + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + Медведь медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; + args.DataService.UpdateObject(медв); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + bool exists = true; + Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedМедв); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + } + + // ------------------ Удаление детейла и объекта с детейлами ----------------------------- + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + медв = new Медведь { Пол = tПол.Мужской, Вес = 80, ПорядковыйНомер = 1 }; + медв.Берлога.Add(new Берлога { Наименование = "Берлога для хорошего настроения" }); + медв.Берлога.Add(new Берлога { Наименование = "Берлога для плохого настроения" }); + Берлога delБерлога = new Берлога { Наименование = "Отдельно удаляемая берлога" }; + медв.Берлога.Add(delБерлога); + args.DataService.UpdateObject(медв); + + // Проверяем что до вызова удалений в базе есть все детейлы. + var ldef = ICSSoft.STORMNET.FunctionalLanguage.SQLWhere.SQLWhereLanguageDef.LanguageDef; + LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(Берлога), Берлога.Views.БерлогаE); + lcs.LoadingTypes = new[] { typeof(Берлога) }; + ICSSoft.STORMNET.DataObject[] dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(3, dobjs.Length); + + // Формируем URL запроса к OData-сервису для удаления объекта-детейла (с идентификатором удаляемой сущности). + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Берлога)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)delБерлога.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект-детейл был удален из базы. + bool exists = true; + Берлога deletedБерлога = new Берлога { __PrimaryKey = delБерлога.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedБерлога); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + + // Проверяем что объект-агрегатор остался в базе. + exists = true; + Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedМедв); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.True(exists); + + // Проверяем что детейлов объекта в базе осталось на 1 меньше, чем создавали. + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(2, dobjs.Length); + } + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)медв.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису для удаления объекта с детейлами и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + bool exists = true; + Медведь deletedМедв = new Медведь { __PrimaryKey = медв.__PrimaryKey }; + try + { + args.DataService.LoadObject(deletedМедв); + } + catch (Exception ex) + { + if (ex is CantFindDataObjectException) + exists = false; + } + + Assert.False(exists); + + // Проверяем что детейлов объекта в базе не осталось. + dobjs = args.DataService.LoadObjects(lcs); + + Assert.Equal(0, dobjs.Length); + } + }); + } + + /// + /// Осуществляет проверку обновления мастера с иерархическими детейлами. + /// Мастер и детейлы заданы и модифицируются. + /// Объект с изменениями передается JSON-строкой. + /// + [Fact] + public void UpdateCicleDeteilTest() + { + ActODataService(args => + { + // Мастер тестирования обновления. + TestMaster testMaster1 = new TestMaster { TestMasterName = "TestMasterName" }; + var objs = new DataObject[] { testMaster1 }; + args.DataService.UpdateObjects(ref objs); + + // Колличество создаваемых детейлов. + int deteilCount = 20; + + // Детейлы тестирования обновления. + TestDetailWithCicle[] testDetailWithCicleArray = new TestDetailWithCicle[deteilCount]; + TestDetailWithCicle testDetailWithCicle = null; + + for (int i = 0; i < deteilCount; i++) + { + if (i == 0) + { + testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName0", TestMaster = testMaster1 }; + } + else + { + testDetailWithCicle = new TestDetailWithCicle { TestDetailName = "TestDeteilName" + i.ToString(), TestMaster = testMaster1, Parent = testDetailWithCicle }; + } + + testDetailWithCicleArray[i] = testDetailWithCicle; + objs = new DataObject[] { testDetailWithCicle }; + args.DataService.UpdateObjects(ref objs); + } + + // Обновляем атрибут мастера. + testMaster1.TestMasterName = "TestMasterNameUpdate"; + + // Представление, по которому будем обновлять. + string[] testMasterPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.TestMasterName) + }; + var testMasterDynamicView = new View(new ViewAttribute("testMasterDynamicView", testMasterPropertiesNames), typeof(TestMaster)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = testMaster1.ToJson(testMasterDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, ((ICSSoft.STORMNET.KeyGen.KeyGuid)testMaster1.__PrimaryKey).Guid.ToString()); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + TestMaster updatedTestMaster = new TestMaster { __PrimaryKey = testMaster1.__PrimaryKey }; + args.DataService.LoadObject(updatedTestMaster); + + Assert.Equal(testMaster1.TestMasterName, updatedTestMaster.TestMasterName); + } + + // Обновление атрибутов Детейлов. + for (int i = 0; i < deteilCount; i++) + { + testDetailWithCicleArray[i].TestDetailName += "Update"; + } + + // Представление, по которому будем обновлять. + string[] testDetailWithCiclePropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.TestDetailName) + }; + + var testDetailWithCicleDynamicView = new View(new ViewAttribute("testDetailWithCicleDynamicView", testDetailWithCiclePropertiesNames), typeof(TestDetailWithCicle)); + + for (int i = 0; i < deteilCount; i++) + { + // Преобразуем объект данных в JSON-строку. + string requestJsonDatatestDetailWithCicle = testDetailWithCicleArray[i].ToJson(testDetailWithCicleDynamicView, args.Token.Model); + DataObjectDictionary objJson = DataObjectDictionary.Parse(requestJsonDatatestDetailWithCicle, testDetailWithCicleDynamicView, args.Token.Model); + + objJson.Add("TestMaster@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(TestMaster)).Name, + ((KeyGuid)testMaster1.__PrimaryKey).Guid.ToString("D"))); + + if (i != 0) + { + objJson.Add("Parent@odata.bind", string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, + ((KeyGuid)testDetailWithCicleArray[i - 1].__PrimaryKey).Guid.ToString("D"))); + } + + requestJsonDatatestDetailWithCicle = objJson.Serialize(); + + // Формируем URL запроса к OData-сервису. + requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(TestDetailWithCicle)).Name, ((KeyGuid)testDetailWithCicleArray[i].__PrimaryKey).Guid.ToString()); + + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonDatatestDetailWithCicle).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + TestDetailWithCicle updatedTestDetailWithCicle = new TestDetailWithCicle { __PrimaryKey = testDetailWithCicleArray[i].__PrimaryKey }; + args.DataService.LoadObject(updatedTestDetailWithCicle); + + Assert.Equal(testDetailWithCicleArray[i].TestDetailName, updatedTestDetailWithCicle.TestDetailName); + } + } + }); + } + + /// + /// Test save details with inheritance. + /// + [Fact] + public void SaveDetailWithInheritanceTest() + { + ActODataService(args => + { + var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; + var детейл = new ДетейлНаследник() { prop1 = 1 }; + базовыйКласс.Детейл.Add(детейл); + + args.DataService.UpdateObject(базовыйКласс); + int newValue = 2; + детейл.prop1 = newValue; + + const string baseUrl = "http://localhost/odata"; + + string detJson = детейл.ToJson(ДетейлНаследник.Views.ДетейлНаследникE, args.Token.Model); + detJson = detJson.Replace(nameof(ДетейлНаследник.БазовыйКласс), $"{nameof(ДетейлНаследник.БазовыйКласс)}@odata.bind"); + detJson = detJson.Replace("{\"__PrimaryKey\":\"", $"\"{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}("); + detJson = detJson.Replace("\"}", ")\""); + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", + "{}", + базовыйКласс), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(ДетейлНаследник)).Name}", + detJson, + детейл), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + args.DataService.LoadObject(БазовыйКласс.Views.БазовыйКлассE, базовыйКласс); + + var детейлы = базовыйКласс.Детейл.Cast<ДетейлНаследник>(); + + Assert.Equal(1, детейлы.Count()); + Assert.Equal(newValue, детейлы.First().prop1); + } + }); + } + + /// + /// Test update details with Aggregator. + /// + [Fact] + public void UpdateDetailWithAggregatorTest() + { + ActODataService(args => + { + string[] лапаPropertiesNames = + { + Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лапа>(x => x.Размер), + }; + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; + var лапа = new Лапа() { Размер = 50 }; + кошка.Лапа.Add(лапа); + + args.DataService.UpdateObject(кошка); + + кошка.Кличка = "100"; + кошка.Тип = ТипКошки.Дикая; + лапа.Размер = 100; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", + кошка.ToJson(кошкаDynamicView, args.Token.Model), + кошка), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа, + лапа), + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); + + args.DataService.LoadObject(кошкаDynamicView, кошка); + + var лапы = кошка.Лапа.Cast<Лапа>(); + + Assert.Equal("100", кошка.Кличка); + Assert.Equal(ТипКошки.Дикая, кошка.Тип); + Assert.Equal(1, лапы.Count(б => б.Размер == 100)); + } + }); + } + + /// + /// Test update details with Aggregator. + /// + [Fact] + public void UpdateSecondDetailWithAggregatorTest() + { + ActODataService(args => + { + // Arrange. + DateTime date = new DateTime(2010, 10, 10, 10, 10, 10, DateTimeKind.Local); + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода }; + var лапа = new Лапа() { Размер = 50 }; + кошка.Лапа.Add(лапа); + var перелом = new Перелом() { Дата = DateTime.UtcNow, Тип = ТипПерелома.Открытый }; + лапа.Перелом.Add(перелом); + + args.DataService.UpdateObject(кошка); + + string[] переломPropertiesNames = + { + Information.ExtractPropertyPath<Перелом>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Перелом>(x => x.Дата), + }; + var переломDynamicView = new View(new ViewAttribute("переломDynamicView", переломPropertiesNames), typeof(Перелом)); + + string[] лапаPropertiesNames = + { + Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лапа>(x => x.Размер), + Information.ExtractPropertyPath<Лапа>(x => x.РазмерСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + лапа.Размер = 100; + перелом.Дата = date; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + string requestJsonDataПерелом = перелом.ToJson(переломDynamicView, args.Token.Model); + DataObjectDictionary objJsonПерелом = DataObjectDictionary.Parse(requestJsonDataПерелом, переломDynamicView, args.Token.Model); + + objJsonПерелом.Add( + $"{nameof(Перелом.Лапа)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name, + ((KeyGuid)лапа.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataПерелом = objJsonПерелом.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа, + лапа), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Перелом)).Name}", + requestJsonDataПерелом, + перелом), + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); + лапаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Лапа>(x => x.Перелом), переломDynamicView, true); + + args.DataService.LoadObject(кошкаDynamicView, кошка); + + var лапы = кошка.Лапа.Cast<Лапа>(); + + var переломы = лапы.FirstOrDefault().Перелом.Cast<Перелом>(); + + Assert.Equal("50", кошка.Кличка); + Assert.Equal(1, лапы.Count(б => б.Размер == 100)); + Assert.Equal(1, переломы.Count(б => б.Дата == date.ToUniversalTime())); + } + }); + } + + /// + /// Test delete and add detail. + /// + [Fact] + public void UpdateDeletedAndAddedDetailWithAggregatorTest() + { + ActODataService(args => + { + string[] лапаPropertiesNames = + { + Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лапа>(x => x.Размер), + }; + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; + var лапа = new Лапа() { Размер = 50 }; + var лапа2 = new Лапа() { Размер = 1000 }; + var лапа3 = new Лапа() { Размер = 2000 }; + кошка.Лапа.Add(лапа); + кошка.Лапа.Add(лапа2); + + args.DataService.UpdateObject(кошка); + + кошка.Кличка = "100"; + кошка.Тип = ТипКошки.Дикая; + лапа.Размер = 100; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + лапа2.SetStatus(ObjectStatus.Deleted); + + string requestJsonDataЛапа3 = лапа3.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа3 = DataObjectDictionary.Parse(requestJsonDataЛапа3, лапаDynamicView, args.Token.Model); + + objJsonЛапа3.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа3 = objJsonЛапа3.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + string.Empty, + лапа2), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа3, + лапа3), + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.NoContent, HttpStatusCode.Created }); + + кошкаDynamicView.AddDetailInView(Information.ExtractPropertyPath<Кошка>(x => x.Лапа), лапаDynamicView, true); + + args.DataService.LoadObject(кошкаDynamicView, кошка); + + var лапы = кошка.Лапа.Cast<Лапа>(); + + Assert.Equal(2, лапы.Count()); + Assert.Equal(1, лапы.Count(x => x.Размер == 2000)); + Assert.Equal(0, лапы.Count(x => x.Размер == 1000)); + } + }); + } + + /// + /// Test batch update error handling when business server throws exception. + /// + [Fact] + public void BatchUpdateErrorHandlingTest() + { + ActODataService(args => + { + string[] лапаPropertiesNames = + { + Information.ExtractPropertyPath<Лапа>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Лапа>(x => x.Размер), + }; + string[] кошкаPropertiesNames = + { + Information.ExtractPropertyPath<Кошка>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Кошка>(x => x.Кличка), + Information.ExtractPropertyPath<Кошка>(x => x.Тип), + Information.ExtractPropertyPath<Кошка>(x => x.КошкаСтрокой), + }; + var лапаDynamicView = new View(new ViewAttribute("лапаDynamicView", лапаPropertiesNames), typeof(Лапа)); + var кошкаDynamicView = new View(new ViewAttribute("кошкаDynamicView", кошкаPropertiesNames), typeof(Кошка)); + + var порода = new Порода() { Название = "Первая" }; + var кошка = new Кошка() { Кличка = "50", Порода = порода, Тип = ТипКошки.Домашняя }; + var лапа = new Лапа() { Размер = 50 }; + кошка.Лапа.Add(лапа); + + args.DataService.UpdateObject(кошка); + + кошка.Кличка = "100"; + кошка.Тип = ТипКошки.Дикая; + + // Этот размер лапы указан в CatsBS как недопустимый размер, будет сгенерировано исключение. + лапа.Размер = 100899; + + const string baseUrl = "http://localhost/odata"; + + string requestJsonDataЛапа = лапа.ToJson(лапаDynamicView, args.Token.Model); + DataObjectDictionary objJsonЛапа = DataObjectDictionary.Parse(requestJsonDataЛапа, лапаDynamicView, args.Token.Model); + + objJsonЛапа.Add( + $"{nameof(Лапа.Кошка)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name, + ((KeyGuid)кошка.__PrimaryKey).Guid.ToString("D"))); + + requestJsonDataЛапа = objJsonЛапа.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Кошка)).Name}", + кошка.ToJson(кошкаDynamicView, args.Token.Model), + кошка), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Лапа)).Name}", + requestJsonDataЛапа, + лапа), + }; + + int exceptionHandled = 0; + + args.Token.Events.CallbackAfterInternalServerError = (Exception exception, ref HttpStatusCode code) => + { + Exception currentException = exception; + + while (currentException != null) + { + if (currentException.Message == "Недопустимый размер кошачьей лапы!") + { + exceptionHandled++; + } + + currentException = currentException.InnerException; + } + + return exception; + }; + + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.Equal(1, exceptionHandled); + } + }); + } + + /// + /// Test update agregator with inheritance details. + /// + [Fact] + public void UpdateAgregatorWithInheritanceDetailsTest() + { + ActODataService(args => + { + var son = new Son() { Name = "Yakov", SuspendersColor = "Brown" }; + var daughter = new Daughter() { Name = "Yana", DressColor = "Red" }; + var person = new Person() { Name = "Yan" }; + person.Childrens.AddRange(son, daughter); + + args.DataService.UpdateObject(person); + + person.Name = "Yan Yakovlevich"; + + // Преобразуем объект данных в JSON-строку. + string[] personPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + Information.ExtractPropertyPath(x => x.Name) + }; + + var personDynamicView = new View(new ViewAttribute("personDynamicView", personPropertiesNames), typeof(Person)); + + string requestJsonData = person.ToJson(personDynamicView, args.Token.Model); + + // Формируем URL запроса к OData-сервису. + string requestUrl = string.Format("http://localhost/odata/{0}", args.Token.Model.GetEdmEntitySet(typeof(Person)).Name); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем создаваемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PostAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно. + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + } + }); + } + + /// + /// Test batch update detail of detail. + /// + [Fact] + public void UpdateDetailOfDetailTest() + { + ActODataService(args => + { + var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; + var детейл = new Детейл() { prop1 = 1 }; + базовыйКласс.Детейл.Add(детейл); + var детейл2 = new Детейл2() { prop2 = "2" }; + детейл.Детейл2.Add(детейл2); + + args.DataService.UpdateObject(базовыйКласс); + string newValue = "new"; + базовыйКласс.Свойство1 = newValue; + детейл2.prop2 = newValue; + + const string baseUrl = "http://localhost/odata"; + + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), + }; + + string[] detail2PropertiesNames = + { + Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Детейл2>(x => x.prop2), + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); + var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); + + string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); + string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); + + DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); + + objJson.Add( + $"{nameof(Детейл2.Детейл)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, + ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); + + detJson = objJson.Serialize(); + + detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", + classJson, + базовыйКласс), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", + detJson, + детейл2), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + args.DataService.LoadObject(detail2DynamicView, детейл2); + + Assert.Equal(newValue, детейл2.prop2); + } + }); + } + + /// + /// Test batch update detail of detail. + /// + [Fact] + public void UpdateDetailOfDetailDescOrderTest() + { + ActODataService(args => + { + var базовыйКласс = new БазовыйКласс() { Свойство1 = "sv1" }; + var детейл = new Детейл() { prop1 = 1 }; + базовыйКласс.Детейл.Add(детейл); + var детейл2 = new Детейл2() { prop2 = "2" }; + детейл.Детейл2.Add(детейл2); + + args.DataService.UpdateObject(базовыйКласс); + string newValue = "new"; + базовыйКласс.Свойство1 = newValue; + детейл2.prop2 = newValue; + + const string baseUrl = "http://localhost/odata"; + + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<БазовыйКласс>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<БазовыйКласс>(x => x.Свойство1), + }; + + string[] detail2PropertiesNames = + { + Information.ExtractPropertyPath<Детейл2>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Детейл2>(x => x.prop2), + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(БазовыйКласс)); + var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Детейл2)); + + string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); + string detJson = детейл2.ToJson(detail2DynamicView, args.Token.Model); + + DataObjectDictionary objJson = DataObjectDictionary.Parse(detJson, detail2DynamicView, args.Token.Model); + + objJson.Add( + $"{nameof(Детейл2.Детейл)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Детейл)).Name, + ((KeyGuid)детейл.__PrimaryKey).Guid.ToString("D"))); + + detJson = objJson.Serialize(); + + detail2DynamicView.AddProperty(Information.ExtractPropertyPath<Детейл2>(x => x.Детейл)); + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Детейл2)).Name}", + detJson, + детейл2), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(БазовыйКласс)).Name}", + classJson, + базовыйКласс), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK }); + + args.DataService.LoadObject(detail2DynamicView, детейл2); + + Assert.Equal(newValue, детейл2.prop2); + } + }); + } + + /// + /// Test batch update detail and another type detail. + /// + [Fact] + public void UpdateDetailAndDetailTest() + { + ActODataService(args => + { + var базовыйКласс = new Библиотека() { Адрес = "ул. Пушкина" }; + var мастер = new Автор() { Имя = "Александр" }; + var детейл1 = new Книга() { Название = "Му-Му", Автор1 = мастер }; + базовыйКласс.Книга.Add(детейл1); + var детейл2 = new Журнал() { Номер = 2, Автор2 = мастер }; + базовыйКласс.Журнал.Add(детейл2); + + args.DataService.UpdateObject(базовыйКласс); + string newValue = "ул. Лермонтова"; + int newIntValue = 3; + базовыйКласс.Адрес = newValue; + детейл1.Название = newValue; + детейл2.Номер = newIntValue; + + const string baseUrl = "http://localhost/odata"; + + string[] classPropertiesNames = + { + Information.ExtractPropertyPath<Библиотека>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Библиотека>(x => x.Адрес), + }; + + string[] detail1PropertiesNames = + { + Information.ExtractPropertyPath<Книга>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Книга>(x => x.Название), + }; + + string[] detail2PropertiesNames = + { + Information.ExtractPropertyPath<Журнал>(x => x.__PrimaryKey), + Information.ExtractPropertyPath<Журнал>(x => x.Номер), + }; + + var classDynamicView = new View(new ViewAttribute("classDynamicView", classPropertiesNames), typeof(Библиотека)); + var detail1DynamicView = new View(new ViewAttribute("detailDynamicView", detail1PropertiesNames), typeof(Книга)); + var detail2DynamicView = new View(new ViewAttribute("detailDynamicView", detail2PropertiesNames), typeof(Журнал)); + + string classJson = базовыйКласс.ToJson(classDynamicView, args.Token.Model); + string det1Json = детейл1.ToJson(detail1DynamicView, args.Token.Model); + string det2Json = детейл2.ToJson(detail2DynamicView, args.Token.Model); + + DataObjectDictionary obj1Json = DataObjectDictionary.Parse(det1Json, detail1DynamicView, args.Token.Model); + + obj1Json.Add( + $"{nameof(Книга.Библиотека1)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, + ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); + + det1Json = obj1Json.Serialize(); + + DataObjectDictionary obj2Json = DataObjectDictionary.Parse(det2Json, detail2DynamicView, args.Token.Model); + + obj2Json.Add( + $"{nameof(Журнал.Библиотека2)}@odata.bind", + string.Format( + "{0}({1})", + args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name, + ((KeyGuid)базовыйКласс.__PrimaryKey).Guid.ToString("D"))); + + det2Json = obj2Json.Serialize(); + + string[] changesets = new[] + { + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Библиотека)).Name}", + classJson, + базовыйКласс), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Книга)).Name}", + det1Json, + детейл1), + CreateChangeset( + $"{baseUrl}/{args.Token.Model.GetEdmEntitySet(typeof(Журнал)).Name}", + det2Json, + детейл2), + }; + HttpRequestMessage batchRequest = CreateBatchRequest(baseUrl, changesets); + using (HttpResponseMessage response = args.HttpClient.SendAsync(batchRequest).Result) + { + CheckODataBatchResponseStatusCode(response, new HttpStatusCode[] { HttpStatusCode.OK, HttpStatusCode.OK, HttpStatusCode.OK }); + + classDynamicView.AddDetailInView(nameof(Библиотека.Книга), detail1DynamicView, true); + classDynamicView.AddDetailInView(nameof(Библиотека.Журнал), detail2DynamicView, true); + + args.DataService.LoadObject(classDynamicView, базовыйКласс); + + Assert.Equal(newValue, базовыйКласс.Адрес = newValue); + Assert.Equal(newValue, базовыйКласс.Книга[0].Название); + Assert.Equal(newIntValue, базовыйКласс.Журнал[0].Номер); + + } + }); + } + + /// + /// Осуществляет проверку удаления данных. + /// + [Fact] + public void DeletePlainObjectTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + Медведь agregator = new Медведь() { МедведьСтрокой = "Agregator" }; + args.DataService.UpdateObject(agregator); + + View view = new View(typeof(Медведь), View.ReadType.OnlyThatObject); + Медведь foundAgregator0 = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(Медведь)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + Медведь foundAgregator = args.DataService.Query<Медведь>(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но при этом пустые. + /// + [Fact] + public void DeleteObjectWithSameDetailAndMasterEmptyTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; + args.DataService.UpdateObject(agregator); + + View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); + AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Осуществляет проверку удаления данных, если детейл и мастер одного типа? но лишь детейл заполнен. + /// + [Fact] + public void DeleteObjectWithSameDetailAndMasterTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; + args.DataService.UpdateObject(agregator); + + DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; + agregator.Details.Add(dm); + args.DataService.UpdateObject(dm); + + AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; + args.DataService.UpdateObject(agregator2); + DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; + agregator2.Details.Add(dm2); + args.DataService.UpdateObject(dm2); + + View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); + AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Осуществляет проверку удаления данных, если детейл и мастер одного типа, но не пустые. + /// + [Fact] + public void DeleteObjectWithSameDetailAndMaster2Test() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем удалять, и добавляем в базу обычным сервисом данных. + AgregatorSameMD agregator = new AgregatorSameMD() { Name = "Agregator" }; + args.DataService.UpdateObject(agregator); + + DetailAndMaster dm = new DetailAndMaster() { Name = "DetailAndMaster" }; + agregator.Details.Add(dm); + args.DataService.UpdateObject(dm); + + AgregatorSameMD agregator2 = new AgregatorSameMD() { Name = "Agregator2" }; + args.DataService.UpdateObject(agregator2); + DetailAndMaster dm2 = new DetailAndMaster() { Name = "DetailAndMaster2" }; + agregator2.Details.Add(dm2); + args.DataService.UpdateObject(dm2); + + agregator.Master = dm2; + args.DataService.UpdateObject(agregator); + + View view = new View(typeof(AgregatorSameMD), View.ReadType.OnlyThatObject); + AgregatorSameMD foundAgregator0 = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.NotNull(foundAgregator0); + + // Формируем URL запроса к OData-сервису (с идентификатором удаляемой сущности). + string requestUrl = string.Format("http://localhost/odata/{0}({1})", args.Token.Model.GetEdmEntitySet(typeof(AgregatorSameMD)).Name, agregator.__PrimaryKey); + requestUrl = requestUrl.Replace("{", string.Empty).Replace("}", string.Empty); + + // Обращаемся к OData-сервису и обрабатываем ответ. + using (HttpResponseMessage response = args.HttpClient.DeleteAsync(requestUrl).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок удаления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был удален из базы. + AgregatorSameMD foundAgregator = args.DataService.Query(view).FirstOrDefault(x => x.__PrimaryKey == agregator.__PrimaryKey); + Assert.Null(foundAgregator); + } + }); + } + + /// + /// Тест на изменение ссылки. + /// + [Fact] + public void MasterChangeTest() + { + ActODataService(args => + { + // Создаем объект данных, который потом будем обновлять, и добавляем в базу обычным сервисом данных. + Страна страна1 = new Страна { Название = "Страна1" }; + Страна страна2 = new Страна { Название = "Россия" }; + args.DataService.UpdateObject(страна1); + args.DataService.UpdateObject(страна2); + + Лес лес = new Лес { Название = "Тайга", Площадь = 2000, Страна = страна1 }; + args.DataService.UpdateObject(лес); + + // Обновляем ссылку. + лес.Страна = страна2; + + // Представление, по которому будем обновлять. + string[] лесPropertiesNames = + { + Information.ExtractPropertyPath<Лес>(x => x.__PrimaryKey), + }; + var лесDynamicView = new View(new ViewAttribute("лесDynamicView", лесPropertiesNames), typeof(Лес)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonDataЛес = лес.ToJson(лесDynamicView, args.Token.Model); + + // Добавляем в payload информацию, что поменяли ссылку на страну. + var requestJsonData = ODataTestHelper.AddEntryRelationship(requestJsonDataЛес, лесDynamicView, args.Token.Model, страна2, nameof(Лес.Страна)); + + // Получаем адрес для PATCH запроса. + var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, лес); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + Лес updatedЛес = new Лес { __PrimaryKey = лес.__PrimaryKey }; + args.DataService.LoadObject(updatedЛес); + + Assert.Equal(лес.Страна.__PrimaryKey.ToString(), updatedЛес.Страна.__PrimaryKey.ToString()); + } + }); + } + + /// + /// Тест на изменение агрегатора у детейла. + /// + [Fact] + public void AggregatorChangeTest() + { + ActODataService(args => + { + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + Медведь медведь1 = new Медведь { ПорядковыйНомер = 1 }; + Медведь медведь2 = new Медведь { ПорядковыйНомер = 2 }; + args.DataService.UpdateObject(медведь1); + args.DataService.UpdateObject(медведь2); + + Берлога берлога1 = new Берлога { Наименование = "Берлога1", Медведь = медведь1 }; + Берлога берлога2 = new Берлога { Наименование = "Берлога2", Медведь = медведь1 }; + Берлога берлога3 = new Берлога { Наименование = "Берлога3", Медведь = медведь1 }; + args.DataService.UpdateObject(берлога1); + args.DataService.UpdateObject(берлога2); + args.DataService.UpdateObject(берлога3); + + // Обновляем ссылку. + берлога1.Медведь = медведь2; + + // Представление, по которому будем обновлять. + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + + // Добавляем в payload информацию, что поменяли ссылку на медведя. + requestJsonData = ODataTestHelper.AddEntryRelationship(requestJsonData, берлогаDynamicView, args.Token.Model, медведь2, nameof(Берлога.Медведь)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, берлога1); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + Берлога updatedБерлога = new Берлога { __PrimaryKey = берлога1.__PrimaryKey }; + args.DataService.LoadObject(updatedБерлога); + + Assert.Equal(берлога1.Медведь.__PrimaryKey.ToString(), updatedБерлога.Медведь.__PrimaryKey.ToString()); + } + }); + } + + /// + /// Проверка изменения сложного агрегатора (у которого есть ещё ссылки на другие мастера). + /// + [Fact] + public void ComplexAggregatorChangeTest() + { + ActODataService(args => + { + LegoDevice device1 = new LegoDevice { BlockId = 1, Name = "First" }; + LegoDevice device2 = new LegoDevice { BlockId = 2, Name = "Second" }; + LegoDevice device3 = new LegoDevice { BlockId = 3, Name = "Third" }; + args.DataService.UpdateObject(device1); + args.DataService.UpdateObject(device2); + args.DataService.UpdateObject(device3); + + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + LegoPatent patent = new LegoPatent { Description = "Patent A", BaseLegoBlock = device1, Date = DateTime.Now }; + args.DataService.UpdateObject(patent); + + // Обновляем ссылку на агрегатора. + patent.BaseLegoBlock = device2; + + // Представление, по которому будем обновлять. + string[] patentPropertiesNames = + { + Information.ExtractPropertyPath(x => x.__PrimaryKey), + }; + var patentDynamicView = new View(new ViewAttribute("patentDynamicView", patentPropertiesNames), typeof(LegoPatent)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = patent.ToJson(patentDynamicView, args.Token.Model); + + // Добавляем в payload информацию, что поменяли ссылку на агрегатора. + requestJsonData = ODataTestHelper.AddEntryRelationship(requestJsonData, patentDynamicView, args.Token.Model, device2, nameof(LegoPatent.BaseLegoBlock)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, patent); + + // Обращаемся к OData-сервису и обрабатываем ответ, в теле запроса передаем обновляемый объект в формате JSON. + using (HttpResponseMessage response = args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData).Result) + { + // Убедимся, что запрос завершился успешно (тело ответа д.б. пустым при отсутствии ошибок обновления). + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + // Проверяем что объект данных был обновлен в базе, причем только по переданным атрибутам. + LegoPatent updatedLegoPatent = new LegoPatent { __PrimaryKey = patent.__PrimaryKey }; + args.DataService.LoadObject(updatedLegoPatent); + + Assert.Equal(patent.BaseLegoBlock.__PrimaryKey.ToString(), updatedLegoPatent.BaseLegoBlock.__PrimaryKey.ToString()); + } + }); + } + } +} diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs new file mode 100644 index 00000000..b7a9f6ec --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/UpdateViewsTest.cs @@ -0,0 +1,78 @@ +#if NETCOREAPP +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.CRUD.Update +{ + using System; + using System.Net; + using System.Net.Http; + + using ICSSoft.STORMNET; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers; + + using Xunit; + using Xunit.Abstractions; + + /// + /// Тесты для проверки работы UpdateViews - представлений, которые используются вместо представления по умолчанию при обновлении объекта через OData. + /// Для запуска OData backend используется модифицированная версия Startup - , которая задаёт UpdateView для Берлоги и Медведя. + /// + public class UpdateViewsTest : BaseODataServiceIntegratedTest + { + /// + /// Конструктор по-умолчанию. + /// + /// Фабрика для приложения. + /// Вывод диагностической информации по тестам. + public UpdateViewsTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + : base(factory, output) + { + + } + + /// + /// Проверка работы UpdateView - случай, когда в UpdateView не включен мастер. + /// + [Fact] + public void UpdateViewNoMastersTest() + { + ActODataService(args => + { + // Создаем объекты данных, которые потом будем обновлять, и добавляем в базу обычным сервисом данных. + Медведь медведь1 = new Медведь { ПорядковыйНомер = 1 }; + Медведь медведь2 = new Медведь { ПорядковыйНомер = 2 }; + args.DataService.UpdateObject(медведь1); + args.DataService.UpdateObject(медведь2); + + Берлога берлога1 = new Берлога { Наименование = "Берлога1", Медведь = медведь1 }; + Берлога берлога2 = new Берлога { Наименование = "Берлога2", Медведь = медведь1 }; + Берлога берлога3 = new Берлога { Наименование = "Берлога3", Медведь = медведь1 }; + args.DataService.UpdateObject(берлога1); + args.DataService.UpdateObject(берлога2); + args.DataService.UpdateObject(берлога3); + + // Обновляем ссылку. + берлога1.Медведь = медведь2; + + // Представление, по которому будем обновлять. + string[] берлогаPropertiesNames = + { + Information.ExtractPropertyPath<Берлога>(x => x.__PrimaryKey), + }; + var берлогаDynamicView = new View(new ViewAttribute("берлогаDynamicView", берлогаPropertiesNames), typeof(Берлога)); + + // Преобразуем объект данных в JSON-строку. + string requestJsonData = берлога1.ToJson(берлогаDynamicView, args.Token.Model); + + // Добавляем в payload информацию, что поменяли ссылку на медведя. + requestJsonData = ODataTestHelper.AddEntryRelationship(requestJsonData, берлогаDynamicView, args.Token.Model, медведь2, nameof(Берлога.Медведь)); + + // Формируем URL запроса к OData-сервису (с идентификатором изменяемой сущности). + var requestUrl = ODataTestHelper.GetRequestUrl(args.Token.Model, берлога1); + + // Сейчас обновление мастеров не поддерживается. + Assert.ThrowsAsync(() => args.HttpClient.PatchAsJsonStringAsync(requestUrl, requestJsonData)); // Если падает Exception, значит представление поменялось и работает. + }); + } + } +} +#endif diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs index 708d2bb5..672fe9ac 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CRUD/Update/WebFileTest.cs @@ -13,7 +13,12 @@ using Unity; using Xunit; - public class WebFileTest: BaseODataServiceIntegratedTest +#if NETFRAMEWORK + public class WebFileTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class WebFileTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class WebFileTest: BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public WebFileTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public WebFileTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp index 7b58aff7..b149d758 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CodeGen/NewPlatform.Flexberry.ORM.ODataService.Tests.crp @@ -1,1333 +1,1335 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs index 926a4c23..98e815cc 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/CustomWebApplicationFactory.cs @@ -22,7 +22,7 @@ protected override IWebHostBuilder CreateWebHostBuilder() var webHostBuilder = new WebHostBuilder() .UseUnityServiceProvider(container) .UseContentRoot(contentRootDirectory) - .UseStartup(); + .UseStartup(); return webHostBuilder; } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs index e7bb4b35..882334bf 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/DotNetCoreTests.cs @@ -13,11 +13,11 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests /// /// Тесты, специфичные для .NET Core. /// - public class DotNetCoreTests : IClassFixture> + public class DotNetCoreTests : IClassFixture> { - private readonly WebApplicationFactory _factory; + private readonly WebApplicationFactory _factory; - public DotNetCoreTests(CustomWebApplicationFactory factory) + public DotNetCoreTests(CustomWebApplicationFactory factory) { _factory = factory; } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs index 4e108989..1cd43dcb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterGetTest.cs @@ -11,7 +11,12 @@ /// Класс тестов для тестирования логики после операции считывания данных OData-сервисом. /// +#if NETFRAMEWORK public class AfterGetTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class AfterGetTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -19,7 +24,7 @@ public class AfterGetTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public AfterGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public AfterGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs index 428eccb4..aa771b4a 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterInternalServerErrorTest.cs @@ -12,7 +12,12 @@ /// /// Класс тестов для тестирования логики после возникновения исключения. /// +#if NETFRAMEWORK public class AfterInternalServerErrorTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class AfterInternalServerErrorTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -20,7 +25,7 @@ public class AfterInternalServerErrorTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public AfterInternalServerErrorTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public AfterInternalServerErrorTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -48,13 +53,13 @@ public Exception AfterInternalServerError(Exception e, ref HttpStatusCode code) public void TestAfterInternalServerError() { ActODataService(args => - { + { #if NETFRAMEWORK args.Token.Events.CallbackAfterInternalServerError = AfterInternalServerError; #elif NETCOREAPP CustomExceptionFilter.CallbackAfterInternalServerError = AfterInternalServerError; #endif - + Медведь медв = new Медведь { Вес = 48, Пол = tПол.Мужской }; Медведь медв2 = new Медведь { Вес = 148, Пол = tПол.Мужской }; Лес лес = new Лес { Название = "Бор" }; diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs index 52d93515..3779edd7 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/AfterSaveTest.cs @@ -23,7 +23,12 @@ /// /// Класс тестов для тестирования логики после операций модификации данных OData-сервисом (вставка, обновление, удаление). /// +#if NETFRAMEWORK public class AfterSaveTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class AfterSaveTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -31,7 +36,7 @@ public class AfterSaveTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public AfterSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public AfterSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs index 5644de97..ab3ef94a 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeGetTest.cs @@ -1,14 +1,14 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests.Events { - using System; - using System.Collections.Generic; + using System; + using System.Collections.Generic; using System.Net; using System.Net.Http; using ICSSoft.STORMNET; - using ICSSoft.STORMNET.Business; - using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; - using Newtonsoft.Json; + using ICSSoft.STORMNET.Business; + using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; + using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; @@ -16,7 +16,12 @@ /// /// Класс тестов для тестирования логики после операций модификации данных OData-сервисом (вставка, обновление, удаление). /// +#if NETFRAMEWORK public class BeforeGetTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BeforeGetTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -24,7 +29,7 @@ public class BeforeGetTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BeforeGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BeforeGetTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } @@ -43,14 +48,14 @@ public bool BeforeGet(ref LoadingCustomizationStruct lcs) return true; } - /// - /// Блокирует получение объектов. - /// - /// LCS. - /// false - public bool FalseBeforeGet(ref LoadingCustomizationStruct lcs) - { - return false; + /// + /// Блокирует получение объектов. + /// + /// LCS. + /// false + public bool FalseBeforeGet(ref LoadingCustomizationStruct lcs) + { + return false; } /// diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs index 153301b1..0e17c3f2 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Events/BeforeSaveTest.cs @@ -17,7 +17,12 @@ /// /// Класс тестов для тестирования логики перед операциями модификации данных OData-сервисом (вставка, обновление, удаление). /// +#if NETFRAMEWORK public class BeforeSaveTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class BeforeSaveTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -25,7 +30,7 @@ public class BeforeSaveTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public BeforeSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public BeforeSaveTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs index 9ba02a91..3c2f67bb 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Files/FileControllerTest.cs @@ -26,7 +26,12 @@ /// /// Тесты файлового контроллера , отвечающего за загрузку файлов на сервер и их скачивание. /// +#if NETFRAMEWORK public class FileControllerTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FileControllerTest : BaseODataServiceIntegratedTest +#endif { private const string FileBaseUrl = "http://localhost/api/File"; @@ -61,7 +66,7 @@ public class FileControllerTest : BaseODataServiceIntegratedTest #if NETFRAMEWORK public FileControllerTest() #elif NETCOREAPP - public FileControllerTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FileControllerTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) #endif { diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs index f5431b2a..8bba6c0e 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/ActionsTest.cs @@ -29,7 +29,12 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class ActionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class ActionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -37,7 +42,7 @@ public class ActionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public ActionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public ActionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs index f18e1521..fc083751 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/DelegateFunctionsTest.cs @@ -13,7 +13,12 @@ /// /// Unit test class for OData Service user-defined functions /// +#if NETFRAMEWORK public class DelegateFunctionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class DelegateFunctionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -21,7 +26,7 @@ public class DelegateFunctionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public DelegateFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public DelegateFunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs index a43b8ef3..ac38d289 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Functions/FunctionsTest.cs @@ -28,7 +28,12 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class FunctionsTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class FunctionsTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// @@ -36,7 +41,7 @@ public class FunctionsTest : BaseODataServiceIntegratedTest /// /// Фабрика для приложения. /// Вывод отладочной информации. - public FunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public FunctionsTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs new file mode 100644 index 00000000..4f8bf49c --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Helpers/ODataTestHelper.cs @@ -0,0 +1,45 @@ +namespace NewPlatform.Flexberry.ORM.ODataService.Tests.Helpers +{ + using ICSSoft.STORMNET; + using ICSSoft.STORMNET.KeyGen; + using NewPlatform.Flexberry.ORM.ODataService.Model; + + /// + /// Вспомогательные утилиты для тестирования OData. + /// + public static class ODataTestHelper + { + /// + /// Добавить запись об изменении ссылки у объекта в тело запроса к OData. + /// + /// Исходное тело запроса. + /// Представление исходного объекта. + /// EDM модель. + /// Новый объект данных (по ссылке). + /// Ссылка на новый объект данных. + /// Новое тело запроса к OData. + public static string AddEntryRelationship(string requestJsonData, View view, DataObjectEdmModel model, DataObject dataObject, string relationName) + { + DataObjectDictionary objJsonМедв = DataObjectDictionary.Parse(requestJsonData, view, model); + + objJsonМедв.Add( + $"{relationName}@odata.bind", + string.Format( + "{0}({1})", + model.GetEdmEntitySet(dataObject.GetType()).Name, + ((KeyGuid)dataObject.__PrimaryKey).Guid.ToString("D"))); + + var result = objJsonМедв.Serialize(); + return result; + } + + /// + /// Получить URL для запроса к OData. + /// + /// EDM модель. + /// Объект (по которому выполняется запрос). + /// URL запроса к OData. + public static string GetRequestUrl(DataObjectEdmModel model, DataObject dataObject) + => string.Format("http://localhost/odata/{0}({1})", model.GetEdmEntitySet(dataObject.GetType()).Name, ((KeyGuid)dataObject.__PrimaryKey).Guid.ToString()); + } +} diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs index 0b8b0703..00c8b141 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Model/CustomizationEdmModelNames.cs @@ -15,14 +15,19 @@ /// /// Класс тестов для тестирования метаданных, получаемых от OData-сервиса. /// +#if NETFRAMEWORK public class CustomizationEdmModelNames : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class CustomizationEdmModelNames : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// /// Фабрика для приложения. - public CustomizationEdmModelNames(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public CustomizationEdmModelNames(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs index 53e435ae..b1bf2388 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/DefaultOfflineManagerIntegratedTest.cs @@ -15,13 +15,18 @@ using NewPlatform.Flexberry.ORM.ODataService.Tests.Extensions; using NewPlatform.Flexberry.Services; +#if NETFRAMEWORK public class DefaultOfflineManagerIntegratedTest : BaseODataServiceIntegratedTest +#endif +#if NETCOREAPP + public class DefaultOfflineManagerIntegratedTest : BaseODataServiceIntegratedTest +#endif { #if NETCOREAPP /// /// Конструктор по-умолчанию. /// - public DefaultOfflineManagerIntegratedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) + public DefaultOfflineManagerIntegratedTest(CustomWebApplicationFactory factory, Xunit.Abstractions.ITestOutputHelper output) : base(factory, output) { } #endif diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs index 96e4b461..042cf1ae 100644 --- a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Offline/OfflineAuditServiceIntegratedTest.cs @@ -13,7 +13,12 @@ /// ORM-integrated unit test for . /// /// +#if NETFRAMEWORK public class OfflineAuditServiceIntegratedTest : BaseIntegratedTest +#endif +#if NETCOREAPP + public class OfflineAuditServiceIntegratedTest : BaseIntegratedTest +#endif { #if NETFRAMEWORK /// @@ -28,7 +33,7 @@ public OfflineAuditServiceIntegratedTest() /// /// Initializes a new instance of the class. /// - public OfflineAuditServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output) + public OfflineAuditServiceIntegratedTest(CustomWebApplicationFactory factory, ITestOutputHelper output) : base(factory, output, "offline") { } diff --git a/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs new file mode 100644 index 00000000..85785963 --- /dev/null +++ b/Tests/NewPlatform.Flexberry.ORM.ODataService.Tests/Startups/UpdateViewsTestStartup.cs @@ -0,0 +1,77 @@ +#if NETCOREAPP +namespace NewPlatform.Flexberry.ORM.ODataService.Tests +{ + using System; + using System.Collections.Generic; + using ICSSoft.Services; + using ICSSoft.STORMNET; + using IIS.Caseberry.Logging.Objects; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.Configuration; + using NewPlatform.Flexberry.ORM.ODataService; + using NewPlatform.Flexberry.ORM.ODataService.Extensions; + using NewPlatform.Flexberry.ORM.ODataService.Model; + using NewPlatform.Flexberry.ORM.ODataService.WebApi.Extensions; + using NewPlatform.Flexberry.Services; + using ODataServiceSample.AspNetCore; + using Unity; + + /// + /// Startup for testing UpdateView configuration - a view that is used instead of a default view during data object updates. + /// Differs from TestStartup that it sets UpdateView for and data objects. + /// + public class UpdateViewsTestStartup : Startup + { + /// + /// Initialize new instance of TestStartup. + /// + /// Configuration for new instance. + public UpdateViewsTestStartup(IConfiguration configuration) + : base(configuration) + { + } + + /// + public override void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + IUnityContainer unityContainer = UnityFactory.GetContainer(); + unityContainer.RegisterInstance(env); + + app.UseMiddleware(); + + app.UseMvc(builder => + { + builder.MapRoute("Lock", "api/lock/{action}/{dataObjectId}", new { controller = "Lock" }); + builder.MapFileRoute(); + }); + + app.UseODataService(builder => + { + IUnityContainer container = UnityFactory.GetContainer(); + + var assemblies = new[] + { + typeof(Медведь).Assembly, + typeof(ApplicationLog).Assembly, + typeof(UserSetting).Assembly, + typeof(Lock).Assembly, + }; + + PseudoDetailDefinitions pseudoDetailDefinitions = (PseudoDetailDefinitions)container.Resolve(typeof(PseudoDetailDefinitions)); + var updateViews = new Dictionary() // setting updateViews for testing + { + { typeof(Медведь), Медведь.Views.МедведьUpdateView }, + { typeof(Берлога), Берлога.Views.БерлогаUpdateView }, + }; + var modelBuilder = new DefaultDataObjectEdmModelBuilder(assemblies, false, pseudoDetailDefinitions, updateViews: updateViews); + + var token = builder.MapDataObjectRoute(modelBuilder); + + container.RegisterInstance(typeof(ManagementToken), token); + }); + } + } +} +#endif diff --git "a/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" "b/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" index 3467287d..7dcc108c 100644 --- "a/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" +++ "b/Tests/Objects/\320\221\320\265\321\200\320\273\320\276\320\263\320\260.cs" @@ -22,7 +22,12 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests /// Берлога. /// // *** Start programmer edit section *** (Берлога CustomAttributes) - + [View("БерлогаDefaultView", new string[] { + "ПолеБС", + "Наименование as \'Наименование\'", + "Комфортность as \'Комфортность\'", + "Заброшена as \'Заброшена\'", + "ПолеБС"})] // *** End programmer edit section *** (Берлога CustomAttributes) [BusinessServer("NewPlatform.Flexberry.ORM.ODataService.Tests.DenBS, NewPlatform.Flexberry.ORM.ODa" + "taService.Tests.BusinessServers", ICSSoft.STORMNET.Business.DataServiceObjectEvents.OnAllEvents)] @@ -45,6 +50,11 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests "Сертификат", "СертификатСтрока"})] [MasterViewDefineAttribute("БерлогаE", "ЛесРасположения", ICSSoft.STORMNET.LookupTypeEnum.Standard, "", "Название")] + [View("БерлогаUpdateView", new string[] { + "Наименование as \'Наименование\'", + "Комфортность as \'Комфортность\'", + "Заброшена as \'Заброшена\'", + "ПолеБС"})] public class Берлога : ICSSoft.STORMNET.DataObject { @@ -388,6 +398,17 @@ public static ICSSoft.STORMNET.View БерлогаE return ICSSoft.STORMNET.Information.GetView("БерлогаE", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Берлога)); } } + + /// + /// Представление для тестов UpdateView (без мастеров и детейлов). + /// + public static ICSSoft.STORMNET.View БерлогаUpdateView + { + get + { + return ICSSoft.STORMNET.Information.GetView("БерлогаUpdateView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Берлога)); + } + } } } @@ -414,7 +435,7 @@ public class DetailArrayOfБерлога : ICSSoft.STORMNET.DetailArray /// /// Adds object with type Берлога. /// - public DetailArrayOfБерлога(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМедведь) : + public DetailArrayOfБерлога(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь fМедведь) : base(typeof(Берлога), ((ICSSoft.STORMNET.DataObject)(fМедведь))) { } diff --git "a/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" "b/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" index 870b2994..854f42cf 100644 --- "a/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" +++ "b/Tests/Objects/\320\234\320\265\320\264\320\262\320\265\320\264\321\214.cs" @@ -72,6 +72,13 @@ namespace NewPlatform.Flexberry.ORM.ODataService.Tests "ЛесОбитания.Название as \'Название\'"})] [View("МедведьShort", new string[] { "ПорядковыйНомер as \'Порядковый номер\'"})] + [View("МедведьUpdateView", new string[] { + "ПорядковыйНомер as \'Порядковый номер\'", + "Вес as \'Вес\'", + "ЦветГлаз as \'Цвет глаз\'", + "Пол as \'Пол\'", + "ДатаРождения as \'Дата рождения\'", + "ПолеБС"})] [View("МедведьСДелейломИВычислимымСвойством", new string[] { "ПорядковыйНомер as \'Порядковый номер\'", "Вес as \'Вес\'", @@ -221,6 +228,8 @@ public virtual ICSSoft.STORMNET.UserDataTypes.NullableDateTime ДатаРожд [StrLen(255)] [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.MSSQLDataService), "\'ПорядковыйНомер:\' + @ПорядковыйНомер@ + \", Цвет глаз мамы:\" + isnull(@Мама.ЦветГ" + "лаз@,\'\')")] + [DataServiceExpression(typeof(ICSSoft.STORMNET.Business.PostgresDataService), "\'ПорядковыйНомер:\' || @ПорядковыйНомер@ || \", Цвет глаз мамы:\" || coalesce(@Мама.ЦветГ" + + "лаз@,\'\')")] public virtual string МедведьСтрокой { get @@ -722,6 +731,17 @@ public static ICSSoft.STORMNET.View МедведьShort } } + /// + /// Представление для тестов UpdateView (без мастеров и детейлов). + /// + public static ICSSoft.STORMNET.View МедведьUpdateView + { + get + { + return ICSSoft.STORMNET.Information.GetView("МедведьUpdateView", typeof(NewPlatform.Flexberry.ORM.ODataService.Tests.Медведь)); + } + } + /// /// "МедведьСДелейломИВычислимымСвойством" view. ///