Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop -> Main #105

Closed
wants to merge 104 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
1757421
Fix missing code for Unsaved changes that wan't getting called for ty…
PaulLinYao Jun 8, 2023
1f8bac1
CloudLib resolver: fallback to rest more carefully
MarkusHorstmann Jun 13, 2023
a7e3557
Pick up new common
MarkusHorstmann Jun 14, 2023
fae2af7
Pick up staging
MarkusHorstmann Jun 14, 2023
c6d65c4
Composition: properly delete nested generated objects (FolderType pat…
MarkusHorstmann Jun 14, 2023
4a97a50
Enable Unsaved changes warning for Profile Explorer and Side Menu (MR…
PaulLinYao Jun 15, 2023
61f926f
Merge branch 'develop' of https://github.com/cesmii/ProfileDesigner i…
PaulLinYao Jun 15, 2023
ea8bd43
Merge pull request #98 from cesmii/stage
scoxen1 Jun 15, 2023
fbd7162
Merge pull request #100 from cesmii/stage
scoxen1 Jun 15, 2023
ddb0878
Frontend: show/edit OPC NodeID for attributes
MarkusHorstmann Jun 20, 2023
5255a2e
test - add support for more profile integration tests
scoxen1 Jun 28, 2023
0b1deb7
Import/Export: Symbolic Name, EnumValues/EnumStrings, bug fixes
MarkusHorstmann Jun 29, 2023
78e717f
Test: diff updates
MarkusHorstmann Jun 29, 2023
d91e0e3
readme for cloudlib resolver
MarkusHorstmann Jun 29, 2023
739e98a
Test: one more diff update
MarkusHorstmann Jun 29, 2023
a76d486
Merge pull request #101 from cesmii/feature/enumvalues
MarkusHorstmann Jun 29, 2023
67fb178
FrontEnd: prevent unintended redirect
MarkusHorstmann Jun 29, 2023
278f6ea
CloudLibResolver: add option to include nodesets pending approval
MarkusHorstmann Jun 29, 2023
3a18def
Readme update
MarkusHorstmann Jun 29, 2023
a3d9653
add tests for attributes (in progress). unlreated small cosmetic fix.
scoxen1 Jul 5, 2023
bbe19c8
Merge branch 'develop' of https://github.com/cesmii/ProfileDesigner i…
scoxen1 Jul 5, 2023
7ec75fb
refactor - moving controller test code files into different folder fo…
scoxen1 Jul 5, 2023
63f5eda
add attribute tests (in progress)
scoxen1 Jul 5, 2023
bc41658
updating tests to run type def attribute tests. re-factoring code to …
scoxen1 Jul 7, 2023
f9d4b95
fixes to unit tests related to adding attributes
scoxen1 Jul 10, 2023
f5036a8
tweak to DeleteIntermediateItem - removing usage of string constant t…
scoxen1 Jul 10, 2023
9fb9740
cosmetics, usability, cleanup on front end
scoxen1 Jul 11, 2023
1dcc9ac
Import: upgrade cloudlib profiles is newer version is required
MarkusHorstmann Jul 12, 2023
cd97a00
Merge develop
MarkusHorstmann Jul 12, 2023
f27b223
Fix - front end handle api error loops endlessly on error - due to re…
scoxen1 Jul 14, 2023
5f3e9b5
try out abbreviated smoke test
scoxen1 Jul 14, 2023
ea657fd
Update tests-shared.yml
scoxen1 Jul 14, 2023
974e709
Update smoke-tests.yml
scoxen1 Jul 14, 2023
a6df1f5
Update tests-shared.yml
scoxen1 Jul 14, 2023
033e11f
trying out trait at class level and multiple traits per test.
scoxen1 Jul 14, 2023
a580959
Merge branch 'feature/smoke-tests' of https://github.com/cesmii/Profi…
scoxen1 Jul 14, 2023
347e983
adding trait attribute to other contoller tests
scoxen1 Jul 14, 2023
4abfd84
Update tests-shared.yml
scoxen1 Jul 14, 2023
460600d
Update smoke-tests.yml
scoxen1 Jul 14, 2023
ce038ce
Update tests-shared.yml
scoxen1 Jul 14, 2023
fb9818e
Update tests-shared.yml
scoxen1 Jul 14, 2023
5472d95
Update tests-shared.yml
scoxen1 Jul 14, 2023
b88599e
Update tests.yml
scoxen1 Jul 14, 2023
fe4a80b
Update tests-shared.yml
scoxen1 Jul 14, 2023
3cef4b4
Update tests-shared.yml
scoxen1 Jul 14, 2023
43ad481
adding smoke test attr.
scoxen1 Jul 14, 2023
7e21f16
Update tests.yml
scoxen1 Jul 14, 2023
29af06d
Merge branch 'feature/smoke-tests' of https://github.com/cesmii/Profi…
scoxen1 Jul 14, 2023
47f1e81
Delete original profile XML on updates (enables clean cloudlib update)
MarkusHorstmann Jul 14, 2023
6e25aa0
Profile: track creation and update
MarkusHorstmann Jul 14, 2023
8b4080d
ImportLogDAL: avoid null ref on delete of non-exiting ID
MarkusHorstmann Jul 14, 2023
1994e9d
Import: handle profiles without cached XML. Export: Generate encodings
MarkusHorstmann Jul 14, 2023
6f16e82
Test: cloud lib mock update
MarkusHorstmann Jul 15, 2023
f374218
Test: updated diffs
MarkusHorstmann Jul 15, 2023
82346c2
Pick up NodeSet Model 1.0.16
MarkusHorstmann Jul 15, 2023
8890484
Front End: prevent undesired navigation also on edit (workaround)
MarkusHorstmann Jul 15, 2023
05fac62
FE cloudlib grid: Remove dead code
MarkusHorstmann Jul 15, 2023
1e8eac9
Merge pull request #103 from cesmii/feature/smoke-tests
scoxen1 Jul 17, 2023
bbc5d0c
Readme: add section on running PD, MP, Cloudlib on dev machine
MarkusHorstmann Jul 17, 2023
a3a97d6
Pick up common/develop
MarkusHorstmann Jul 17, 2023
bd61a05
Merge develop
MarkusHorstmann Jul 17, 2023
84be158
Test fixes
MarkusHorstmann Jul 18, 2023
144fcbb
Test fix
MarkusHorstmann Jul 18, 2023
56c66ae
Test: one more diff
MarkusHorstmann Jul 18, 2023
0293540
Fixes for update script
MarkusHorstmann Jul 18, 2023
f6da7fb
Merge pull request #102 from cesmii/feature/pendingresolver
MarkusHorstmann Jul 18, 2023
a7a0003
Profile: don't map IsActive
MarkusHorstmann Jul 18, 2023
8e0fe97
Test: update column count
MarkusHorstmann Jul 18, 2023
90a8ca2
Import Export smoketests
MarkusHorstmann Jul 19, 2023
662c5fb
Test: rename import/export tests
MarkusHorstmann Jul 19, 2023
1e1d50d
Merge pull request #104 from cesmii/feature/importsmoke
MarkusHorstmann Jul 19, 2023
4fe904b
Outgoing email using 'CESMII DevOps Team' in signature of email
PaulLinYao Aug 10, 2023
d126fe7
Update CorsSettings in appsettings.Staging.json
PaulLinYao Aug 11, 2023
2bdec05
importing logs - check for null before foreach
scoxen1 Aug 16, 2023
81d1ca9
Merge branch 'develop' of https://github.com/cesmii/ProfileDesigner i…
scoxen1 Aug 16, 2023
9edc653
Comment out add cols portion - cols already present in prod db
scoxen1 Aug 24, 2023
b6b3aba
fix - if import fails on first try, correct to allow a subsequent try
scoxen1 Sep 29, 2023
99b0056
Merge pull request #107 from cesmii/feature/import-azure-function
scoxen1 Oct 2, 2023
b1f56aa
Merge pull request #109 from cesmii/main
scoxen1 Nov 13, 2023
8c6a6a7
Link to common submodule
PaulLinYao Mar 4, 2024
143764c
Create tests-github-filter.yml
PaulLinYao Nov 24, 2023
4a8a4d5
Delete .github/workflows/tests-github-filter.yml
PaulLinYao Nov 24, 2023
38f4002
Create tests-in-browser.yml
PaulLinYao Nov 24, 2023
6e6ee26
Start to fixing problem with multiple database records preventing use…
PaulLinYao Mar 4, 2024
6adb0c2
Adding missing function for ClouLibMock (was missing 'SearchAsync')
PaulLinYao Mar 4, 2024
1113e7b
Bug fix - correcting wrong handling of login when multiple database r…
PaulLinYao Mar 4, 2024
bcc1311
Github Issue - changing actions/checkout@v3 to actions/checkout@v2
PaulLinYao Mar 4, 2024
ff9de18
Fixing single sign to deal with multiple user records in public.user
PaulLinYao Apr 9, 2024
4de55c4
Update nuget packages with known vulnerabilities
PaulLinYao Apr 9, 2024
ad8de79
Self Service Sign Up -- Removing code that deletes duplicate records …
PaulLinYao Apr 10, 2024
1e3fd6d
Make equivalent to Main - updating versions of script plugins
scoxen1 Aug 20, 2024
71220d6
Make equivalent to Main - updating versions of script plugins
scoxen1 Aug 20, 2024
f7cc729
Make equivalent to Main - updating versions of script plugins
scoxen1 Aug 20, 2024
db9aea8
Make equivalent to Main - resolve merge conflicts
scoxen1 Aug 20, 2024
38ce4e6
Merge pull request #126 from cesmii/main
scoxen1 Sep 3, 2024
29882c7
Update README.md
scoxen1 Sep 3, 2024
f2c8f03
Update README.md
scoxen1 Sep 3, 2024
cc556cb
Update tests.yml
scoxen1 Sep 3, 2024
427685c
Update AuthController.cs
scoxen1 Sep 3, 2024
e5ea560
updating submodule common to point to main branch
scoxen1 Sep 4, 2024
ed99abd
Merge branch 'develop' into CO5/Develop
scoxen1 Sep 4, 2024
bce369d
update develop branch to point to common/develop branch for submodule…
scoxen1 Sep 4, 2024
0957799
point to develop branch on common submodule
scoxen1 Sep 4, 2024
9fa1b06
Merge pull request #127 from cesmii/CO5/Develop
scoxen1 Sep 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
updating tests to run type def attribute tests. re-factoring code to …
…share more code across test classes.
scoxen1 committed Jul 7, 2023
commit bc416589a9d21df8dc8d85cf70b49c590c939f2f
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ public class ProfileTypeDefControllerIntegrationTest : ProfileTypeDefControllerT
"'minValue':null,'maxValue':null,'engUnit':null,'compositionId':-999,'composition':{'id':-999,'name':'Test Comp Add','description':'','browseName':'','relatedProfileTypeDefinitionId':-999,'relatedName':'Test Comp Add'}," +
"'interfaceId':-1,'interface':null,'description':'','displayName':'','typeDefinitionId':-888,'isArray':false,'isRequired':false,'enumValue':null}";

#region API constants
#region API constants
private const string URL_INIT = "/api/profiletypedefinition/init";
private const string URL_LIBRARY = "/api/profiletypedefinition/library";
private const string URL_DELETE = "/api/profiletypedefinition/delete";
@@ -94,7 +94,7 @@ public async Task ExtendItem(int counter)
var apiClient = base.ApiClient;

//create parent profile and entity to extend
var itemExtend = await InsertMockProfileAndExtendEntity(_guidCommon);
var itemExtend = await InsertMockProfileAndTypeDefinition(TYPE_ID_DEFAULT, _guidCommon);

// ACT
//extend item
@@ -130,7 +130,7 @@ public async Task AddItem_GetItem(ProfileTypeDefinitionModel model)

//treat inbound item as the new type def. Once we call extend, we need to apply some updates to it and save.
//create parent profile and entity to extend
var itemExtend = await InsertMockProfileAndExtendEntity(_guidCommon);
var itemExtend = await InsertMockProfileAndTypeDefinition(TYPE_ID_DEFAULT, _guidCommon);
var resultExtend = await MapModelToExtendedItem(apiClient, _guidCommon, itemExtend, model);
////extend item
//var resultExtend = await apiClient.ApiGetItemAsync<ProfileTypeDefinitionModel>(URL_EXTEND,
@@ -189,7 +189,7 @@ public async Task DeleteItem(ProfileTypeDefinitionModel model)
//add an item so that we can delete it
//have to properly extend a base item, etc. before adding
//then get the id of newly added item so we can call delete
var itemExtend = await InsertMockProfileAndExtendEntity(_guidCommon);
var itemExtend = await InsertMockProfileAndTypeDefinition(TYPE_ID_DEFAULT, _guidCommon);
var resultExtend = await MapModelToExtendedItem(apiClient, _guidCommon, itemExtend, model);
var resultAdd = await apiClient.ApiExecuteAsync<ResultMessageWithDataModel>(URL_ADD, resultExtend);
var modelId = new IdIntModel() { ID = (int)resultAdd.Data };
@@ -217,7 +217,7 @@ public async Task NoDeleteParent(ProfileTypeDefinitionModel model)
var apiClient = base.ApiClient;
//add an item to a parent item and then try to delete parent.
//we should get a message indicating can't delete parent due to dependency. Must delete child then you can delete parent.
var entityParent = await InsertMockProfileAndExtendEntity(_guidCommon);
var entityParent = await InsertMockProfileAndTypeDefinition(TYPE_ID_DEFAULT, _guidCommon);
var modelChildNew = await MapModelToExtendedItem(apiClient, _guidCommon, entityParent, model);
var resultChild = await apiClient.ApiExecuteAsync<ResultMessageWithDataModel>(URL_ADD, modelChildNew);
var parentId = new IdIntModel() { ID = entityParent.ID.Value };
@@ -243,14 +243,15 @@ public async Task DeleteItemIntermediateObject(ProfileTypeDefinitionModel model)
var apiClient = base.ApiClient;

//insert base parent
var entityParent = await InsertMockProfileAndExtendEntity(_guidCommon);
var entityParent = await InsertMockProfileAndTypeDefinition(TYPE_ID_DEFAULT, _guidCommon);
//extend base parent as item 1 - use this as composition for item 2
var modelChild1New = await MapModelToExtendedItem(apiClient, _guidCommon, entityParent, model);
var resultChild1Added = await apiClient.ApiExecuteAsync<ResultMessageWithDataModel>(URL_ADD, modelChild1New);
var modelChild1Id = new IdIntModel() { ID = (int)resultChild1Added.Data };
//extend base parent as item 2 - add composition pointing to item 1
var modelChild2New = await MapModelToExtendedItem(apiClient, _guidCommon, entityParent, model);
//add composition attribute pointing to child 1
//var attr = CreateAttributeComposition($"Attribute-Comp", );
var attr = JsonConvert.DeserializeObject<ProfileAttributeModel>(_attributeComposition);
attr.CompositionId = modelChild1Id.ID;
attr.Composition.ID = modelChild1Id.ID;
@@ -402,24 +403,24 @@ private async Task<List<ProfileTypeDefinition>> InsertMockEntitiesForSearchTests

//create a parent type definition - make parents null so it doesn't impact the
//search calls
var parentClass = CreateEntity(0, profileMine.ID, null, _guidCommon, Guid.NewGuid(), user);
var parentClass = CreateEntity(0, profileMine.ID, null, TYPE_ID_DEFAULT, _guidCommon, Guid.NewGuid(), user);
parentClass.AuthorId = null;
parentClass.OwnerId = null;
parentClass.ExternalAuthor = _guidCommon.ToString();
await repo.AddAsync(parentClass);
var parentInterface = CreateEntity(0, profileMine.ID, null, _guidCommon, Guid.NewGuid(), user);
var parentInterface = CreateEntity(0, profileMine.ID, null, TYPE_ID_DEFAULT, _guidCommon, Guid.NewGuid(), user);
parentInterface.ProfileTypeId = (int)ProfileItemTypeEnum.Interface;
parentInterface.AuthorId = null;
parentInterface.OwnerId = null;
parentInterface.ExternalAuthor = _guidCommon.ToString();
await repo.AddAsync(parentInterface);
var parentEnum = CreateEntity(0, profileMine.ID, null, _guidCommon, Guid.NewGuid(), user);
var parentEnum = CreateEntity(0, profileMine.ID, null, TYPE_ID_DEFAULT, _guidCommon, Guid.NewGuid(), user);
parentEnum.ProfileTypeId = (int)ProfileItemTypeEnum.Enumeration;
parentEnum.AuthorId = null;
parentEnum.OwnerId = null;
parentEnum.ExternalAuthor = _guidCommon.ToString();
await repo.AddAsync(parentEnum);
var parentStructure = CreateEntity(0, profileMine.ID, null, _guidCommon, Guid.NewGuid(), user);
var parentStructure = CreateEntity(0, profileMine.ID, null, TYPE_ID_DEFAULT, _guidCommon, Guid.NewGuid(), user);
parentStructure.ProfileTypeId = (int)ProfileItemTypeEnum.Structure;
parentStructure.AuthorId = null;
parentStructure.OwnerId = null;
@@ -445,7 +446,7 @@ private async Task<List<ProfileTypeDefinition>> InsertMockEntitiesForSearchTests
//distribute the parent type def assignment
var p = i % 5 == 0 ? parentInterface : i % 4 == 0 ? parentClass : i % 3 == 0 ? parentStructure : parentEnum;
//set owner to 2/3 of the items
var entity = CreateEntity(i, i % 3 == 0 ? profileCore.ID : profileMine.ID, p, _guidCommon, uuid, user);
var entity = CreateEntity(i, i % 3 == 0 ? profileCore.ID : profileMine.ID, p, p.ProfileTypeId.Value, _guidCommon, uuid, user);
int? authorId = i % 3 == 0 ? null : user.ID;
entity.AuthorId = authorId;
entity.OwnerId = authorId;
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

using Xunit;
using Xunit.Abstractions;
using Newtonsoft.Json;

@@ -26,14 +25,22 @@ public class ProfileTypeDefControllerTestBase : ControllerTestBase
//for some tests, tie together a common guid so we can delete the created items at end of test.
protected Guid _guidCommon = Guid.NewGuid();

//get some lookup data that will be needed when we start adding addtribute tests
protected AppLookupModel _lookupData = null;
protected ProfileLookupModel _lookupRelated = null;

protected int _profileRootId;
protected int _compositionRootId; //_baseObjectType id
protected int _interfaceRootId; //_baseInterfaceType id
protected const string FN_GET_DESCENDANTS = "public.fn_profile_type_definition_get_descendants";

#region API constants
//protected const string URL_INIT = "/api/profiletypedefinition/init";
protected const string URL_EXTEND = "/api/profiletypedefinition/extend";
protected const string URL_ADD = "/api/profiletypedefinition/add";
//protected const string URL_LIBRARY = "/api/profiletypedefinition/library";
protected const string URL_GETBYID = "/api/profiletypedefinition/getbyid";
//protected const string URL_DELETE = "/api/profiletypedefinition/delete";
//protected const string URL_DELETE_MANY = "/api/profiletypedefinition/deletemany";

protected const string URL_LOOKUP_ALL = "/api/lookup/all";
protected const string URL_LOOKUP_RELATED = "/api/profiletypedefinition/lookup/profilerelated/extend";
#endregion

#region data naming constants
@@ -58,46 +65,100 @@ public ProfileTypeDefControllerTestBase(
services.AddScoped<IRepository<Profile>, BaseRepo<Profile, ProfileDesignerPgContext>>();
services.AddScoped<IRepository<ProfileTypeDefinition>, BaseRepo<ProfileTypeDefinition, ProfileDesignerPgContext>>();
services.AddScoped<IRepository<ProfileTypeDefinitionAnalytic>, BaseRepo<ProfileTypeDefinitionAnalytic, ProfileDesignerPgContext>>();
services.AddScoped<IRepository<ProfileAttribute>, BaseRepo<ProfileAttribute, ProfileDesignerPgContext>>();
services.AddScoped<IRepository<LookupDataType>, BaseRepo<LookupDataType, ProfileDesignerPgContext>>();

services.AddScoped<IRepositoryStoredProcedure<ProfileTypeDefinitionSimple>, BaseRepoStoredProcedure<ProfileTypeDefinitionSimple, ProfileDesignerPgContext>>();

//need to get user id of test user when we add profile
services.AddScoped<IRepository<User>, BaseRepo<User, ProfileDesignerPgContext>>();

_serviceProvider = services.BuildServiceProvider();

//note - base.ApiClient - this will force creation of test user if not yet added, this is needed downstream
//if we run this from a pristine db
PrepareMockData(base.ApiClient);
}

#pragma warning disable xUnit1026 // Stop warnings related to parameters not used in test cases.

#region Helper Methods
/// <summary>
/// Create a mock parent item. Then set up a newly extended item with that parent item.
/// Finally, apply the model values to the newly extended item and return.
/// </summary>
/// <returns></returns>
protected async Task<ProfileTypeDefinitionModel> MapModelToExtendedItem(MyNamespace.Client apiClient, Guid guidCommon,
ProfileTypeDefinition itemExtend, ProfileTypeDefinitionModel model)
#region Insert Mock Data
protected void PrepareMockData(MyNamespace.Client apiClient)
{
//extend item
var result = await apiClient.ApiGetItemAsync<ProfileTypeDefinitionModel>(URL_EXTEND,
new IdIntModel() { ID = itemExtend.ID.Value });
//map data to newly created extend
result.OpcNodeId = model.OpcNodeId;
result.Name = model.Name;
result.BrowseName = model.BrowseName;
result.SymbolicName = guidCommon.ToString(); //so we can delete this item once done
result.Description = model.Description;
result.Created = model.Created;
result.MetaTags = model.MetaTags;
result.Attributes = model.Attributes;
result.ProfileId = itemExtend.ProfileId;
result.Profile = new ProfileModel() { ID = itemExtend.ProfileId };
return result;
//to make this stateless (not dependent on the existence of other nodesets being present),
//create all of our own dependent data
InsertDependentMockData().Wait();

//get lookup data to be used when adding attributes
_lookupData = GetLookupData(apiClient, _guidCommon).Result;
//get related lookup data - variable types list, compositions, interfaces. Using mock data to keep this stateless.
_lookupRelated = GetRelatedData(_profileRootId);
}

protected async Task InsertDependentMockData()
{
//root type def and parent profile
var baseDataType = await InsertMockProfileAndTypeDefinition((int)ProfileItemTypeEnum.CustomDataType, _guidCommon, "_BaseDataType");
_profileRootId = baseDataType.ProfileId.Value;

using (var scope = _serviceProvider.CreateScope())
{
var repo = scope.ServiceProvider.GetService<IRepository<ProfileTypeDefinition>>();
var repoDataType = scope.ServiceProvider.GetService<IRepository<LookupDataType>>();
var repoUser = scope.ServiceProvider.GetService<IRepository<User>>();

var baseObjectType = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, null, (int)ProfileItemTypeEnum.Class, _guidCommon, "_BaseObjectType");
_compositionRootId = baseObjectType.ID.Value;
var baseVariableType = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, null, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_BaseVariableType");
var baseInterfaceType = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseObjectType, (int)ProfileItemTypeEnum.Interface, _guidCommon, "_BaseInterfaceType");
_interfaceRootId = baseInterfaceType.ID.Value;
var baseEnumeration = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseDataType, (int)ProfileItemTypeEnum.Enumeration, _guidCommon, "_Enumeration");

var baseDataVariableType = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_BaseDataVariableType");
//data types
var dt0 = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_Int");
await InsertMockDataType(repoDataType, dt0, _guidCommon);
var dt1 = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_DateTime");
await InsertMockDataType(repoDataType, dt1, _guidCommon);
var dt2 = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_Boolean");
await InsertMockDataType(repoDataType, dt2, _guidCommon);
var dt3 = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_Number");
await InsertMockDataType(repoDataType, dt3, _guidCommon);
var dt4 = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_String");
await InsertMockDataType(repoDataType, dt4, _guidCommon);
var dt5 = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_Double");
await InsertMockDataType(repoDataType, dt5, _guidCommon);
var dt6 = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_Float");
await InsertMockDataType(repoDataType, dt6, _guidCommon);

//variable types
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseDataVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_FrameType");
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseDataVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_ProgramDiagnosticType");
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseDataVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_ReferenceDescriptionVariableType");
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseDataVariableType, (int)ProfileItemTypeEnum.VariableType, _guidCommon, "_PubSubDiagnosticsCounterType");

//compositions - make them descendants of each other for more layers
var compCurrent = baseObjectType;
for (int i = 1; i <= 5; i++)
{
compCurrent = await InsertMockTypeDefinition(repo, repoUser, _profileRootId, compCurrent, (int)ProfileItemTypeEnum.Class, _guidCommon, $"_BaseComposition_{i}");
}

//interfaces
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseInterfaceType, (int)ProfileItemTypeEnum.Interface, _guidCommon, "_IVlanIdType");
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseInterfaceType, (int)ProfileItemTypeEnum.Interface, _guidCommon, "_IOrderedObjectType");
//enumerations
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseEnumeration, (int)ProfileItemTypeEnum.Enumeration, _guidCommon, "_ServerState");
await InsertMockTypeDefinition(repo, repoUser, _profileRootId, baseEnumeration, (int)ProfileItemTypeEnum.Enumeration, _guidCommon, "_ApplicationType");
}
}

/// <summary>
/// Create a parent profile and an entity to extend from.
/// </summary>
/// <param name="guidCommon"></param>
/// <returns></returns>
protected async Task<ProfileTypeDefinition> InsertMockProfileAndExtendEntity(Guid guidCommon)
protected async Task<ProfileTypeDefinition> InsertMockProfileAndTypeDefinition(int typeId, Guid guidCommon, string name = null)
{
using (var scope = _serviceProvider.CreateScope())
{
@@ -111,22 +172,253 @@ protected async Task<ProfileTypeDefinition> InsertMockProfileAndExtendEntity(Gui
await repoProfile.AddAsync(profile);

//create a parent type definition
var result = CreateEntity(0, profile.ID, null, guidCommon, Guid.NewGuid(), user);
var result = CreateEntity(0, profile.ID, null, typeId, guidCommon, Guid.NewGuid(), user);
if (!string.IsNullOrEmpty(name)) result.Name = name;
await repo.AddAsync(result);

//assign profile to type def in case caller needs it.
result.Profile = profile;

return result;
}
}

protected async Task<ProfileTypeDefinition> InsertMockTypeDefinition(int profileId,
ProfileTypeDefinition parent, int typeId, Guid guidCommon, string name = null)
{
using (var scope = _serviceProvider.CreateScope())
{
var repo = scope.ServiceProvider.GetService<IRepository<ProfileTypeDefinition>>();
var repoUser = scope.ServiceProvider.GetService<IRepository<User>>();
return await InsertMockTypeDefinition(repo, repoUser, profileId, parent, typeId, guidCommon, name);
}
}

protected async Task<ProfileTypeDefinition> InsertMockTypeDefinition(
IRepository<ProfileTypeDefinition> repo,
IRepository<User> repoUser,
int profileId, ProfileTypeDefinition parent, int typeId, Guid guidCommon, string name = null)
{
var user = GetTestUser(repoUser);
//create a type definition
var result = CreateEntity(0, profileId, parent, typeId, guidCommon, Guid.NewGuid(), user);
if (!string.IsNullOrEmpty(name)) result.Name = name;
await repo.AddAsync(result);
return result;
}

protected async Task<LookupDataType> InsertMockDataType(
IRepository<LookupDataType> repo,
ProfileTypeDefinition typeDef, Guid guidCommon)
{
//create a data type, pointing to a type def
//sybmolic name includes guidCommon
var result = new LookupDataType() { Code = $"http://{guidCommon}/{typeDef.Name}", Name = typeDef.Name, CustomTypeId = typeDef.ID };
await repo.AddAsync(result);
return result;
}

/// <summary>
/// This is used to create a row directly into DB. Bypasses everything except baseRepo
/// </summary>
/// <param name="i"></param>
/// <param name="uuid"></param>
/// <param name="creator"></param>
/// <param name="cloudLibraryId"></param>
/// <returns></returns>
protected static ProfileTypeDefinition CreateEntity(int i, int? profileId, ProfileTypeDefinition parent, int typeId, Guid guidCommon, Guid uuid, User user)
{
var parentName = parent == null ? "TypeDef" : $"{parent.Name}::Extend";
var dt = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
var tags = new List<MetaTag>() {
new MetaTag() { Name = (i % 4 == 0 ? "abcd" : (i % 3 == 0) ? "efgh" : (i % 2 == 0) ? "ijkl" : "mnop") }
};
return new ProfileTypeDefinition()
{
OpcNodeId = uuid.ToString(),
Name = $"{parentName}-{i}",
ProfileId = profileId,
ParentId = parent?.ID, //for some tests, we start with null parent and assign during test
BrowseName = $"browse-{i}-{guidCommon}-{uuid}",
SymbolicName = guidCommon.ToString(),
Description = (i % 3 == 0 ? "Unique description for 3" : (i % 2 == 0) ? "Unique description for 2" : "Common description"),
ProfileTypeId = parent == null ? typeId : parent?.ProfileTypeId,
IsAbstract = i % 9 == 0,
Created = dt,
Updated = dt,
AuthorId = user?.ID,
OwnerId = user?.ID,
CreatedById = user == null ? 0 : user.ID.Value,
UpdatedById = user == null ? 0 : user.ID.Value,
MetaTags = JsonConvert.SerializeObject(tags),
IsActive = true,
};
}

/// <summary>
/// This is used to create a row directly into DB. Bypasses everything except baseRepo
/// </summary>
protected static Profile CreateProfileEntity(Guid guidCommon, User user)
{
var dt = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
return new Profile()
{
Namespace = $"{PARENT_PROFILE_NAMESPACE}/{guidCommon}",
Title = TITLE_PATTERN,
Version = "1.0.0.0",
CategoryName = "TEST",
PublishDate = dt,
AuthorId = user?.ID,
OwnerId = user != null ? user.ID : null,
Keywords = new string[] { guidCommon.ToString() }
};
}

/// <summary>
/// Create a composition attribute
/// </summary>
protected static ProfileAttributeModel CreateAttributeComposition(
string name, string compositionName, Guid guidCommon,
ProfileLookupModel lookupRelated, AppLookupModel lookupData)
{
var dt = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
var comp = lookupRelated.Compositions.Find(x => x.Name.ToLower().Equals(compositionName.ToLower()));
var attrType = lookupData.AttributeTypes.Find(x => x.Name.ToLower().Equals("composition"));
//var dataType = _lookupData.DataTypes.Find(x => x.Name.ToLower().Equals("composition"));

return new ProfileAttributeModel()
{
CompositionId = comp.ID,
Composition = new ProfileTypeDefinitionRelatedModel()
{
ID = comp.ID,
SymbolicName = guidCommon.ToString(),
RelatedProfileTypeDefinitionId = comp.ID,
Name = comp.Name
},
AttributeType = attrType,
//matching happens on browse name - add unique portion to browse name
BrowseName = $"{Guid.NewGuid().ToString()}:::{guidCommon.ToString()}",
SymbolicName = guidCommon.ToString(),
//DataType = dataType,
//DataTypeId = dataType.ID,
Name = name
};
}

protected static ProfileTypeDefinitionModel CreateItemModel(int i, int? profileId, ProfileTypeDefinition parent, Guid guidCommon, Guid uuid, string cloudLibraryId = null)
{
var entity = CreateEntity(i, profileId, parent, guidCommon, uuid, null);
var entity = CreateEntity(i, profileId, parent, parent == null ? TYPE_ID_DEFAULT : parent.ProfileTypeId.Value, guidCommon, uuid, null);
return MapToModel(entity);
}

#endregion

#region Helper Methods
protected ProfileLookupModel GetRelatedData(int profileId)
{
using (var scope = _serviceProvider.CreateScope())
{
var repo = scope.ServiceProvider.GetService<IRepository<ProfileTypeDefinition>>();
var repoDataType = scope.ServiceProvider.GetService<IRepository<LookupDataTypeRanked>>();
var repoUser = scope.ServiceProvider.GetService<IRepository<User>>();
var repoRelated = scope.ServiceProvider
.GetService<IRepositoryStoredProcedure<ProfileTypeDefinitionSimple>>();
var user = GetTestUser(repoUser);

var items = repo.FindByCondition(x => x.ProfileId.HasValue && x.ProfileId.Value.Equals(profileId));

var orderBys = new List<OrderBySimple>() { new OrderBySimple() { FieldName = "name" } };
var itemsComposition = repoRelated.ExecStoredFunction(FN_GET_DESCENDANTS, null, null, null, orderBys,
_compositionRootId, user.ID, false, false)
.ToList();
//(fnName, null, skip, take, orderBys, parameters)
var itemsInterface = repoRelated.ExecStoredFunction(FN_GET_DESCENDANTS, null, null, null, orderBys,
_interfaceRootId, user.ID, false, false)
.ToList();

return new ProfileLookupModel()
{
VariableTypes = items.Where(x => x.ProfileTypeId.Equals((int)ProfileItemTypeEnum.VariableType))
.Select(x => new ProfileTypeDefinitionSimpleModel()
{
Name = x.Name,
//Type = (ProfileItemTypeEnum)x.ProfileTypeId.Value,
ProfileId = x.ProfileId.Value,
VariableDataTypeId = x.VariableDataTypeId.Value,
SymbolicName = x.SymbolicName,
ID = x.ID,
BrowseName = x.BrowseName,
}).ToList(),
Compositions = itemsComposition
.Select(x => new ProfileTypeDefinitionSimpleModel()
{
Name = x.Name,
ProfileId = x.ProfileId,
VariableDataTypeId = x.VariableDataTypeId.HasValue ? x.VariableDataTypeId.Value : null,
ID = x.ID,
BrowseName = x.BrowseName,
}).ToList(),
Interfaces = itemsInterface
.Select(x => new ProfileTypeDefinitionModel()
{
Name = x.Name,
ProfileId = x.ProfileId,
ID = x.ID,
BrowseName = x.BrowseName,
}).ToList()
};
}
}

protected async Task<AppLookupModel> GetLookupData(MyNamespace.Client apiClient, Guid guidCommon)
{
//get some static lookup data from db by calling api. get mock inserted data in a more direct manner.
var result = await apiClient.ApiGetItemGenericAsync<AppLookupModel>(URL_LOOKUP_ALL, method: "GET");

using (var scope = _serviceProvider.CreateScope())
{
//var repo = scope.ServiceProvider.GetService<IRepository<ProfileTypeDefinition>>();
var repoDataType = scope.ServiceProvider.GetService<IRepository<LookupDataType>>();

var itemDatatypes = repoDataType.FindByCondition(x => x.Code.Contains(guidCommon.ToString()));
result.DataTypes = itemDatatypes.Select(x => new LookupDataTypeRankedModel()
{
Code = x.Code,
BaseDataTypeId = x.CustomTypeId,
CustomTypeId = x.CustomTypeId,
Name = x.Name
}).ToList();
}
return result;
}

/// <summary>
/// Create a mock parent item. Then set up a newly extended item with that parent item.
/// Finally, apply the model values to the newly extended item and return.
/// </summary>
/// <returns></returns>
protected async Task<ProfileTypeDefinitionModel> MapModelToExtendedItem(MyNamespace.Client apiClient, Guid guidCommon,
ProfileTypeDefinition itemExtend, ProfileTypeDefinitionModel model)
{
//extend item
var result = await apiClient.ApiGetItemAsync<ProfileTypeDefinitionModel>(URL_EXTEND,
new IdIntModel() { ID = itemExtend.ID.Value });
//map data to newly created extend
result.OpcNodeId = model.OpcNodeId;
result.Name = model.Name;
result.BrowseName = model.BrowseName;
result.SymbolicName = guidCommon.ToString(); //so we can delete this item once done
result.Description = model.Description;
result.Created = model.Created;
result.MetaTags = model.MetaTags;
result.Attributes = model.Attributes;
result.ProfileId = itemExtend.ProfileId;
result.Profile = new ProfileModel() { ID = itemExtend.ProfileId };
return result;
}


protected static ProfileTypeDefinitionModel MapToModel(ProfileTypeDefinition entity)
{
var tags = string.IsNullOrEmpty(entity.MetaTags) ? new List<string>() :
@@ -181,63 +473,7 @@ protected static ProfileTypeDefinitionSimpleModel MapToModelSimple(ProfileTypeDe
Author = new UserSimpleModel() {ID = entity.AuthorId } ,
IsAbstract = entity.IsAbstract,
MetaTags = string.IsNullOrEmpty(entity.MetaTags) ? new List<string>() :
Newtonsoft.Json.JsonConvert.DeserializeObject<List<MetaTag>>(entity.MetaTags).Select(s => s.Name.Trim()).ToList(),
};
}

/// <summary>
/// This is used to create a row directly into DB. Bypasses everything except baseRepo
/// </summary>
/// <param name="i"></param>
/// <param name="uuid"></param>
/// <param name="creator"></param>
/// <param name="cloudLibraryId"></param>
/// <returns></returns>
protected static ProfileTypeDefinition CreateEntity(int i, int? profileId, ProfileTypeDefinition parent, Guid guidCommon, Guid uuid, User user)
{
var parentName = parent == null ? "TypeDef" : $"{parent.Name}::Extend";
var dt = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
var tags = new List<MetaTag>() {
new MetaTag() { Name = (i % 4 == 0 ? "abcd" : (i % 3 == 0) ? "efgh" : (i % 2 == 0) ? "ijkl" : "mnop") }
};
return new ProfileTypeDefinition()
{
OpcNodeId = uuid.ToString(),
Name = $"{parentName}-{i}",
ProfileId = profileId,
ParentId = parent?.ID, //for some tests, we start with null parent and assign during test
BrowseName = $"browse-{i}-{guidCommon}-{uuid}",
SymbolicName = guidCommon.ToString(),
Description = (i % 3 == 0 ? "Unique description for 3" : (i % 2 == 0) ? "Unique description for 2" : "Common description"),
ProfileTypeId = parent == null ? TYPE_ID_DEFAULT : parent?.ProfileTypeId,
IsAbstract = i % 9 == 0,
Created = dt,
Updated = dt,
AuthorId = user?.ID,
OwnerId = user?.ID,
CreatedById = user == null ? 0 : user.ID.Value,
UpdatedById = user == null ? 0 : user.ID.Value,
MetaTags = Newtonsoft.Json.JsonConvert.SerializeObject(tags),
IsActive = true,
};
}

/// <summary>
/// This is used to create a row directly into DB. Bypasses everything except baseRepo
/// </summary>
protected static Profile CreateProfileEntity(Guid guidCommon, User user)
{
var dt = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
return new Profile()
{
Namespace = $"{PARENT_PROFILE_NAMESPACE}/{guidCommon}",
Title = TITLE_PATTERN,
Version = "1.0.0.0",
CategoryName = "TEST",
PublishDate = dt,
AuthorId = user?.ID,
OwnerId = user != null ? user.ID : null,
Keywords = new string[] { guidCommon.ToString() }
JsonConvert.DeserializeObject<List<MetaTag>>(entity.MetaTags).Select(s => s.Name.Trim()).ToList(),
};
}

@@ -253,10 +489,9 @@ protected virtual async Task CleanupEntities()
{
//type defs
var repo = scope.ServiceProvider.GetService<IRepository<ProfileTypeDefinition>>();

//var repoUser = scope.ServiceProvider.GetService<IRepository<User>>();
//var user = GetTestUser(repoUser);
//var itemsAll = repo.FindByCondition(x => x.OwnerId.Equals(user.ID)).ToList();
var repoProfile = scope.ServiceProvider.GetService<IRepository<Profile>>();
var repoAttribute = scope.ServiceProvider.GetService<IRepository<ProfileAttribute>>();
var repoDataType = scope.ServiceProvider.GetService<IRepository<LookupDataType>>();

//order by to account for some fk delete issues, include child tables - analytics, attributes, compositions, interfaces
var items = repo.FindByCondition(x =>
@@ -270,11 +505,15 @@ protected virtual async Task CleanupEntities()
.ToList();

//parent profiles
var repoProfile = scope.ServiceProvider.GetService<IRepository<Profile>>();
var itemsProfile = repoProfile.FindByCondition(x =>
items.Select(y => y.ProfileId.Value).Contains(x.ID.Value))
.ToList();

//child attrs
var itemsAttribute = repoAttribute.FindByCondition(x =>
items.Select(y => y.ID.Value).Contains(x.ProfileTypeDefinitionId.Value))
.ToList();

//get intermediate items created server side that are related to items test created - intermediate objs
//assuming the item would be a child of the parent profile we will delete below.
//prevent dups - only the items not collected above so that we don't try to delete something not there.
@@ -287,42 +526,22 @@ protected virtual async Task CleanupEntities()
.Include(x => x.Attributes)
.ToList();

//works
//var itemsIntermediate = repo.FindByCondition(x =>
// string.IsNullOrEmpty(x.SymbolicName) && ((ProfileItemTypeEnum)x.ProfileTypeId).Equals(ProfileItemTypeEnum.Object)
// && (x.InstanceParentId.HasValue && items.Select(y => y.ID.Value).Contains(x.InstanceParentId.Value))
// )
// .ToList();

//var itemsIntermediate = itemsIntermediate0.Where(x =>
// items.Select(y => y.ID.Value).Contains(x.InstanceParentId.Value)
// )
// .ToList();
//var itemsIntermediate1 = itemsIntermediate0.Where(x =>
// (x.ParentId.HasValue && items.Select(y => y.ID.Value).Contains(x.ParentId.Value)) ||
// (x.InstanceParentId.HasValue && items.Select(y => y.ID.Value).Contains(x.InstanceParentId.Value))
// )
// .ToList();

/*
var itemsIntermediate = repo.FindByCondition(x =>
string.IsNullOrEmpty(x.SymbolicName) && ((ProfileItemTypeEnum)x.ProfileTypeId).Equals(ProfileItemTypeEnum.Object) &&
((x.ParentId.HasValue && items.Select(y => y.ID.Value).Equals(x.ParentId.Value)) ||
(x.InstanceParentId.HasValue && items.Select(y => y.ID.Value).Equals(x.InstanceParentId.Value)))
)
.ToList();
*/
//type def analytics
//var repoAnalytic = scope.ServiceProvider.GetService<IRepository<ProfileTypeDefinitionAnalytic>>();
//order to account for some fk delete issues
//var itemsAnalytic = repoAnalytic.FindByCondition(x =>
// items.Select(y => y.ID.Value).Contains(x.ProfileTypeDefinitionId))
// .ToList();
//foreach (var a in itemsAnalytic)
//{
//await repoAnalytic.DeleteAsync(a);
//}
//await repoAnalytic.SaveChangesAsync();
var itemsDataType = repoDataType.FindByCondition(x => x.Code.Contains(_guidCommon.ToString())).ToList();

//order of operation matters - several dependencies and FK constraints
//attributes associated w/ type defs - do these first so we can remove FK in order to then delete data types
foreach (var item in itemsAttribute)
{
await repoAttribute.DeleteAsync(item);
}
await repoAttribute.SaveChangesAsync();

//data types
foreach (var item in itemsDataType)
{
await repoDataType.DeleteAsync(item);
}
await repoDataType.SaveChangesAsync();

//type defs
foreach (var item in items)
@@ -347,7 +566,6 @@ protected virtual async Task CleanupEntities()
}
#endregion


/// <summary>
/// do any post test cleanup here.
/// </summary>
Original file line number Diff line number Diff line change
@@ -3,49 +3,25 @@
using System.Threading.Tasks;
using System.Linq;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

using Xunit;
using Xunit.Abstractions;
using Newtonsoft.Json;

using CESMII.ProfileDesigner.Common.Enums;
using CESMII.ProfileDesigner.DAL.Models;
using CESMII.ProfileDesigner.Data.Repositories;
using CESMII.ProfileDesigner.Data.Entities;
using CESMII.ProfileDesigner.Data.Contexts;
using CESMII.ProfileDesigner.Api.Shared.Models;

namespace CESMII.ProfileDesigner.Api.Tests.Int
{
public class TypeDefControllerAttributeIntegrationTest : ProfileTypeDefControllerTestBase
{
//get some lookup data that will be needed when we start adding addtribute tests
private AppLookupModel _lookupData = null;
private ProfileLookupModel _lookupRelated = null;

#region API constants
private const string URL_LOOKUP_ALL = "/api/lookup/all";
private const string URL_LOOKUP_RELATED = "/api/profiletypedefinition/lookup/profilerelated/extend";
#endregion

public TypeDefControllerAttributeIntegrationTest(
CustomWebApplicationFactory<Api.Startup> factory,
ITestOutputHelper output):
CustomWebApplicationFactory<Api.Startup> factory,
ITestOutputHelper output) :
base(factory, output)
{
//load lookup data that will be needed for attribute adds
//note there is a dependency that the DB contains this look up data prior to the test being run.
//some of the data is seeded by the create db script, some of the data is seeded by the import of the
//2 root nodesets used everywhere - ua and ua/di

//get lookup data to be used when adding attributes
var apiClient = base.ApiClient;
_lookupData = apiClient.ApiGetItemGenericAsync<AppLookupModel>(URL_LOOKUP_ALL, method:"GET").Result;
//get related lookup data which depends on existence of type def
_lookupRelated = apiClient.ApiGetItemGenericAsync<ProfileLookupModel>(URL_LOOKUP_RELATED).Result;

}

#pragma warning disable xUnit1026 // Stop warnings related to parameters not used in test cases.
@@ -56,31 +32,32 @@ public TypeDefControllerAttributeIntegrationTest(
/// <param name="model"></param>
/// <returns></returns>
[Theory]
[InlineData(AttributeTypeIdEnum.DataVariable, "BaseDataVariableType", "Boolean", null, 5)]
[InlineData(AttributeTypeIdEnum.DataVariable, "TransitionVariableType", "Float", null, 4)]
[InlineData(AttributeTypeIdEnum.DataVariable, "AnalogItemType", "Double", null, 3)]
//[InlineData(AttributeTypeIdEnum.Property, null, "Int64", null, 5)]
//[InlineData(AttributeTypeIdEnum.Property, null, "Counter", null, 4)]
//[InlineData(AttributeTypeIdEnum.Property, null, "String", null, 3)]
//[InlineData(AttributeTypeIdEnum.StructureField, "BaseDataVariableType", "", null, 4)]
//[InlineData(AttributeTypeIdEnum.EnumField, "BaseDataVariableType", "", null, 3)]
[InlineData(AttributeTypeIdEnum.DataVariable, "_BaseDataVariableType", "_Boolean", 5)]
[InlineData(AttributeTypeIdEnum.DataVariable, "_BaseDataVariableType", "_Float", 4)]
[InlineData(AttributeTypeIdEnum.DataVariable, "_BaseDataVariableType", "_Double", 3)]
//[InlineData(AttributeTypeIdEnum.Property, null, "Int64", 5)]
//[InlineData(AttributeTypeIdEnum.Property, null, "Counter", 4)]
//[InlineData(AttributeTypeIdEnum.Property, null, "String", 3)]
//[InlineData(AttributeTypeIdEnum.StructureField, "BaseDataVariableType", "", 4)]
//[InlineData(AttributeTypeIdEnum.EnumField, "BaseDataVariableType", "", 3)]
public async Task AddItem_AddAttributes(AttributeTypeIdEnum attrType, string variableTypeName
, string dataTypeName, string engUnitName, int numItems)
, string dataTypeName, int numItems)
{
// ARRANGE
//get api client
var apiClient = base.ApiClient;

//create parent profile and entity to extend
var itemExtend = await InsertMockProfileAndExtendEntity(_guidCommon);
var model = CreateItemModel(1,itemExtend.ProfileId, itemExtend, _guidCommon, Guid.NewGuid());
var itemExtend = await InsertMockProfileAndTypeDefinition(TYPE_ID_DEFAULT, _guidCommon);
var model = CreateItemModel(1, itemExtend.ProfileId, itemExtend, _guidCommon, Guid.NewGuid());
var resultExtend = await MapModelToExtendedItem(apiClient, _guidCommon, itemExtend, model);

//add attributes
resultExtend.ProfileAttributes = new List<ProfileAttributeModel>();
for (int i = 1; i <= numItems; i++)
{
resultExtend.ProfileAttributes.Add(CreateAttribute($"Attribute-{i}", attrType, variableTypeName, dataTypeName, engUnitName, _guidCommon));
var attr = CreateAttribute($"Attribute-{i}", attrType, variableTypeName, dataTypeName, _guidCommon);
resultExtend.ProfileAttributes.Add(attr);
}

// ACT
@@ -100,29 +77,35 @@ public async Task AddItem_AddAttributes(AttributeTypeIdEnum attrType, string var
/// <summary>
/// Extend from an item, then Add many different composition attributes and save. Confirm attributes added properly
/// </summary>
/// <remarks>To pass in multiple strings in each test, separate with |</remarks>
/// <param name="model"></param>
/// <returns></returns>
[Theory]
[InlineData("AlarmMetricsType", 1)]
[InlineData("AliasNameType", 1)]
[InlineData("PubSubConfigurationType", 1)]
[InlineData("ServerType", 1)]
public async Task AddItem_AddAttributeComposition(string compositionName, int numItems)
[InlineData(5)]
[InlineData(4)]
[InlineData(3)]
[InlineData(2)]
[InlineData(1)]
public async Task AddItem_AddAttributeComposition(int numItems)
{
const string COMPOSITION_NAME_ROOT = "_BaseComposition_";

// ARRANGE
//get api client
var apiClient = base.ApiClient;

//create parent profile and entity to extend
var itemExtend = await InsertMockProfileAndExtendEntity(_guidCommon);
var itemExtend = await InsertMockProfileAndTypeDefinition(TYPE_ID_DEFAULT, _guidCommon);
var model = CreateItemModel(1, itemExtend.ProfileId, itemExtend, _guidCommon, Guid.NewGuid());
var resultExtend = await MapModelToExtendedItem(apiClient, _guidCommon, itemExtend, model);

//add attributes
resultExtend.ProfileAttributes = new List<ProfileAttributeModel>();
for (int i = 1; i <= numItems; i++)
{
resultExtend.ProfileAttributes.Add(CreateAttributeComposition($"Attribute-Comp-{i}", compositionName, _guidCommon));
var n = $"{COMPOSITION_NAME_ROOT}{i}";
resultExtend.ProfileAttributes.Add(CreateAttributeComposition($"Attribute-Comp-{i}", n, _guidCommon,
_lookupRelated, _lookupData));
}

// ACT
@@ -143,14 +126,13 @@ public async Task AddItem_AddAttributeComposition(string compositionName, int nu
/// Create an attribute
/// </summary>
private ProfileAttributeModel CreateAttribute(
string name, AttributeTypeIdEnum attrTypeId, string variableTypeName, string dataTypeName,
string engUnitName, Guid guidCommon)
string name, AttributeTypeIdEnum attrTypeId, string variableTypeName, string dataTypeName, Guid guidCommon)
{
var dt = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
var attrType = _lookupData.AttributeTypes.Find(x => x.ID.Value.Equals((int)attrTypeId));
var dataType = _lookupData.DataTypes.Find(x => x.Name.ToLower().Equals(dataTypeName.ToLower()));
var varType = string.IsNullOrEmpty(variableTypeName) ? null : _lookupRelated.VariableTypes.Find(x => x.Name.ToLower().Equals(variableTypeName.ToLower()));
var engUnit = string.IsNullOrEmpty(engUnitName) ? null : _lookupData.EngUnits.Find(x => x.DisplayName.ToLower().Equals(engUnitName.ToLower()));
//var engUnit = string.IsNullOrEmpty(engUnitName) ? null : _lookupData.EngUnits.Find(x => x.DisplayName.ToLower().Equals(engUnitName.ToLower()));

return new ProfileAttributeModel()
{
@@ -163,46 +145,14 @@ private ProfileAttributeModel CreateAttribute(
VariableTypeDefinition = string.IsNullOrEmpty(variableTypeName) ? null :
new ProfileTypeDefinitionModel() { ID = varType.ID, Name = varType.Name, BrowseName = varType.BrowseName },
VariableTypeDefinitionId = string.IsNullOrEmpty(variableTypeName) ? null : varType.ID,
EngUnit = engUnit,
//EngUnit = engUnit,
Name = name
};
}

/// <summary>
/// Create an attribute
/// </summary>
private ProfileAttributeModel CreateAttributeComposition(string name, string compositionName, Guid guidCommon)
{
var dt = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc);
var comp = _lookupRelated.Compositions.Find(x => x.Name.ToLower().Equals(compositionName.ToLower()));
var attrType = _lookupData.AttributeTypes.Find(x => x.Name.ToLower().Equals("composition"));
//var dataType = _lookupData.DataTypes.Find(x => x.Name.ToLower().Equals("composition"));

return new ProfileAttributeModel()
{
CompositionId = comp.ID,
Composition = new ProfileTypeDefinitionRelatedModel()
{
ID = comp.ID,
SymbolicName = _guidCommon.ToString(),
RelatedProfileTypeDefinitionId = comp.ID,
Name = comp.Name
},
AttributeType = attrType,
BrowseName = guidCommon.ToString(),
SymbolicName = guidCommon.ToString(),
//DataType = dataType,
//DataTypeId = dataType.ID,
Name = name
};
}
#endregion

//dispose happens in base class.
//public override void Dispose()
//{
// CleanupEntities().Wait();
//}

}
}